@hasna/hooks 0.9.1 → 0.9.3
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 +10 -0
- package/bin/hooks-mcp.js +295 -98
- package/bin/index.js +430 -168
- package/bin/serve.js +11 -0
- package/dist/index.js +302 -104
- package/dist/lib/codex-settings.d.ts +3 -0
- package/dist/lib/installer.d.ts +5 -2
- package/dist/lib/mementos-options.d.ts +16 -0
- package/dist/lib/registration.d.ts +1 -0
- package/hooks/hook-mementos-context/README.md +106 -0
- package/hooks/hook-mementos-context/src/hook.ts +116 -0
- package/package.json +5 -4
package/dist/index.js
CHANGED
|
@@ -4974,21 +4974,21 @@ __export(exports_profiles, {
|
|
|
4974
4974
|
deleteProfile: () => deleteProfile,
|
|
4975
4975
|
createProfile: () => createProfile
|
|
4976
4976
|
});
|
|
4977
|
-
import { existsSync as
|
|
4977
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5, readdirSync as readdirSync4, rmSync, cpSync as cpSync2 } from "fs";
|
|
4978
4978
|
import { join as join8 } from "path";
|
|
4979
4979
|
function getProfilesDir() {
|
|
4980
4980
|
const newDir = join8(getEffectiveDataRoot(), "profiles");
|
|
4981
4981
|
const oldDir = join8(getHomeDir(), ".hooks", "profiles");
|
|
4982
|
-
if (!
|
|
4983
|
-
|
|
4982
|
+
if (!existsSync9(newDir) && existsSync9(oldDir)) {
|
|
4983
|
+
mkdirSync6(newDir, { recursive: true });
|
|
4984
4984
|
cpSync2(oldDir, newDir, { recursive: true });
|
|
4985
4985
|
}
|
|
4986
4986
|
return newDir;
|
|
4987
4987
|
}
|
|
4988
4988
|
function ensureProfilesDir() {
|
|
4989
4989
|
const dir = getProfilesDir();
|
|
4990
|
-
if (!
|
|
4991
|
-
|
|
4990
|
+
if (!existsSync9(dir)) {
|
|
4991
|
+
mkdirSync6(dir, { recursive: true });
|
|
4992
4992
|
}
|
|
4993
4993
|
}
|
|
4994
4994
|
function profilePath(id) {
|
|
@@ -5011,30 +5011,30 @@ function createProfile(input) {
|
|
|
5011
5011
|
if (input.name) {
|
|
5012
5012
|
profile.name = input.name;
|
|
5013
5013
|
}
|
|
5014
|
-
|
|
5014
|
+
writeFileSync5(profilePath(id), JSON.stringify(profile, null, 2) + `
|
|
5015
5015
|
`);
|
|
5016
5016
|
return profile;
|
|
5017
5017
|
}
|
|
5018
5018
|
function getProfile(id) {
|
|
5019
5019
|
const path = profilePath(id);
|
|
5020
5020
|
try {
|
|
5021
|
-
if (!
|
|
5021
|
+
if (!existsSync9(path))
|
|
5022
5022
|
return null;
|
|
5023
|
-
return JSON.parse(
|
|
5023
|
+
return JSON.parse(readFileSync6(path, "utf-8"));
|
|
5024
5024
|
} catch {
|
|
5025
5025
|
return null;
|
|
5026
5026
|
}
|
|
5027
5027
|
}
|
|
5028
5028
|
function listProfiles() {
|
|
5029
5029
|
const dir = getProfilesDir();
|
|
5030
|
-
if (!
|
|
5030
|
+
if (!existsSync9(dir))
|
|
5031
5031
|
return [];
|
|
5032
5032
|
try {
|
|
5033
5033
|
const files = readdirSync4(dir).filter((f) => f.endsWith(".json"));
|
|
5034
5034
|
const profiles = [];
|
|
5035
5035
|
for (const file of files) {
|
|
5036
5036
|
try {
|
|
5037
|
-
const content =
|
|
5037
|
+
const content = readFileSync6(join8(dir, file), "utf-8");
|
|
5038
5038
|
profiles.push(JSON.parse(content));
|
|
5039
5039
|
} catch {}
|
|
5040
5040
|
}
|
|
@@ -5051,13 +5051,13 @@ function updateProfile(id, data) {
|
|
|
5051
5051
|
profile.name = data.name;
|
|
5052
5052
|
if (data.preferences !== undefined)
|
|
5053
5053
|
profile.preferences = data.preferences;
|
|
5054
|
-
|
|
5054
|
+
writeFileSync5(profilePath(id), JSON.stringify(profile, null, 2) + `
|
|
5055
5055
|
`);
|
|
5056
5056
|
return profile;
|
|
5057
5057
|
}
|
|
5058
5058
|
function deleteProfile(id) {
|
|
5059
5059
|
const path = profilePath(id);
|
|
5060
|
-
if (!
|
|
5060
|
+
if (!existsSync9(path))
|
|
5061
5061
|
return false;
|
|
5062
5062
|
try {
|
|
5063
5063
|
rmSync(path);
|
|
@@ -5071,7 +5071,7 @@ function touchProfile(id) {
|
|
|
5071
5071
|
if (!profile)
|
|
5072
5072
|
return;
|
|
5073
5073
|
profile.last_seen_at = new Date().toISOString();
|
|
5074
|
-
|
|
5074
|
+
writeFileSync5(profilePath(id), JSON.stringify(profile, null, 2) + `
|
|
5075
5075
|
`);
|
|
5076
5076
|
}
|
|
5077
5077
|
function exportProfiles() {
|
|
@@ -5087,11 +5087,11 @@ function importProfiles(profiles) {
|
|
|
5087
5087
|
continue;
|
|
5088
5088
|
}
|
|
5089
5089
|
const path = profilePath(profile.agent_id);
|
|
5090
|
-
if (
|
|
5090
|
+
if (existsSync9(path)) {
|
|
5091
5091
|
skipped++;
|
|
5092
5092
|
continue;
|
|
5093
5093
|
}
|
|
5094
|
-
|
|
5094
|
+
writeFileSync5(path, JSON.stringify(profile, null, 2) + `
|
|
5095
5095
|
`);
|
|
5096
5096
|
imported++;
|
|
5097
5097
|
}
|
|
@@ -5104,7 +5104,7 @@ var init_profiles = __esm(() => {
|
|
|
5104
5104
|
// ../../node_modules/.bun/@hasna+contracts@1.0.2+e0db24ae03127387/node_modules/@hasna/contracts/dist/client/transport.js
|
|
5105
5105
|
import { isIP } from "net";
|
|
5106
5106
|
import { spawnSync } from "child_process";
|
|
5107
|
-
import { closeSync, fstatSync, openSync, readFileSync as
|
|
5107
|
+
import { closeSync as closeSync2, fstatSync, openSync as openSync2, readFileSync as readFileSync7 } from "fs";
|
|
5108
5108
|
import { O_NOFOLLOW, O_NONBLOCK, O_RDONLY } from "constants";
|
|
5109
5109
|
import { createRequire } from "module";
|
|
5110
5110
|
import { hostname as osHostname } from "os";
|
|
@@ -5208,7 +5208,7 @@ function readAppConfigFile(path) {
|
|
|
5208
5208
|
};
|
|
5209
5209
|
let fd = -1;
|
|
5210
5210
|
try {
|
|
5211
|
-
fd =
|
|
5211
|
+
fd = openSync2(path, O_RDONLY | O_NOFOLLOW | O_NONBLOCK);
|
|
5212
5212
|
} catch (error) {
|
|
5213
5213
|
const code = error.code;
|
|
5214
5214
|
if (code === "ENOENT" || code === "ENOTDIR")
|
|
@@ -5229,7 +5229,7 @@ function readAppConfigFile(path) {
|
|
|
5229
5229
|
unsafe("the file is not owned by the current user");
|
|
5230
5230
|
if (before.size > MAX_CREDENTIAL_FILE_BYTES)
|
|
5231
5231
|
unsafe("the file exceeds the size limit");
|
|
5232
|
-
const bytes =
|
|
5232
|
+
const bytes = readFileSync7(fd);
|
|
5233
5233
|
const after = fstatSync(fd);
|
|
5234
5234
|
if (!configFileReadsCoherent(before, after)) {
|
|
5235
5235
|
unsafe("the file changed while being read");
|
|
@@ -5237,7 +5237,7 @@ function readAppConfigFile(path) {
|
|
|
5237
5237
|
return parseEnvFile(bytes.toString("utf8"));
|
|
5238
5238
|
} finally {
|
|
5239
5239
|
if (fd !== -1)
|
|
5240
|
-
|
|
5240
|
+
closeSync2(fd);
|
|
5241
5241
|
}
|
|
5242
5242
|
}
|
|
5243
5243
|
function readCredentialFile(path, apiKeyKeys) {
|
|
@@ -6013,8 +6013,8 @@ var init_db_writer = __esm(() => {
|
|
|
6013
6013
|
});
|
|
6014
6014
|
|
|
6015
6015
|
// src/lib/hook-env.ts
|
|
6016
|
-
import { dirname as
|
|
6017
|
-
import { existsSync as
|
|
6016
|
+
import { dirname as dirname6, isAbsolute as isAbsolute3 } from "path";
|
|
6017
|
+
import { existsSync as existsSync11, realpathSync as realpathSync2, statSync } from "fs";
|
|
6018
6018
|
import { tmpdir as tmpdir2 } from "os";
|
|
6019
6019
|
function isDeniedEnvName(name) {
|
|
6020
6020
|
const upper = name.toUpperCase();
|
|
@@ -6062,9 +6062,9 @@ function isUnsafePathEntry(entry, home) {
|
|
|
6062
6062
|
}
|
|
6063
6063
|
function sanitizeHookPath(pathValue, home) {
|
|
6064
6064
|
const baseline = PATH_BASELINE.slice();
|
|
6065
|
-
if (process.platform === "darwin" &&
|
|
6065
|
+
if (process.platform === "darwin" && existsSync11("/opt/homebrew/bin"))
|
|
6066
6066
|
baseline.push("/opt/homebrew/bin");
|
|
6067
|
-
const bunDir =
|
|
6067
|
+
const bunDir = dirname6(process.execPath);
|
|
6068
6068
|
const seen = new Set;
|
|
6069
6069
|
const out = [];
|
|
6070
6070
|
for (const dir of baseline) {
|
|
@@ -6188,8 +6188,8 @@ __export(exports_run, {
|
|
|
6188
6188
|
HookTimeoutError: () => HookTimeoutError
|
|
6189
6189
|
});
|
|
6190
6190
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
6191
|
-
import { rmSync as rmSync3, writeFileSync as
|
|
6192
|
-
import { basename as basename2, dirname as
|
|
6191
|
+
import { rmSync as rmSync3, writeFileSync as writeFileSync6 } from "fs";
|
|
6192
|
+
import { basename as basename2, dirname as dirname7, isAbsolute as isAbsolute4, join as join12 } from "path";
|
|
6193
6193
|
function extensionOf(scriptPath) {
|
|
6194
6194
|
const base = basename2(scriptPath);
|
|
6195
6195
|
const dot = base.lastIndexOf(".");
|
|
@@ -6258,7 +6258,7 @@ async function readPipeWithDeadline(stream, deadlineMs) {
|
|
|
6258
6258
|
return chunks.join("");
|
|
6259
6259
|
}
|
|
6260
6260
|
async function executeVerifiedScript(options) {
|
|
6261
|
-
const scriptDir =
|
|
6261
|
+
const scriptDir = dirname7(options.scriptPath);
|
|
6262
6262
|
const interpreter = interpreterFor(options.name, options.scriptPath, options.content);
|
|
6263
6263
|
const tempPath = join12(scriptDir, `.hooks-verified-${randomBytes2(12).toString("hex")}.${interpreter.tempExt}`);
|
|
6264
6264
|
let proc = null;
|
|
@@ -6268,7 +6268,7 @@ async function executeVerifiedScript(options) {
|
|
|
6268
6268
|
killGroup(proc, { leaderAlive: proc !== null && proc.exitCode === null });
|
|
6269
6269
|
}, options.timeout) : null;
|
|
6270
6270
|
try {
|
|
6271
|
-
|
|
6271
|
+
writeFileSync6(tempPath, options.content, { flag: "wx", mode: 384 });
|
|
6272
6272
|
proc = Bun.spawn([...interpreter.command, isAbsolute4(tempPath) ? tempPath : `./${tempPath}`, ...options.args ?? []], {
|
|
6273
6273
|
stdin: new Response(options.stdin),
|
|
6274
6274
|
stdout: "pipe",
|
|
@@ -7016,6 +7016,17 @@ var HOOKS = [
|
|
|
7016
7016
|
matcher: "",
|
|
7017
7017
|
tags: ["knowledge", "context", "codewith", "deterministic", "session-start", "prompt", "subagent"]
|
|
7018
7018
|
},
|
|
7019
|
+
{
|
|
7020
|
+
name: "mementos-context",
|
|
7021
|
+
displayName: "Mementos Prompt Context",
|
|
7022
|
+
description: "Retrieves scoped memories for a user prompt, with optional relevance judgments",
|
|
7023
|
+
version: "0.1.0",
|
|
7024
|
+
category: "Context Management",
|
|
7025
|
+
event: "UserPromptSubmit",
|
|
7026
|
+
matcher: "",
|
|
7027
|
+
timeoutSeconds: 12,
|
|
7028
|
+
tags: ["mementos", "memory", "prompt", "codex", "claude", "codewith", "opt-in"]
|
|
7029
|
+
},
|
|
7019
7030
|
{
|
|
7020
7031
|
name: "autoformat",
|
|
7021
7032
|
displayName: "Auto Format",
|
|
@@ -7341,8 +7352,8 @@ function getHook(name) {
|
|
|
7341
7352
|
return HOOKS.find((h) => h.name === name);
|
|
7342
7353
|
}
|
|
7343
7354
|
// src/lib/installer.ts
|
|
7344
|
-
import { existsSync as
|
|
7345
|
-
import { join as join7, dirname as
|
|
7355
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync5, readdirSync as readdirSync3, rmdirSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
7356
|
+
import { join as join7, dirname as dirname4 } from "path";
|
|
7346
7357
|
import { homedir as homedir3 } from "os";
|
|
7347
7358
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
7348
7359
|
|
|
@@ -7434,15 +7445,157 @@ function resolveHook(name) {
|
|
|
7434
7445
|
// src/lib/installer.ts
|
|
7435
7446
|
init_manifest();
|
|
7436
7447
|
|
|
7448
|
+
// src/lib/mementos-options.ts
|
|
7449
|
+
function validateMementosRegistration(name, value) {
|
|
7450
|
+
if (value === undefined)
|
|
7451
|
+
return;
|
|
7452
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value)) || !Object.hasOwn(value, "enabled") || Object.keys(value).some((key) => key !== "enabled" && key !== "secretRef") || name.replace(/^hook-/, "") !== "mementos-context" || typeof value.enabled !== "boolean") {
|
|
7453
|
+
throw new Error("Mementos options require the mementos-context hook and an explicit enable/disable choice.");
|
|
7454
|
+
}
|
|
7455
|
+
if (value.secretRef !== undefined && (!value.enabled || typeof value.secretRef !== "string" || value.secretRef.length > 256 || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]*(?:\/[a-zA-Z0-9][a-zA-Z0-9_.-]*){1,12}$/.test(value.secretRef))) {
|
|
7456
|
+
throw new Error("OpenRouter requires an enabled Mementos hook and a valid Secrets reference, never a key value.");
|
|
7457
|
+
}
|
|
7458
|
+
}
|
|
7459
|
+
function mementosRegistrationFromFlags(name, flags) {
|
|
7460
|
+
if (!flags.enableMementos && !flags.disableMementos && flags.openrouterSecretRef === undefined)
|
|
7461
|
+
return;
|
|
7462
|
+
if (Boolean(flags.enableMementos) === Boolean(flags.disableMementos)) {
|
|
7463
|
+
throw new Error("Choose exactly one of --enable-mementos or --disable-mementos.");
|
|
7464
|
+
}
|
|
7465
|
+
const value = { enabled: flags.enableMementos === true, ...flags.openrouterSecretRef !== undefined ? { secretRef: flags.openrouterSecretRef } : {} };
|
|
7466
|
+
validateMementosRegistration(name, value);
|
|
7467
|
+
return value;
|
|
7468
|
+
}
|
|
7469
|
+
function mementosCommandSuffix(value) {
|
|
7470
|
+
validateMementosRegistration("mementos-context", value);
|
|
7471
|
+
if (!value)
|
|
7472
|
+
return "";
|
|
7473
|
+
return value.enabled ? ` --enable-mementos${value.secretRef ? ` --openrouter-secret-ref ${value.secretRef}` : ""}` : " --disable-mementos";
|
|
7474
|
+
}
|
|
7475
|
+
function retainedMementosRegistration(commands) {
|
|
7476
|
+
const values = commands.filter((command) => /^hooks run mementos-context(?:\s|$)/.test(command)).map((command) => {
|
|
7477
|
+
const args = command.trim().split(/\s+/).slice(3);
|
|
7478
|
+
const flags = {};
|
|
7479
|
+
const seen = new Set;
|
|
7480
|
+
while (args.length) {
|
|
7481
|
+
const key = args.shift();
|
|
7482
|
+
if (seen.has(key))
|
|
7483
|
+
throw new Error("Ambiguous Mementos registration; specify its enablement explicitly.");
|
|
7484
|
+
seen.add(key);
|
|
7485
|
+
if (key === "--profile" && /^[\w-]+$/.test(args[0] ?? ""))
|
|
7486
|
+
args.shift();
|
|
7487
|
+
else if (key === "--enable-mementos")
|
|
7488
|
+
flags.enableMementos = true;
|
|
7489
|
+
else if (key === "--disable-mementos")
|
|
7490
|
+
flags.disableMementos = true;
|
|
7491
|
+
else if (key === "--openrouter-secret-ref" && args.length)
|
|
7492
|
+
flags.openrouterSecretRef = args.shift();
|
|
7493
|
+
else
|
|
7494
|
+
throw new Error("Unrecognized Mementos registration options; specify its enablement explicitly.");
|
|
7495
|
+
}
|
|
7496
|
+
return mementosRegistrationFromFlags("mementos-context", flags);
|
|
7497
|
+
});
|
|
7498
|
+
if (new Set(values.map((value) => JSON.stringify(value))).size > 1) {
|
|
7499
|
+
throw new Error("Conflicting Mementos registrations; specify its enablement explicitly.");
|
|
7500
|
+
}
|
|
7501
|
+
return values[0];
|
|
7502
|
+
}
|
|
7503
|
+
|
|
7437
7504
|
// src/lib/registration.ts
|
|
7505
|
+
var REGISTRATION_COMMAND_RE = /^(?:hooks run ([\w-]+)(?:\s+--profile\s+[\w-]+)?|hook-([\w-]+))$/;
|
|
7506
|
+
function registrationName(command) {
|
|
7507
|
+
if (typeof command !== "string")
|
|
7508
|
+
return;
|
|
7509
|
+
if (/^hooks run mementos-context(?:\s|$)/.test(command.trim())) {
|
|
7510
|
+
try {
|
|
7511
|
+
retainedMementosRegistration([command.trim()]);
|
|
7512
|
+
return "mementos-context";
|
|
7513
|
+
} catch {
|
|
7514
|
+
return;
|
|
7515
|
+
}
|
|
7516
|
+
}
|
|
7517
|
+
const match = command.trim().match(REGISTRATION_COMMAND_RE);
|
|
7518
|
+
return match ? match[1] ?? match[2] : undefined;
|
|
7519
|
+
}
|
|
7438
7520
|
function matchersOverlap(a, b) {
|
|
7439
7521
|
return a === b || a.includes(b) || b.includes(a);
|
|
7440
7522
|
}
|
|
7441
7523
|
|
|
7442
7524
|
// src/lib/installer.ts
|
|
7443
7525
|
init_store();
|
|
7444
|
-
|
|
7445
|
-
|
|
7526
|
+
|
|
7527
|
+
// src/lib/codex-settings.ts
|
|
7528
|
+
import { closeSync, existsSync as existsSync7, lstatSync, mkdirSync as mkdirSync4, openSync, readFileSync as readFileSync4, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
|
|
7529
|
+
import { dirname as dirname3 } from "path";
|
|
7530
|
+
import { randomUUID } from "crypto";
|
|
7531
|
+
var snapshots = new WeakMap;
|
|
7532
|
+
function current(path) {
|
|
7533
|
+
try {
|
|
7534
|
+
const stat = lstatSync(path);
|
|
7535
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 1048576)
|
|
7536
|
+
throw new Error("invalid_file");
|
|
7537
|
+
return readFileSync4(path, "utf8");
|
|
7538
|
+
} catch (error) {
|
|
7539
|
+
if (error.code === "ENOENT")
|
|
7540
|
+
return null;
|
|
7541
|
+
throw new Error("Cannot safely read Codex hooks.json; no settings were changed.");
|
|
7542
|
+
}
|
|
7543
|
+
}
|
|
7544
|
+
function readCodexSettings(path) {
|
|
7545
|
+
const raw = current(path);
|
|
7546
|
+
let settings;
|
|
7547
|
+
try {
|
|
7548
|
+
settings = raw === null ? {} : JSON.parse(raw);
|
|
7549
|
+
if (!settings || typeof settings !== "object" || Array.isArray(settings))
|
|
7550
|
+
throw new Error;
|
|
7551
|
+
if (settings.hooks !== undefined) {
|
|
7552
|
+
if (!settings.hooks || typeof settings.hooks !== "object" || Array.isArray(settings.hooks))
|
|
7553
|
+
throw new Error;
|
|
7554
|
+
for (const entries of Object.values(settings.hooks)) {
|
|
7555
|
+
if (!Array.isArray(entries) || entries.some((entry) => !entry || !Array.isArray(entry.hooks) || entry.hooks.some((hook) => !hook || typeof hook !== "object")))
|
|
7556
|
+
throw new Error;
|
|
7557
|
+
}
|
|
7558
|
+
}
|
|
7559
|
+
} catch {
|
|
7560
|
+
throw new Error("Invalid Codex hooks.json; repair it before installing or removing hooks.");
|
|
7561
|
+
}
|
|
7562
|
+
snapshots.set(settings, { path, raw });
|
|
7563
|
+
return settings;
|
|
7564
|
+
}
|
|
7565
|
+
function writeCodexSettings(path, settings) {
|
|
7566
|
+
const snapshot = snapshots.get(settings);
|
|
7567
|
+
if (!snapshot || snapshot.path !== path)
|
|
7568
|
+
throw new Error("Codex settings require a complete current read before writing.");
|
|
7569
|
+
mkdirSync4(dirname3(path), { recursive: true });
|
|
7570
|
+
const lock = `${path}.hooks-write.lock`;
|
|
7571
|
+
let fd;
|
|
7572
|
+
try {
|
|
7573
|
+
fd = openSync(lock, "wx", 384);
|
|
7574
|
+
} catch {
|
|
7575
|
+
throw new Error("Codex settings are being edited; retry after the active writer finishes.");
|
|
7576
|
+
}
|
|
7577
|
+
const temp = `${path}.${randomUUID()}.tmp`;
|
|
7578
|
+
try {
|
|
7579
|
+
if (current(path) !== snapshot.raw)
|
|
7580
|
+
throw new Error("Codex settings changed since reading; re-read before retrying.");
|
|
7581
|
+
const next = JSON.stringify(settings, null, 2) + `
|
|
7582
|
+
`;
|
|
7583
|
+
writeFileSync3(temp, next, { flag: "wx", mode: 384 });
|
|
7584
|
+
if (current(path) !== snapshot.raw)
|
|
7585
|
+
throw new Error("Codex settings changed since reading; re-read before retrying.");
|
|
7586
|
+
renameSync2(temp, path);
|
|
7587
|
+
snapshots.set(settings, { path, raw: next });
|
|
7588
|
+
} finally {
|
|
7589
|
+
if (existsSync7(temp))
|
|
7590
|
+
unlinkSync(temp);
|
|
7591
|
+
closeSync(fd);
|
|
7592
|
+
unlinkSync(lock);
|
|
7593
|
+
}
|
|
7594
|
+
}
|
|
7595
|
+
|
|
7596
|
+
// src/lib/installer.ts
|
|
7597
|
+
var __dirname2 = dirname4(fileURLToPath2(import.meta.url));
|
|
7598
|
+
var HOOKS_DIR = existsSync8(join7(__dirname2, "..", "..", "hooks", "hook-gitguard")) ? join7(__dirname2, "..", "..", "hooks") : join7(__dirname2, "..", "hooks");
|
|
7446
7599
|
function normalizeHookName(name) {
|
|
7447
7600
|
return name.startsWith("hook-") ? name : `hook-${name}`;
|
|
7448
7601
|
}
|
|
@@ -7450,10 +7603,15 @@ function shortHookName(name) {
|
|
|
7450
7603
|
return name.startsWith("hook-") ? name.slice("hook-".length) : name;
|
|
7451
7604
|
}
|
|
7452
7605
|
function removeHookEntriesByName(entries, hookName) {
|
|
7453
|
-
return entries.
|
|
7454
|
-
|
|
7455
|
-
|
|
7456
|
-
|
|
7606
|
+
return entries.flatMap((entry) => {
|
|
7607
|
+
if (!Array.isArray(entry.hooks))
|
|
7608
|
+
return [entry];
|
|
7609
|
+
const kept = entry.hooks.filter((h) => {
|
|
7610
|
+
const match = h.command?.match(/^hooks run ([\w-]+)/);
|
|
7611
|
+
return !(match && match[1] === hookName);
|
|
7612
|
+
});
|
|
7613
|
+
return kept.length === entry.hooks.length ? [entry] : kept.length ? [{ ...entry, hooks: kept }] : [];
|
|
7614
|
+
});
|
|
7457
7615
|
}
|
|
7458
7616
|
var EVENT_MAP = {
|
|
7459
7617
|
claude: {
|
|
@@ -7463,7 +7621,7 @@ var EVENT_MAP = {
|
|
|
7463
7621
|
Notification: "Notification",
|
|
7464
7622
|
SessionStart: "SessionStart",
|
|
7465
7623
|
SessionEnd: "SessionEnd",
|
|
7466
|
-
UserPromptSubmit:
|
|
7624
|
+
UserPromptSubmit: "UserPromptSubmit",
|
|
7467
7625
|
SubagentStart: null
|
|
7468
7626
|
},
|
|
7469
7627
|
gemini: {
|
|
@@ -7485,9 +7643,21 @@ var EVENT_MAP = {
|
|
|
7485
7643
|
SessionEnd: null,
|
|
7486
7644
|
UserPromptSubmit: "UserPromptSubmit",
|
|
7487
7645
|
SubagentStart: "SubagentStart"
|
|
7646
|
+
},
|
|
7647
|
+
codex: {
|
|
7648
|
+
PreToolUse: "PreToolUse",
|
|
7649
|
+
PostToolUse: "PostToolUse",
|
|
7650
|
+
Stop: "Stop",
|
|
7651
|
+
Notification: null,
|
|
7652
|
+
SessionStart: "SessionStart",
|
|
7653
|
+
SessionEnd: "SessionEnd",
|
|
7654
|
+
UserPromptSubmit: "UserPromptSubmit",
|
|
7655
|
+
SubagentStart: "SubagentStart"
|
|
7488
7656
|
}
|
|
7489
7657
|
};
|
|
7490
7658
|
function getTargetSettingsDir(target) {
|
|
7659
|
+
if (target === "codex")
|
|
7660
|
+
return ".codex";
|
|
7491
7661
|
if (target === "codewith")
|
|
7492
7662
|
return ".codewith";
|
|
7493
7663
|
if (target === "gemini")
|
|
@@ -7495,6 +7665,8 @@ function getTargetSettingsDir(target) {
|
|
|
7495
7665
|
return ".claude";
|
|
7496
7666
|
}
|
|
7497
7667
|
function getGlobalSettingsPathOverride(target) {
|
|
7668
|
+
if (target === "codex")
|
|
7669
|
+
return process.env.HASNA_HOOKS_CODEX_SETTINGS_PATH;
|
|
7498
7670
|
if (target === "claude")
|
|
7499
7671
|
return process.env.HASNA_HOOKS_CLAUDE_SETTINGS_PATH;
|
|
7500
7672
|
if (target === "gemini")
|
|
@@ -7511,6 +7683,8 @@ function getSettingsPath(scope = "global", target = "claude", codewithConfigPath
|
|
|
7511
7683
|
if (globalOverride)
|
|
7512
7684
|
return globalOverride;
|
|
7513
7685
|
const dir = getTargetSettingsDir(target);
|
|
7686
|
+
if (target === "codex")
|
|
7687
|
+
return join7(scope === "project" ? process.cwd() : homedir3(), dir, "hooks.json");
|
|
7514
7688
|
if (scope === "project") {
|
|
7515
7689
|
return target === "codewith" ? join7(process.cwd(), dir, "config.toml") : join7(process.cwd(), dir, "settings.json");
|
|
7516
7690
|
}
|
|
@@ -7524,13 +7698,15 @@ function getHookPath(name) {
|
|
|
7524
7698
|
return join7(HOOKS_DIR, normalizeHookName(shortName));
|
|
7525
7699
|
}
|
|
7526
7700
|
function hookExists(name) {
|
|
7527
|
-
return
|
|
7701
|
+
return existsSync8(getHookPath(name));
|
|
7528
7702
|
}
|
|
7529
7703
|
function readSettings(scope = "global", target = "claude") {
|
|
7530
7704
|
const path = getSettingsPath(scope, target);
|
|
7705
|
+
if (target === "codex")
|
|
7706
|
+
return readCodexSettings(path);
|
|
7531
7707
|
try {
|
|
7532
|
-
if (
|
|
7533
|
-
return JSON.parse(
|
|
7708
|
+
if (existsSync8(path)) {
|
|
7709
|
+
return JSON.parse(readFileSync5(path, "utf-8"));
|
|
7534
7710
|
}
|
|
7535
7711
|
} catch (error) {
|
|
7536
7712
|
console.warn(`[hooks] Failed to read settings at ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -7539,11 +7715,13 @@ function readSettings(scope = "global", target = "claude") {
|
|
|
7539
7715
|
}
|
|
7540
7716
|
function writeSettings(settings, scope = "global", target = "claude") {
|
|
7541
7717
|
const path = getSettingsPath(scope, target);
|
|
7542
|
-
|
|
7543
|
-
|
|
7544
|
-
|
|
7718
|
+
if (target === "codex")
|
|
7719
|
+
return writeCodexSettings(path, settings);
|
|
7720
|
+
const dir = dirname4(path);
|
|
7721
|
+
if (!existsSync8(dir)) {
|
|
7722
|
+
mkdirSync5(dir, { recursive: true });
|
|
7545
7723
|
}
|
|
7546
|
-
|
|
7724
|
+
writeFileSync4(path, JSON.stringify(settings, null, 2) + `
|
|
7547
7725
|
`);
|
|
7548
7726
|
}
|
|
7549
7727
|
function getTargetEventName(internalEvent, target) {
|
|
@@ -7566,6 +7744,8 @@ function codewithMatcher(matcher) {
|
|
|
7566
7744
|
}
|
|
7567
7745
|
function codewithTimeout(name) {
|
|
7568
7746
|
switch (name) {
|
|
7747
|
+
case "mementos-context":
|
|
7748
|
+
return 12;
|
|
7569
7749
|
case "knowledge-context":
|
|
7570
7750
|
return 6;
|
|
7571
7751
|
case "session-start":
|
|
@@ -7602,12 +7782,13 @@ function codewithStatusMessage(name) {
|
|
|
7602
7782
|
return `Running ${name}`;
|
|
7603
7783
|
}
|
|
7604
7784
|
}
|
|
7605
|
-
function buildCodewithTomlFragment(name, profile) {
|
|
7785
|
+
function buildCodewithTomlFragment(name, profile, mementos) {
|
|
7606
7786
|
const shortName = shortHookName(name);
|
|
7607
7787
|
const meta = resolveHookMeta(shortName);
|
|
7608
7788
|
if (!meta)
|
|
7609
7789
|
throw new Error(`Hook '${shortName}' not found`);
|
|
7610
|
-
|
|
7790
|
+
validateMementosRegistration(shortName, mementos);
|
|
7791
|
+
const command = (profile ? `hooks run ${shortName} --profile ${profile}` : `hooks run ${shortName}`) + mementosCommandSuffix(mementos);
|
|
7611
7792
|
const matcher = codewithMatcher(meta.matcher);
|
|
7612
7793
|
const fragments = [];
|
|
7613
7794
|
for (const event of getHookEvents(meta)) {
|
|
@@ -7632,21 +7813,21 @@ function buildCodewithTomlFragment(name, profile) {
|
|
|
7632
7813
|
function readCodewithConfig(scope, configPath) {
|
|
7633
7814
|
const path = getSettingsPath(scope, "codewith", configPath);
|
|
7634
7815
|
try {
|
|
7635
|
-
return
|
|
7816
|
+
return existsSync8(path) ? readFileSync5(path, "utf-8") : "";
|
|
7636
7817
|
} catch {
|
|
7637
7818
|
return "";
|
|
7638
7819
|
}
|
|
7639
7820
|
}
|
|
7640
7821
|
function appendCodewithFragment(fragment, scope, configPath) {
|
|
7641
7822
|
const path = getSettingsPath(scope, "codewith", configPath);
|
|
7642
|
-
const dir =
|
|
7643
|
-
if (!
|
|
7644
|
-
|
|
7645
|
-
const existing =
|
|
7823
|
+
const dir = dirname4(path);
|
|
7824
|
+
if (!existsSync8(dir))
|
|
7825
|
+
mkdirSync5(dir, { recursive: true });
|
|
7826
|
+
const existing = existsSync8(path) ? readFileSync5(path, "utf-8") : "";
|
|
7646
7827
|
const sep2 = existing.trim() ? `
|
|
7647
7828
|
|
|
7648
7829
|
` : "";
|
|
7649
|
-
|
|
7830
|
+
writeFileSync4(path, `${existing.replace(/\s*$/, "")}${sep2}${fragment}`);
|
|
7650
7831
|
return path;
|
|
7651
7832
|
}
|
|
7652
7833
|
function detectConflict(name, scope, target) {
|
|
@@ -7689,14 +7870,14 @@ function detectRewriteConflict(name, scope, target, lookup = getHook) {
|
|
|
7689
7870
|
}
|
|
7690
7871
|
return;
|
|
7691
7872
|
}
|
|
7692
|
-
function installForTarget(name, scope, overwrite, target, profile, codewithMode = "fragment", codewithConfigPath) {
|
|
7873
|
+
function installForTarget(name, scope, overwrite, target, profile, codewithMode = "fragment", codewithConfigPath, mementos) {
|
|
7693
7874
|
const shortName = shortHookName(name);
|
|
7694
7875
|
if (!hookExists(shortName)) {
|
|
7695
7876
|
return { hook: shortName, success: false, error: `Hook '${shortName}' not found`, target };
|
|
7696
7877
|
}
|
|
7697
7878
|
if (target === "codewith") {
|
|
7698
7879
|
try {
|
|
7699
|
-
const fragment = buildCodewithTomlFragment(shortName, profile);
|
|
7880
|
+
const fragment = buildCodewithTomlFragment(shortName, profile, mementos);
|
|
7700
7881
|
if (codewithMode !== "write") {
|
|
7701
7882
|
return {
|
|
7702
7883
|
hook: shortName,
|
|
@@ -7743,18 +7924,25 @@ function installForTarget(name, scope, overwrite, target, profile, codewithMode
|
|
|
7743
7924
|
};
|
|
7744
7925
|
}
|
|
7745
7926
|
}
|
|
7746
|
-
const registered = getRegisteredHooksForTarget(scope, target);
|
|
7747
|
-
if (registered.includes(shortName) && !overwrite) {
|
|
7748
|
-
return { hook: shortName, success: false, error: "Already installed. Use --overwrite to replace.", scope, target };
|
|
7749
|
-
}
|
|
7750
|
-
const conflict = detectConflict(shortName, scope, target);
|
|
7751
|
-
const rewriteConflict = detectRewriteConflict(shortName, scope, target);
|
|
7752
|
-
if (rewriteConflict) {
|
|
7753
|
-
return { hook: shortName, success: false, error: `Refused: ${rewriteConflict}`, scope, target };
|
|
7754
|
-
}
|
|
7755
7927
|
try {
|
|
7756
|
-
|
|
7757
|
-
|
|
7928
|
+
const registered = getRegisteredHooksForTarget(scope, target);
|
|
7929
|
+
if (registered.includes(shortName) && !overwrite) {
|
|
7930
|
+
return { hook: shortName, success: false, error: "Already installed. Use --overwrite to replace.", scope, target };
|
|
7931
|
+
}
|
|
7932
|
+
const conflict = detectConflict(shortName, scope, target);
|
|
7933
|
+
const rewriteConflict = detectRewriteConflict(shortName, scope, target);
|
|
7934
|
+
if (rewriteConflict) {
|
|
7935
|
+
return { hook: shortName, success: false, error: `Refused: ${rewriteConflict}`, scope, target };
|
|
7936
|
+
}
|
|
7937
|
+
registerHook(shortName, scope, target, profile, mementos);
|
|
7938
|
+
return {
|
|
7939
|
+
hook: shortName,
|
|
7940
|
+
success: true,
|
|
7941
|
+
scope,
|
|
7942
|
+
target,
|
|
7943
|
+
...conflict ? { conflict } : {},
|
|
7944
|
+
...target === "codex" ? { applied: true, configPath: getSettingsPath(scope, target), note: "Registered in Codex hooks.json. Review and trust the exact definition in Codex /hooks; project hooks also require a trusted project. Registration alone does not prove execution." } : {}
|
|
7945
|
+
};
|
|
7758
7946
|
} catch (error) {
|
|
7759
7947
|
return {
|
|
7760
7948
|
hook: shortName,
|
|
@@ -7765,7 +7953,15 @@ function installForTarget(name, scope, overwrite, target, profile, codewithMode
|
|
|
7765
7953
|
}
|
|
7766
7954
|
}
|
|
7767
7955
|
function installHook(name, options = {}) {
|
|
7768
|
-
const { scope = "global", overwrite = false, target = "claude", profile, codewithMode = "fragment", codewithConfigPath } = options;
|
|
7956
|
+
const { scope = "global", overwrite = false, target = "claude", profile, codewithMode = "fragment", codewithConfigPath, mementos } = options;
|
|
7957
|
+
try {
|
|
7958
|
+
validateMementosRegistration(name, mementos);
|
|
7959
|
+
} catch (error) {
|
|
7960
|
+
return { hook: name, success: false, target, error: error instanceof Error ? error.message : "Invalid Mementos options" };
|
|
7961
|
+
}
|
|
7962
|
+
if (!/^[\w-]+$/.test(name) || profile !== undefined && !/^[\w-]+$/.test(profile)) {
|
|
7963
|
+
return { hook: name, success: false, error: "Hook and profile names must contain only letters, digits, underscores or hyphens.", target };
|
|
7964
|
+
}
|
|
7769
7965
|
if (target === "all") {
|
|
7770
7966
|
const shortName = shortHookName(name);
|
|
7771
7967
|
const meta = resolveHookMeta(shortName);
|
|
@@ -7780,7 +7976,7 @@ function installHook(name, options = {}) {
|
|
|
7780
7976
|
};
|
|
7781
7977
|
}
|
|
7782
7978
|
}
|
|
7783
|
-
const claudeResult = installForTarget(name, scope, overwrite, "claude", profile);
|
|
7979
|
+
const claudeResult = installForTarget(name, scope, overwrite, "claude", profile, "fragment", undefined, mementos);
|
|
7784
7980
|
if (!claudeResult.success) {
|
|
7785
7981
|
return {
|
|
7786
7982
|
...claudeResult,
|
|
@@ -7788,7 +7984,7 @@ function installHook(name, options = {}) {
|
|
|
7788
7984
|
target: "all"
|
|
7789
7985
|
};
|
|
7790
7986
|
}
|
|
7791
|
-
const codewithResult = installForTarget(name, scope, overwrite, "codewith", profile, codewithMode, codewithConfigPath);
|
|
7987
|
+
const codewithResult = installForTarget(name, scope, overwrite, "codewith", profile, codewithMode, codewithConfigPath, mementos);
|
|
7792
7988
|
if (!codewithResult.success) {
|
|
7793
7989
|
return {
|
|
7794
7990
|
...codewithResult,
|
|
@@ -7798,9 +7994,9 @@ function installHook(name, options = {}) {
|
|
|
7798
7994
|
}
|
|
7799
7995
|
return { ...claudeResult, target: "all" };
|
|
7800
7996
|
}
|
|
7801
|
-
return installForTarget(name, scope, overwrite, target, profile, codewithMode, codewithConfigPath);
|
|
7997
|
+
return installForTarget(name, scope, overwrite, target, profile, codewithMode, codewithConfigPath, mementos);
|
|
7802
7998
|
}
|
|
7803
|
-
function registerHook(name, scope = "global", target = "claude", profile) {
|
|
7999
|
+
function registerHook(name, scope = "global", target = "claude", profile, mementos) {
|
|
7804
8000
|
const meta = resolveHookMeta(name);
|
|
7805
8001
|
if (!meta)
|
|
7806
8002
|
return;
|
|
@@ -7818,8 +8014,9 @@ function registerHook(name, scope = "global", target = "claude", profile) {
|
|
|
7818
8014
|
const settings = readSettings(scope, target);
|
|
7819
8015
|
if (!settings.hooks)
|
|
7820
8016
|
settings.hooks = {};
|
|
8017
|
+
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;
|
|
7821
8018
|
removeHookFromAllEvents(settings, name);
|
|
7822
|
-
const hookCommand = profile ? `hooks run ${name} --profile ${profile}` : `hooks run ${name}
|
|
8019
|
+
const hookCommand = (profile ? `hooks run ${name} --profile ${profile}` : `hooks run ${name}`) + mementosCommandSuffix(mementos ?? retained);
|
|
7823
8020
|
for (const eventKey of uniqueEventKeys) {
|
|
7824
8021
|
if (!settings.hooks[eventKey])
|
|
7825
8022
|
settings.hooks[eventKey] = [];
|
|
@@ -7864,10 +8061,12 @@ function getRegisteredHooksForTarget(scope = "global", target = "claude") {
|
|
|
7864
8061
|
if (target === "codewith") {
|
|
7865
8062
|
const config = readCodewithConfig(scope);
|
|
7866
8063
|
const registered2 = [];
|
|
7867
|
-
const re =
|
|
8064
|
+
const re = /^\s*command\s*=\s*["']([^"'\n]+)["']\s*$/gm;
|
|
7868
8065
|
let match;
|
|
7869
8066
|
while (match = re.exec(config)) {
|
|
7870
|
-
|
|
8067
|
+
const name = registrationName(match[1]);
|
|
8068
|
+
if (name)
|
|
8069
|
+
registered2.push(name);
|
|
7871
8070
|
}
|
|
7872
8071
|
return [...new Set(registered2)];
|
|
7873
8072
|
}
|
|
@@ -7878,12 +8077,9 @@ function getRegisteredHooksForTarget(scope = "global", target = "claude") {
|
|
|
7878
8077
|
for (const eventKey of Object.keys(settings.hooks)) {
|
|
7879
8078
|
for (const entry of settings.hooks[eventKey]) {
|
|
7880
8079
|
for (const hook of entry.hooks || []) {
|
|
7881
|
-
const
|
|
7882
|
-
|
|
7883
|
-
|
|
7884
|
-
if (match) {
|
|
7885
|
-
registered.push(match[1]);
|
|
7886
|
-
}
|
|
8080
|
+
const name = registrationName(hook.command);
|
|
8081
|
+
if (name)
|
|
8082
|
+
registered.push(name);
|
|
7887
8083
|
}
|
|
7888
8084
|
}
|
|
7889
8085
|
}
|
|
@@ -7898,7 +8094,8 @@ function removeHook(name, scope = "global", target = "claude") {
|
|
|
7898
8094
|
if (target === "all") {
|
|
7899
8095
|
const claudeRemoved = removeHookForTarget(shortName, scope, "claude");
|
|
7900
8096
|
const geminiRemoved = removeHookForTarget(shortName, scope, "gemini");
|
|
7901
|
-
|
|
8097
|
+
const codexRemoved = removeHookForTarget(shortName, scope, "codex");
|
|
8098
|
+
return claudeRemoved || geminiRemoved || codexRemoved;
|
|
7902
8099
|
}
|
|
7903
8100
|
if (target === "codewith") {
|
|
7904
8101
|
return false;
|
|
@@ -7913,9 +8110,10 @@ function removeHookForTarget(name, scope, target) {
|
|
|
7913
8110
|
unregisterHook(name, scope, target);
|
|
7914
8111
|
return true;
|
|
7915
8112
|
}
|
|
8113
|
+
var JSON_SETTINGS_TARGETS = ["claude", "gemini", "codex"];
|
|
7916
8114
|
function removeCodewithHookEntry(configText, name) {
|
|
7917
8115
|
const sections = configText.split(/\n\s*\n/);
|
|
7918
|
-
const
|
|
8116
|
+
const commandRe = /^command\s*=\s*"([^"\n]+)"$/;
|
|
7919
8117
|
const entryHeaderRe = /^\[\[hooks\.([\w]+)\.hooks\]\]$/;
|
|
7920
8118
|
const eventHeaderRe = /^\[\[hooks\.([\w]+)\]\]$/;
|
|
7921
8119
|
const eventsWithEntries = new Set;
|
|
@@ -7928,7 +8126,7 @@ function removeCodewithHookEntry(configText, name) {
|
|
|
7928
8126
|
if (entryMatch) {
|
|
7929
8127
|
const event = entryMatch[1];
|
|
7930
8128
|
eventsWithEntries.add(event);
|
|
7931
|
-
if (lines.some((line) =>
|
|
8129
|
+
if (lines.some((line) => registrationName(commandRe.exec(line.trim())?.[1]) === name)) {
|
|
7932
8130
|
removed = true;
|
|
7933
8131
|
continue;
|
|
7934
8132
|
}
|
|
@@ -7969,10 +8167,10 @@ function codewithHasHookEntryInText(configText, name) {
|
|
|
7969
8167
|
}
|
|
7970
8168
|
function codewithHasHookEntry(name, scope = "global") {
|
|
7971
8169
|
const path = getSettingsPath(scope, "codewith");
|
|
7972
|
-
if (!
|
|
8170
|
+
if (!existsSync8(path))
|
|
7973
8171
|
return false;
|
|
7974
8172
|
try {
|
|
7975
|
-
return codewithHasHookEntryInText(
|
|
8173
|
+
return codewithHasHookEntryInText(readFileSync5(path, "utf-8"), name);
|
|
7976
8174
|
} catch {
|
|
7977
8175
|
return false;
|
|
7978
8176
|
}
|
|
@@ -7986,7 +8184,7 @@ function removeStoreDirFailClosed(dir) {
|
|
|
7986
8184
|
}
|
|
7987
8185
|
for (const entry of entries) {
|
|
7988
8186
|
if (!entry.isDirectory()) {
|
|
7989
|
-
|
|
8187
|
+
unlinkSync2(join7(dir, entry.name));
|
|
7990
8188
|
}
|
|
7991
8189
|
}
|
|
7992
8190
|
rmdirSync(dir);
|
|
@@ -7996,14 +8194,14 @@ function uninstallHook(name, scope = "global", target = "claude") {
|
|
|
7996
8194
|
const custom2 = readCustomManifest(shortName);
|
|
7997
8195
|
const bundledMeta = custom2 ? undefined : getHook(shortName);
|
|
7998
8196
|
const existsInTarget = (t) => getRegisteredHooksForTarget("global", t).includes(shortName) || getRegisteredHooksForTarget("project", t).includes(shortName);
|
|
7999
|
-
const registeredClaudeOrGemini = target === "all" ?
|
|
8197
|
+
const registeredClaudeOrGemini = target === "all" ? JSON_SETTINGS_TARGETS.some(existsInTarget) : false;
|
|
8000
8198
|
const registeredGlobal = registeredClaudeOrGemini || getRegisteredHooksForTarget("global", target === "all" ? "claude" : target).includes(shortName);
|
|
8001
8199
|
const registeredProject = registeredClaudeOrGemini || getRegisteredHooksForTarget("project", target === "all" ? "claude" : target).includes(shortName);
|
|
8002
8200
|
const codewithOnly = (target === "codewith" || target === "all") && codewithHasHookEntry(shortName, scope);
|
|
8003
8201
|
if (!custom2 && !bundledMeta && !registeredGlobal && !registeredProject && !codewithOnly) {
|
|
8004
8202
|
return { name: shortName, removed: false, source: null, settingsScopes: [], storeDirRemoved: false, pinRemoved: false, dbRecordRemoved: false, registrationsRemaining: [], error: `Hook '${shortName}' not found` };
|
|
8005
8203
|
}
|
|
8006
|
-
const writableTargets = target === "all" ?
|
|
8204
|
+
const writableTargets = target === "all" ? JSON_SETTINGS_TARGETS : target === "codewith" ? [] : [target];
|
|
8007
8205
|
const scopes = [];
|
|
8008
8206
|
for (const s of ["global", "project"]) {
|
|
8009
8207
|
for (const t of writableTargets) {
|
|
@@ -8017,11 +8215,11 @@ function uninstallHook(name, scope = "global", target = "claude") {
|
|
|
8017
8215
|
const registrationsRemaining = [];
|
|
8018
8216
|
if (target === "codewith" || target === "all") {
|
|
8019
8217
|
const codewithPath = getSettingsPath(scope, "codewith");
|
|
8020
|
-
if (
|
|
8021
|
-
const before =
|
|
8218
|
+
if (existsSync8(codewithPath)) {
|
|
8219
|
+
const before = readFileSync5(codewithPath, "utf-8");
|
|
8022
8220
|
const after = removeCodewithHookEntry(before, shortName);
|
|
8023
8221
|
if (after.removed) {
|
|
8024
|
-
|
|
8222
|
+
writeFileSync4(codewithPath, after.text, "utf-8");
|
|
8025
8223
|
}
|
|
8026
8224
|
if (codewithHasHookEntryInText(after.removed ? after.text : before, shortName)) {
|
|
8027
8225
|
registrationsRemaining.push("codewith");
|
|
@@ -8060,7 +8258,7 @@ function uninstallHook(name, scope = "global", target = "claude") {
|
|
|
8060
8258
|
// src/index.ts
|
|
8061
8259
|
init_profiles();
|
|
8062
8260
|
init_config();
|
|
8063
|
-
import { existsSync as
|
|
8261
|
+
import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
|
|
8064
8262
|
|
|
8065
8263
|
// src/lib/transport.ts
|
|
8066
8264
|
init_transport();
|
|
@@ -8147,15 +8345,15 @@ init_manifest();
|
|
|
8147
8345
|
// src/lib/custom-install.ts
|
|
8148
8346
|
init_config();
|
|
8149
8347
|
init_manifest();
|
|
8150
|
-
import { existsSync as
|
|
8348
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync7, mkdtempSync, readFileSync as readFileSync8, rmSync as rmSync2 } from "fs";
|
|
8151
8349
|
import { join as join10 } from "path";
|
|
8152
8350
|
import { tmpdir } from "os";
|
|
8153
8351
|
function manifestAt(dir) {
|
|
8154
8352
|
const path = join10(dir, "manifest.json");
|
|
8155
|
-
if (!
|
|
8353
|
+
if (!existsSync10(path))
|
|
8156
8354
|
return;
|
|
8157
8355
|
try {
|
|
8158
|
-
return parseManifest(
|
|
8356
|
+
return parseManifest(readFileSync8(path, "utf-8"));
|
|
8159
8357
|
} catch {
|
|
8160
8358
|
return;
|
|
8161
8359
|
}
|
|
@@ -8198,7 +8396,7 @@ function isCustomSource(value) {
|
|
|
8198
8396
|
return true;
|
|
8199
8397
|
if (value.startsWith("http://") || value.startsWith("https://"))
|
|
8200
8398
|
return true;
|
|
8201
|
-
return
|
|
8399
|
+
return existsSync10(value);
|
|
8202
8400
|
}
|
|
8203
8401
|
async function installCustomSource(source) {
|
|
8204
8402
|
if (isGitUrl(source)) {
|
|
@@ -8212,11 +8410,11 @@ async function installCustomSource(source) {
|
|
|
8212
8410
|
if (source.startsWith("http://") || source.startsWith("https://")) {
|
|
8213
8411
|
return installManifestUrl(source);
|
|
8214
8412
|
}
|
|
8215
|
-
if (!
|
|
8413
|
+
if (!existsSync10(source)) {
|
|
8216
8414
|
throw new Error(`No such local path: ${source}`);
|
|
8217
8415
|
}
|
|
8218
8416
|
if (source.endsWith("manifest.json")) {
|
|
8219
|
-
const manifest = parseManifest(
|
|
8417
|
+
const manifest = parseManifest(readFileSync8(source, "utf-8"));
|
|
8220
8418
|
return writeFromManifest(manifest, source.substring(0, source.lastIndexOf("/")), source, "local");
|
|
8221
8419
|
}
|
|
8222
8420
|
return copyManifestDir(source, source, "local");
|
|
@@ -8247,7 +8445,7 @@ async function installManifestUrl(url) {
|
|
|
8247
8445
|
}
|
|
8248
8446
|
// src/lib/sync.ts
|
|
8249
8447
|
import { createHash as createHash2 } from "crypto";
|
|
8250
|
-
import { readFileSync as
|
|
8448
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
8251
8449
|
init_db();
|
|
8252
8450
|
init_store();
|
|
8253
8451
|
init_manifest();
|
|
@@ -8255,7 +8453,7 @@ function sha256OfText(text) {
|
|
|
8255
8453
|
return createHash2("sha256").update(text).digest("hex");
|
|
8256
8454
|
}
|
|
8257
8455
|
function sha256OfPath(path) {
|
|
8258
|
-
return createHash2("sha256").update(
|
|
8456
|
+
return createHash2("sha256").update(readFileSync9(path)).digest("hex");
|
|
8259
8457
|
}
|
|
8260
8458
|
function collectBundledCatalog() {
|
|
8261
8459
|
const out = [];
|
|
@@ -8549,8 +8747,8 @@ async function installPinnedFromBundled(name, version) {
|
|
|
8549
8747
|
// src/serve.ts
|
|
8550
8748
|
init_manifest();
|
|
8551
8749
|
init_store();
|
|
8552
|
-
import { readFileSync as
|
|
8553
|
-
import { dirname as
|
|
8750
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
8751
|
+
import { dirname as dirname5, join as join11 } from "path";
|
|
8554
8752
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
8555
8753
|
|
|
8556
8754
|
// src/lib/secure-compare.ts
|
|
@@ -8724,8 +8922,8 @@ var SERVE_HOST = "127.0.0.1";
|
|
|
8724
8922
|
var SERVE_SERVICE_NAME = "hooks-registry";
|
|
8725
8923
|
function packageVersion() {
|
|
8726
8924
|
try {
|
|
8727
|
-
const pkgPath = join11(
|
|
8728
|
-
return JSON.parse(
|
|
8925
|
+
const pkgPath = join11(dirname5(fileURLToPath3(import.meta.url)), "..", "package.json");
|
|
8926
|
+
return JSON.parse(readFileSync10(pkgPath, "utf-8")).version || "0.0.0";
|
|
8729
8927
|
} catch {
|
|
8730
8928
|
return "0.0.0";
|
|
8731
8929
|
}
|
|
@@ -8991,11 +9189,11 @@ async function runHook(name, input, options = {}) {
|
|
|
8991
9189
|
throw new Error(`Hook '${name}' not found`);
|
|
8992
9190
|
const hookScript = getHookPath(name) + "/src/hook.ts";
|
|
8993
9191
|
const resolvedScript = custom2?.scriptPath;
|
|
8994
|
-
const script = resolvedScript ?? (
|
|
9192
|
+
const script = resolvedScript ?? (existsSync12(hookScript) ? hookScript : undefined);
|
|
8995
9193
|
if (!script)
|
|
8996
9194
|
throw new Error(`Hook script not found: ${name}`);
|
|
8997
9195
|
const { sha256Of: sha256Of2, checkScriptHash: checkScriptHash2 } = await Promise.resolve().then(() => (init_store(), exports_store));
|
|
8998
|
-
const content =
|
|
9196
|
+
const content = readFileSync11(script);
|
|
8999
9197
|
const check = checkScriptHash2(name, sha256Of2(content));
|
|
9000
9198
|
if (!check.ok) {
|
|
9001
9199
|
throw new Error(`Hook '${name}' script changed since it was trusted (sha256 ${check.expected} != ${check.actual}). Run 'hooks trust ${name}' to trust the new content.`);
|