@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/bin/hooks-mcp.js
CHANGED
|
@@ -377,6 +377,17 @@ var init_registry = __esm(() => {
|
|
|
377
377
|
matcher: "",
|
|
378
378
|
tags: ["knowledge", "context", "codewith", "deterministic", "session-start", "prompt", "subagent"]
|
|
379
379
|
},
|
|
380
|
+
{
|
|
381
|
+
name: "mementos-context",
|
|
382
|
+
displayName: "Mementos Prompt Context",
|
|
383
|
+
description: "Retrieves scoped memories for a user prompt, with optional relevance judgments",
|
|
384
|
+
version: "0.1.0",
|
|
385
|
+
category: "Context Management",
|
|
386
|
+
event: "UserPromptSubmit",
|
|
387
|
+
matcher: "",
|
|
388
|
+
timeoutSeconds: 12,
|
|
389
|
+
tags: ["mementos", "memory", "prompt", "codex", "claude", "codewith", "opt-in"]
|
|
390
|
+
},
|
|
380
391
|
{
|
|
381
392
|
name: "autoformat",
|
|
382
393
|
displayName: "Auto Format",
|
|
@@ -1410,7 +1421,7 @@ var init_redact = __esm(() => {
|
|
|
1410
1421
|
// ../../node_modules/.bun/@hasna+contracts@1.0.2+e0db24ae03127387/node_modules/@hasna/contracts/dist/client/transport.js
|
|
1411
1422
|
import { isIP } from "net";
|
|
1412
1423
|
import { spawnSync } from "child_process";
|
|
1413
|
-
import { closeSync, fstatSync, openSync, readFileSync as
|
|
1424
|
+
import { closeSync as closeSync2, fstatSync, openSync as openSync2, readFileSync as readFileSync7 } from "fs";
|
|
1414
1425
|
import { O_NOFOLLOW, O_NONBLOCK, O_RDONLY } from "constants";
|
|
1415
1426
|
import { createRequire } from "module";
|
|
1416
1427
|
import { hostname as osHostname } from "os";
|
|
@@ -1514,7 +1525,7 @@ function readAppConfigFile(path) {
|
|
|
1514
1525
|
};
|
|
1515
1526
|
let fd = -1;
|
|
1516
1527
|
try {
|
|
1517
|
-
fd =
|
|
1528
|
+
fd = openSync2(path, O_RDONLY | O_NOFOLLOW | O_NONBLOCK);
|
|
1518
1529
|
} catch (error) {
|
|
1519
1530
|
const code = error.code;
|
|
1520
1531
|
if (code === "ENOENT" || code === "ENOTDIR")
|
|
@@ -1535,7 +1546,7 @@ function readAppConfigFile(path) {
|
|
|
1535
1546
|
unsafe("the file is not owned by the current user");
|
|
1536
1547
|
if (before.size > MAX_CREDENTIAL_FILE_BYTES)
|
|
1537
1548
|
unsafe("the file exceeds the size limit");
|
|
1538
|
-
const bytes =
|
|
1549
|
+
const bytes = readFileSync7(fd);
|
|
1539
1550
|
const after = fstatSync(fd);
|
|
1540
1551
|
if (!configFileReadsCoherent(before, after)) {
|
|
1541
1552
|
unsafe("the file changed while being read");
|
|
@@ -1543,7 +1554,7 @@ function readAppConfigFile(path) {
|
|
|
1543
1554
|
return parseEnvFile(bytes.toString("utf8"));
|
|
1544
1555
|
} finally {
|
|
1545
1556
|
if (fd !== -1)
|
|
1546
|
-
|
|
1557
|
+
closeSync2(fd);
|
|
1547
1558
|
}
|
|
1548
1559
|
}
|
|
1549
1560
|
function readCredentialFile(path, apiKeyKeys) {
|
|
@@ -2198,8 +2209,8 @@ var init_local_opt_in = __esm(() => {
|
|
|
2198
2209
|
});
|
|
2199
2210
|
|
|
2200
2211
|
// src/lib/hook-env.ts
|
|
2201
|
-
import { dirname as
|
|
2202
|
-
import { existsSync as
|
|
2212
|
+
import { dirname as dirname5, isAbsolute as isAbsolute3 } from "path";
|
|
2213
|
+
import { existsSync as existsSync10, realpathSync as realpathSync2, statSync } from "fs";
|
|
2203
2214
|
import { tmpdir } from "os";
|
|
2204
2215
|
function isDeniedEnvName(name) {
|
|
2205
2216
|
const upper = name.toUpperCase();
|
|
@@ -2247,9 +2258,9 @@ function isUnsafePathEntry(entry, home) {
|
|
|
2247
2258
|
}
|
|
2248
2259
|
function sanitizeHookPath(pathValue, home) {
|
|
2249
2260
|
const baseline = PATH_BASELINE.slice();
|
|
2250
|
-
if (process.platform === "darwin" &&
|
|
2261
|
+
if (process.platform === "darwin" && existsSync10("/opt/homebrew/bin"))
|
|
2251
2262
|
baseline.push("/opt/homebrew/bin");
|
|
2252
|
-
const bunDir =
|
|
2263
|
+
const bunDir = dirname5(process.execPath);
|
|
2253
2264
|
const seen = new Set;
|
|
2254
2265
|
const out = [];
|
|
2255
2266
|
for (const dir of baseline) {
|
|
@@ -2373,8 +2384,8 @@ __export(exports_run, {
|
|
|
2373
2384
|
HookTimeoutError: () => HookTimeoutError
|
|
2374
2385
|
});
|
|
2375
2386
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
2376
|
-
import { rmSync as rmSync2, writeFileSync as
|
|
2377
|
-
import { basename as basename2, dirname as
|
|
2387
|
+
import { rmSync as rmSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
2388
|
+
import { basename as basename2, dirname as dirname6, isAbsolute as isAbsolute4, join as join10 } from "path";
|
|
2378
2389
|
function extensionOf(scriptPath) {
|
|
2379
2390
|
const base = basename2(scriptPath);
|
|
2380
2391
|
const dot = base.lastIndexOf(".");
|
|
@@ -2443,7 +2454,7 @@ async function readPipeWithDeadline(stream, deadlineMs) {
|
|
|
2443
2454
|
return chunks.join("");
|
|
2444
2455
|
}
|
|
2445
2456
|
async function executeVerifiedScript(options) {
|
|
2446
|
-
const scriptDir =
|
|
2457
|
+
const scriptDir = dirname6(options.scriptPath);
|
|
2447
2458
|
const interpreter = interpreterFor(options.name, options.scriptPath, options.content);
|
|
2448
2459
|
const tempPath = join10(scriptDir, `.hooks-verified-${randomBytes2(12).toString("hex")}.${interpreter.tempExt}`);
|
|
2449
2460
|
let proc = null;
|
|
@@ -2453,7 +2464,7 @@ async function executeVerifiedScript(options) {
|
|
|
2453
2464
|
killGroup(proc, { leaderAlive: proc !== null && proc.exitCode === null });
|
|
2454
2465
|
}, options.timeout) : null;
|
|
2455
2466
|
try {
|
|
2456
|
-
|
|
2467
|
+
writeFileSync6(tempPath, options.content, { flag: "wx", mode: 384 });
|
|
2457
2468
|
proc = Bun.spawn([...interpreter.command, isAbsolute4(tempPath) ? tempPath : `./${tempPath}`, ...options.args ?? []], {
|
|
2458
2469
|
stdin: new Response(options.stdin),
|
|
2459
2470
|
stdout: "pipe",
|
|
@@ -2653,21 +2664,94 @@ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
|
2653
2664
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
2654
2665
|
import { z as z2 } from "zod";
|
|
2655
2666
|
init_registry();
|
|
2656
|
-
import { existsSync as
|
|
2657
|
-
import { join as join11, dirname as
|
|
2667
|
+
import { existsSync as existsSync11, readFileSync as readFileSync8 } from "fs";
|
|
2668
|
+
import { join as join11, dirname as dirname7 } from "path";
|
|
2658
2669
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
2659
2670
|
|
|
2660
2671
|
// src/lib/installer.ts
|
|
2661
2672
|
init_registry();
|
|
2662
2673
|
init_resolve();
|
|
2663
2674
|
init_manifest();
|
|
2664
|
-
import { existsSync as
|
|
2665
|
-
import { join as join7, dirname as
|
|
2675
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync5, readdirSync as readdirSync3, rmdirSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
2676
|
+
import { join as join7, dirname as dirname4 } from "path";
|
|
2666
2677
|
import { homedir as homedir3 } from "os";
|
|
2667
2678
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2668
2679
|
|
|
2669
2680
|
// src/lib/registration.ts
|
|
2670
2681
|
init_resolve();
|
|
2682
|
+
|
|
2683
|
+
// src/lib/mementos-options.ts
|
|
2684
|
+
function validateMementosRegistration(name, value) {
|
|
2685
|
+
if (value === undefined)
|
|
2686
|
+
return;
|
|
2687
|
+
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") {
|
|
2688
|
+
throw new Error("Mementos options require the mementos-context hook and an explicit enable/disable choice.");
|
|
2689
|
+
}
|
|
2690
|
+
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))) {
|
|
2691
|
+
throw new Error("OpenRouter requires an enabled Mementos hook and a valid Secrets reference, never a key value.");
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
function mementosRegistrationFromFlags(name, flags) {
|
|
2695
|
+
if (!flags.enableMementos && !flags.disableMementos && flags.openrouterSecretRef === undefined)
|
|
2696
|
+
return;
|
|
2697
|
+
if (Boolean(flags.enableMementos) === Boolean(flags.disableMementos)) {
|
|
2698
|
+
throw new Error("Choose exactly one of --enable-mementos or --disable-mementos.");
|
|
2699
|
+
}
|
|
2700
|
+
const value = { enabled: flags.enableMementos === true, ...flags.openrouterSecretRef !== undefined ? { secretRef: flags.openrouterSecretRef } : {} };
|
|
2701
|
+
validateMementosRegistration(name, value);
|
|
2702
|
+
return value;
|
|
2703
|
+
}
|
|
2704
|
+
function mementosCommandSuffix(value) {
|
|
2705
|
+
validateMementosRegistration("mementos-context", value);
|
|
2706
|
+
if (!value)
|
|
2707
|
+
return "";
|
|
2708
|
+
return value.enabled ? ` --enable-mementos${value.secretRef ? ` --openrouter-secret-ref ${value.secretRef}` : ""}` : " --disable-mementos";
|
|
2709
|
+
}
|
|
2710
|
+
function retainedMementosRegistration(commands) {
|
|
2711
|
+
const values = commands.filter((command) => /^hooks run mementos-context(?:\s|$)/.test(command)).map((command) => {
|
|
2712
|
+
const args = command.trim().split(/\s+/).slice(3);
|
|
2713
|
+
const flags = {};
|
|
2714
|
+
const seen = new Set;
|
|
2715
|
+
while (args.length) {
|
|
2716
|
+
const key = args.shift();
|
|
2717
|
+
if (seen.has(key))
|
|
2718
|
+
throw new Error("Ambiguous Mementos registration; specify its enablement explicitly.");
|
|
2719
|
+
seen.add(key);
|
|
2720
|
+
if (key === "--profile" && /^[\w-]+$/.test(args[0] ?? ""))
|
|
2721
|
+
args.shift();
|
|
2722
|
+
else if (key === "--enable-mementos")
|
|
2723
|
+
flags.enableMementos = true;
|
|
2724
|
+
else if (key === "--disable-mementos")
|
|
2725
|
+
flags.disableMementos = true;
|
|
2726
|
+
else if (key === "--openrouter-secret-ref" && args.length)
|
|
2727
|
+
flags.openrouterSecretRef = args.shift();
|
|
2728
|
+
else
|
|
2729
|
+
throw new Error("Unrecognized Mementos registration options; specify its enablement explicitly.");
|
|
2730
|
+
}
|
|
2731
|
+
return mementosRegistrationFromFlags("mementos-context", flags);
|
|
2732
|
+
});
|
|
2733
|
+
if (new Set(values.map((value) => JSON.stringify(value))).size > 1) {
|
|
2734
|
+
throw new Error("Conflicting Mementos registrations; specify its enablement explicitly.");
|
|
2735
|
+
}
|
|
2736
|
+
return values[0];
|
|
2737
|
+
}
|
|
2738
|
+
|
|
2739
|
+
// src/lib/registration.ts
|
|
2740
|
+
var REGISTRATION_COMMAND_RE = /^(?:hooks run ([\w-]+)(?:\s+--profile\s+[\w-]+)?|hook-([\w-]+))$/;
|
|
2741
|
+
function registrationName(command) {
|
|
2742
|
+
if (typeof command !== "string")
|
|
2743
|
+
return;
|
|
2744
|
+
if (/^hooks run mementos-context(?:\s|$)/.test(command.trim())) {
|
|
2745
|
+
try {
|
|
2746
|
+
retainedMementosRegistration([command.trim()]);
|
|
2747
|
+
return "mementos-context";
|
|
2748
|
+
} catch {
|
|
2749
|
+
return;
|
|
2750
|
+
}
|
|
2751
|
+
}
|
|
2752
|
+
const match = command.trim().match(REGISTRATION_COMMAND_RE);
|
|
2753
|
+
return match ? match[1] ?? match[2] : undefined;
|
|
2754
|
+
}
|
|
2671
2755
|
function matchersOverlap(a, b) {
|
|
2672
2756
|
return a === b || a.includes(b) || b.includes(a);
|
|
2673
2757
|
}
|
|
@@ -2861,9 +2945,78 @@ function checkScriptHash(name, actual) {
|
|
|
2861
2945
|
return { ok: true, pinned: true, expected, actual, name };
|
|
2862
2946
|
}
|
|
2863
2947
|
|
|
2948
|
+
// src/lib/codex-settings.ts
|
|
2949
|
+
import { closeSync, existsSync as existsSync7, lstatSync, mkdirSync as mkdirSync4, openSync, readFileSync as readFileSync4, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
|
|
2950
|
+
import { dirname as dirname3 } from "path";
|
|
2951
|
+
import { randomUUID } from "crypto";
|
|
2952
|
+
var snapshots = new WeakMap;
|
|
2953
|
+
function current(path) {
|
|
2954
|
+
try {
|
|
2955
|
+
const stat = lstatSync(path);
|
|
2956
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 1048576)
|
|
2957
|
+
throw new Error("invalid_file");
|
|
2958
|
+
return readFileSync4(path, "utf8");
|
|
2959
|
+
} catch (error) {
|
|
2960
|
+
if (error.code === "ENOENT")
|
|
2961
|
+
return null;
|
|
2962
|
+
throw new Error("Cannot safely read Codex hooks.json; no settings were changed.");
|
|
2963
|
+
}
|
|
2964
|
+
}
|
|
2965
|
+
function readCodexSettings(path) {
|
|
2966
|
+
const raw = current(path);
|
|
2967
|
+
let settings;
|
|
2968
|
+
try {
|
|
2969
|
+
settings = raw === null ? {} : JSON.parse(raw);
|
|
2970
|
+
if (!settings || typeof settings !== "object" || Array.isArray(settings))
|
|
2971
|
+
throw new Error;
|
|
2972
|
+
if (settings.hooks !== undefined) {
|
|
2973
|
+
if (!settings.hooks || typeof settings.hooks !== "object" || Array.isArray(settings.hooks))
|
|
2974
|
+
throw new Error;
|
|
2975
|
+
for (const entries of Object.values(settings.hooks)) {
|
|
2976
|
+
if (!Array.isArray(entries) || entries.some((entry) => !entry || !Array.isArray(entry.hooks) || entry.hooks.some((hook) => !hook || typeof hook !== "object")))
|
|
2977
|
+
throw new Error;
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
} catch {
|
|
2981
|
+
throw new Error("Invalid Codex hooks.json; repair it before installing or removing hooks.");
|
|
2982
|
+
}
|
|
2983
|
+
snapshots.set(settings, { path, raw });
|
|
2984
|
+
return settings;
|
|
2985
|
+
}
|
|
2986
|
+
function writeCodexSettings(path, settings) {
|
|
2987
|
+
const snapshot = snapshots.get(settings);
|
|
2988
|
+
if (!snapshot || snapshot.path !== path)
|
|
2989
|
+
throw new Error("Codex settings require a complete current read before writing.");
|
|
2990
|
+
mkdirSync4(dirname3(path), { recursive: true });
|
|
2991
|
+
const lock = `${path}.hooks-write.lock`;
|
|
2992
|
+
let fd;
|
|
2993
|
+
try {
|
|
2994
|
+
fd = openSync(lock, "wx", 384);
|
|
2995
|
+
} catch {
|
|
2996
|
+
throw new Error("Codex settings are being edited; retry after the active writer finishes.");
|
|
2997
|
+
}
|
|
2998
|
+
const temp = `${path}.${randomUUID()}.tmp`;
|
|
2999
|
+
try {
|
|
3000
|
+
if (current(path) !== snapshot.raw)
|
|
3001
|
+
throw new Error("Codex settings changed since reading; re-read before retrying.");
|
|
3002
|
+
const next = JSON.stringify(settings, null, 2) + `
|
|
3003
|
+
`;
|
|
3004
|
+
writeFileSync3(temp, next, { flag: "wx", mode: 384 });
|
|
3005
|
+
if (current(path) !== snapshot.raw)
|
|
3006
|
+
throw new Error("Codex settings changed since reading; re-read before retrying.");
|
|
3007
|
+
renameSync2(temp, path);
|
|
3008
|
+
snapshots.set(settings, { path, raw: next });
|
|
3009
|
+
} finally {
|
|
3010
|
+
if (existsSync7(temp))
|
|
3011
|
+
unlinkSync(temp);
|
|
3012
|
+
closeSync(fd);
|
|
3013
|
+
unlinkSync(lock);
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
|
|
2864
3017
|
// src/lib/installer.ts
|
|
2865
|
-
var __dirname2 =
|
|
2866
|
-
var HOOKS_DIR =
|
|
3018
|
+
var __dirname2 = dirname4(fileURLToPath2(import.meta.url));
|
|
3019
|
+
var HOOKS_DIR = existsSync8(join7(__dirname2, "..", "..", "hooks", "hook-gitguard")) ? join7(__dirname2, "..", "..", "hooks") : join7(__dirname2, "..", "hooks");
|
|
2867
3020
|
function normalizeHookName(name) {
|
|
2868
3021
|
return name.startsWith("hook-") ? name : `hook-${name}`;
|
|
2869
3022
|
}
|
|
@@ -2871,10 +3024,15 @@ function shortHookName(name) {
|
|
|
2871
3024
|
return name.startsWith("hook-") ? name.slice("hook-".length) : name;
|
|
2872
3025
|
}
|
|
2873
3026
|
function removeHookEntriesByName(entries, hookName) {
|
|
2874
|
-
return entries.
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
3027
|
+
return entries.flatMap((entry) => {
|
|
3028
|
+
if (!Array.isArray(entry.hooks))
|
|
3029
|
+
return [entry];
|
|
3030
|
+
const kept = entry.hooks.filter((h) => {
|
|
3031
|
+
const match = h.command?.match(/^hooks run ([\w-]+)/);
|
|
3032
|
+
return !(match && match[1] === hookName);
|
|
3033
|
+
});
|
|
3034
|
+
return kept.length === entry.hooks.length ? [entry] : kept.length ? [{ ...entry, hooks: kept }] : [];
|
|
3035
|
+
});
|
|
2878
3036
|
}
|
|
2879
3037
|
var EVENT_MAP = {
|
|
2880
3038
|
claude: {
|
|
@@ -2884,7 +3042,7 @@ var EVENT_MAP = {
|
|
|
2884
3042
|
Notification: "Notification",
|
|
2885
3043
|
SessionStart: "SessionStart",
|
|
2886
3044
|
SessionEnd: "SessionEnd",
|
|
2887
|
-
UserPromptSubmit:
|
|
3045
|
+
UserPromptSubmit: "UserPromptSubmit",
|
|
2888
3046
|
SubagentStart: null
|
|
2889
3047
|
},
|
|
2890
3048
|
gemini: {
|
|
@@ -2906,9 +3064,21 @@ var EVENT_MAP = {
|
|
|
2906
3064
|
SessionEnd: null,
|
|
2907
3065
|
UserPromptSubmit: "UserPromptSubmit",
|
|
2908
3066
|
SubagentStart: "SubagentStart"
|
|
3067
|
+
},
|
|
3068
|
+
codex: {
|
|
3069
|
+
PreToolUse: "PreToolUse",
|
|
3070
|
+
PostToolUse: "PostToolUse",
|
|
3071
|
+
Stop: "Stop",
|
|
3072
|
+
Notification: null,
|
|
3073
|
+
SessionStart: "SessionStart",
|
|
3074
|
+
SessionEnd: "SessionEnd",
|
|
3075
|
+
UserPromptSubmit: "UserPromptSubmit",
|
|
3076
|
+
SubagentStart: "SubagentStart"
|
|
2909
3077
|
}
|
|
2910
3078
|
};
|
|
2911
3079
|
function getTargetSettingsDir(target) {
|
|
3080
|
+
if (target === "codex")
|
|
3081
|
+
return ".codex";
|
|
2912
3082
|
if (target === "codewith")
|
|
2913
3083
|
return ".codewith";
|
|
2914
3084
|
if (target === "gemini")
|
|
@@ -2916,6 +3086,8 @@ function getTargetSettingsDir(target) {
|
|
|
2916
3086
|
return ".claude";
|
|
2917
3087
|
}
|
|
2918
3088
|
function getGlobalSettingsPathOverride(target) {
|
|
3089
|
+
if (target === "codex")
|
|
3090
|
+
return process.env.HASNA_HOOKS_CODEX_SETTINGS_PATH;
|
|
2919
3091
|
if (target === "claude")
|
|
2920
3092
|
return process.env.HASNA_HOOKS_CLAUDE_SETTINGS_PATH;
|
|
2921
3093
|
if (target === "gemini")
|
|
@@ -2932,6 +3104,8 @@ function getSettingsPath(scope = "global", target = "claude", codewithConfigPath
|
|
|
2932
3104
|
if (globalOverride)
|
|
2933
3105
|
return globalOverride;
|
|
2934
3106
|
const dir = getTargetSettingsDir(target);
|
|
3107
|
+
if (target === "codex")
|
|
3108
|
+
return join7(scope === "project" ? process.cwd() : homedir3(), dir, "hooks.json");
|
|
2935
3109
|
if (scope === "project") {
|
|
2936
3110
|
return target === "codewith" ? join7(process.cwd(), dir, "config.toml") : join7(process.cwd(), dir, "settings.json");
|
|
2937
3111
|
}
|
|
@@ -2945,13 +3119,15 @@ function getHookPath(name) {
|
|
|
2945
3119
|
return join7(HOOKS_DIR, normalizeHookName(shortName));
|
|
2946
3120
|
}
|
|
2947
3121
|
function hookExists(name) {
|
|
2948
|
-
return
|
|
3122
|
+
return existsSync8(getHookPath(name));
|
|
2949
3123
|
}
|
|
2950
3124
|
function readSettings(scope = "global", target = "claude") {
|
|
2951
3125
|
const path = getSettingsPath(scope, target);
|
|
3126
|
+
if (target === "codex")
|
|
3127
|
+
return readCodexSettings(path);
|
|
2952
3128
|
try {
|
|
2953
|
-
if (
|
|
2954
|
-
return JSON.parse(
|
|
3129
|
+
if (existsSync8(path)) {
|
|
3130
|
+
return JSON.parse(readFileSync5(path, "utf-8"));
|
|
2955
3131
|
}
|
|
2956
3132
|
} catch (error) {
|
|
2957
3133
|
console.warn(`[hooks] Failed to read settings at ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -2960,11 +3136,13 @@ function readSettings(scope = "global", target = "claude") {
|
|
|
2960
3136
|
}
|
|
2961
3137
|
function writeSettings(settings, scope = "global", target = "claude") {
|
|
2962
3138
|
const path = getSettingsPath(scope, target);
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
3139
|
+
if (target === "codex")
|
|
3140
|
+
return writeCodexSettings(path, settings);
|
|
3141
|
+
const dir = dirname4(path);
|
|
3142
|
+
if (!existsSync8(dir)) {
|
|
3143
|
+
mkdirSync5(dir, { recursive: true });
|
|
2966
3144
|
}
|
|
2967
|
-
|
|
3145
|
+
writeFileSync4(path, JSON.stringify(settings, null, 2) + `
|
|
2968
3146
|
`);
|
|
2969
3147
|
}
|
|
2970
3148
|
function getTargetEventName(internalEvent, target) {
|
|
@@ -2987,6 +3165,8 @@ function codewithMatcher(matcher) {
|
|
|
2987
3165
|
}
|
|
2988
3166
|
function codewithTimeout(name) {
|
|
2989
3167
|
switch (name) {
|
|
3168
|
+
case "mementos-context":
|
|
3169
|
+
return 12;
|
|
2990
3170
|
case "knowledge-context":
|
|
2991
3171
|
return 6;
|
|
2992
3172
|
case "session-start":
|
|
@@ -3023,12 +3203,13 @@ function codewithStatusMessage(name) {
|
|
|
3023
3203
|
return `Running ${name}`;
|
|
3024
3204
|
}
|
|
3025
3205
|
}
|
|
3026
|
-
function buildCodewithTomlFragment(name, profile) {
|
|
3206
|
+
function buildCodewithTomlFragment(name, profile, mementos) {
|
|
3027
3207
|
const shortName = shortHookName(name);
|
|
3028
3208
|
const meta = resolveHookMeta(shortName);
|
|
3029
3209
|
if (!meta)
|
|
3030
3210
|
throw new Error(`Hook '${shortName}' not found`);
|
|
3031
|
-
|
|
3211
|
+
validateMementosRegistration(shortName, mementos);
|
|
3212
|
+
const command = (profile ? `hooks run ${shortName} --profile ${profile}` : `hooks run ${shortName}`) + mementosCommandSuffix(mementos);
|
|
3032
3213
|
const matcher = codewithMatcher(meta.matcher);
|
|
3033
3214
|
const fragments = [];
|
|
3034
3215
|
for (const event of getHookEvents(meta)) {
|
|
@@ -3053,21 +3234,21 @@ function buildCodewithTomlFragment(name, profile) {
|
|
|
3053
3234
|
function readCodewithConfig(scope, configPath) {
|
|
3054
3235
|
const path = getSettingsPath(scope, "codewith", configPath);
|
|
3055
3236
|
try {
|
|
3056
|
-
return
|
|
3237
|
+
return existsSync8(path) ? readFileSync5(path, "utf-8") : "";
|
|
3057
3238
|
} catch {
|
|
3058
3239
|
return "";
|
|
3059
3240
|
}
|
|
3060
3241
|
}
|
|
3061
3242
|
function appendCodewithFragment(fragment, scope, configPath) {
|
|
3062
3243
|
const path = getSettingsPath(scope, "codewith", configPath);
|
|
3063
|
-
const dir =
|
|
3064
|
-
if (!
|
|
3065
|
-
|
|
3066
|
-
const existing =
|
|
3244
|
+
const dir = dirname4(path);
|
|
3245
|
+
if (!existsSync8(dir))
|
|
3246
|
+
mkdirSync5(dir, { recursive: true });
|
|
3247
|
+
const existing = existsSync8(path) ? readFileSync5(path, "utf-8") : "";
|
|
3067
3248
|
const sep2 = existing.trim() ? `
|
|
3068
3249
|
|
|
3069
3250
|
` : "";
|
|
3070
|
-
|
|
3251
|
+
writeFileSync4(path, `${existing.replace(/\s*$/, "")}${sep2}${fragment}`);
|
|
3071
3252
|
return path;
|
|
3072
3253
|
}
|
|
3073
3254
|
function detectConflict(name, scope, target) {
|
|
@@ -3110,14 +3291,14 @@ function detectRewriteConflict(name, scope, target, lookup = getHook) {
|
|
|
3110
3291
|
}
|
|
3111
3292
|
return;
|
|
3112
3293
|
}
|
|
3113
|
-
function installForTarget(name, scope, overwrite, target, profile, codewithMode = "fragment", codewithConfigPath) {
|
|
3294
|
+
function installForTarget(name, scope, overwrite, target, profile, codewithMode = "fragment", codewithConfigPath, mementos) {
|
|
3114
3295
|
const shortName = shortHookName(name);
|
|
3115
3296
|
if (!hookExists(shortName)) {
|
|
3116
3297
|
return { hook: shortName, success: false, error: `Hook '${shortName}' not found`, target };
|
|
3117
3298
|
}
|
|
3118
3299
|
if (target === "codewith") {
|
|
3119
3300
|
try {
|
|
3120
|
-
const fragment = buildCodewithTomlFragment(shortName, profile);
|
|
3301
|
+
const fragment = buildCodewithTomlFragment(shortName, profile, mementos);
|
|
3121
3302
|
if (codewithMode !== "write") {
|
|
3122
3303
|
return {
|
|
3123
3304
|
hook: shortName,
|
|
@@ -3164,18 +3345,25 @@ function installForTarget(name, scope, overwrite, target, profile, codewithMode
|
|
|
3164
3345
|
};
|
|
3165
3346
|
}
|
|
3166
3347
|
}
|
|
3167
|
-
const registered = getRegisteredHooksForTarget(scope, target);
|
|
3168
|
-
if (registered.includes(shortName) && !overwrite) {
|
|
3169
|
-
return { hook: shortName, success: false, error: "Already installed. Use --overwrite to replace.", scope, target };
|
|
3170
|
-
}
|
|
3171
|
-
const conflict = detectConflict(shortName, scope, target);
|
|
3172
|
-
const rewriteConflict = detectRewriteConflict(shortName, scope, target);
|
|
3173
|
-
if (rewriteConflict) {
|
|
3174
|
-
return { hook: shortName, success: false, error: `Refused: ${rewriteConflict}`, scope, target };
|
|
3175
|
-
}
|
|
3176
3348
|
try {
|
|
3177
|
-
|
|
3178
|
-
|
|
3349
|
+
const registered = getRegisteredHooksForTarget(scope, target);
|
|
3350
|
+
if (registered.includes(shortName) && !overwrite) {
|
|
3351
|
+
return { hook: shortName, success: false, error: "Already installed. Use --overwrite to replace.", scope, target };
|
|
3352
|
+
}
|
|
3353
|
+
const conflict = detectConflict(shortName, scope, target);
|
|
3354
|
+
const rewriteConflict = detectRewriteConflict(shortName, scope, target);
|
|
3355
|
+
if (rewriteConflict) {
|
|
3356
|
+
return { hook: shortName, success: false, error: `Refused: ${rewriteConflict}`, scope, target };
|
|
3357
|
+
}
|
|
3358
|
+
registerHook(shortName, scope, target, profile, mementos);
|
|
3359
|
+
return {
|
|
3360
|
+
hook: shortName,
|
|
3361
|
+
success: true,
|
|
3362
|
+
scope,
|
|
3363
|
+
target,
|
|
3364
|
+
...conflict ? { conflict } : {},
|
|
3365
|
+
...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." } : {}
|
|
3366
|
+
};
|
|
3179
3367
|
} catch (error) {
|
|
3180
3368
|
return {
|
|
3181
3369
|
hook: shortName,
|
|
@@ -3186,7 +3374,15 @@ function installForTarget(name, scope, overwrite, target, profile, codewithMode
|
|
|
3186
3374
|
}
|
|
3187
3375
|
}
|
|
3188
3376
|
function installHook(name, options = {}) {
|
|
3189
|
-
const { scope = "global", overwrite = false, target = "claude", profile, codewithMode = "fragment", codewithConfigPath } = options;
|
|
3377
|
+
const { scope = "global", overwrite = false, target = "claude", profile, codewithMode = "fragment", codewithConfigPath, mementos } = options;
|
|
3378
|
+
try {
|
|
3379
|
+
validateMementosRegistration(name, mementos);
|
|
3380
|
+
} catch (error) {
|
|
3381
|
+
return { hook: name, success: false, target, error: error instanceof Error ? error.message : "Invalid Mementos options" };
|
|
3382
|
+
}
|
|
3383
|
+
if (!/^[\w-]+$/.test(name) || profile !== undefined && !/^[\w-]+$/.test(profile)) {
|
|
3384
|
+
return { hook: name, success: false, error: "Hook and profile names must contain only letters, digits, underscores or hyphens.", target };
|
|
3385
|
+
}
|
|
3190
3386
|
if (target === "all") {
|
|
3191
3387
|
const shortName = shortHookName(name);
|
|
3192
3388
|
const meta = resolveHookMeta(shortName);
|
|
@@ -3201,7 +3397,7 @@ function installHook(name, options = {}) {
|
|
|
3201
3397
|
};
|
|
3202
3398
|
}
|
|
3203
3399
|
}
|
|
3204
|
-
const claudeResult = installForTarget(name, scope, overwrite, "claude", profile);
|
|
3400
|
+
const claudeResult = installForTarget(name, scope, overwrite, "claude", profile, "fragment", undefined, mementos);
|
|
3205
3401
|
if (!claudeResult.success) {
|
|
3206
3402
|
return {
|
|
3207
3403
|
...claudeResult,
|
|
@@ -3209,7 +3405,7 @@ function installHook(name, options = {}) {
|
|
|
3209
3405
|
target: "all"
|
|
3210
3406
|
};
|
|
3211
3407
|
}
|
|
3212
|
-
const codewithResult = installForTarget(name, scope, overwrite, "codewith", profile, codewithMode, codewithConfigPath);
|
|
3408
|
+
const codewithResult = installForTarget(name, scope, overwrite, "codewith", profile, codewithMode, codewithConfigPath, mementos);
|
|
3213
3409
|
if (!codewithResult.success) {
|
|
3214
3410
|
return {
|
|
3215
3411
|
...codewithResult,
|
|
@@ -3219,9 +3415,9 @@ function installHook(name, options = {}) {
|
|
|
3219
3415
|
}
|
|
3220
3416
|
return { ...claudeResult, target: "all" };
|
|
3221
3417
|
}
|
|
3222
|
-
return installForTarget(name, scope, overwrite, target, profile, codewithMode, codewithConfigPath);
|
|
3418
|
+
return installForTarget(name, scope, overwrite, target, profile, codewithMode, codewithConfigPath, mementos);
|
|
3223
3419
|
}
|
|
3224
|
-
function registerHook(name, scope = "global", target = "claude", profile) {
|
|
3420
|
+
function registerHook(name, scope = "global", target = "claude", profile, mementos) {
|
|
3225
3421
|
const meta = resolveHookMeta(name);
|
|
3226
3422
|
if (!meta)
|
|
3227
3423
|
return;
|
|
@@ -3239,8 +3435,9 @@ function registerHook(name, scope = "global", target = "claude", profile) {
|
|
|
3239
3435
|
const settings = readSettings(scope, target);
|
|
3240
3436
|
if (!settings.hooks)
|
|
3241
3437
|
settings.hooks = {};
|
|
3438
|
+
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;
|
|
3242
3439
|
removeHookFromAllEvents(settings, name);
|
|
3243
|
-
const hookCommand = profile ? `hooks run ${name} --profile ${profile}` : `hooks run ${name}
|
|
3440
|
+
const hookCommand = (profile ? `hooks run ${name} --profile ${profile}` : `hooks run ${name}`) + mementosCommandSuffix(mementos ?? retained);
|
|
3244
3441
|
for (const eventKey of uniqueEventKeys) {
|
|
3245
3442
|
if (!settings.hooks[eventKey])
|
|
3246
3443
|
settings.hooks[eventKey] = [];
|
|
@@ -3282,10 +3479,12 @@ function getRegisteredHooksForTarget(scope = "global", target = "claude") {
|
|
|
3282
3479
|
if (target === "codewith") {
|
|
3283
3480
|
const config = readCodewithConfig(scope);
|
|
3284
3481
|
const registered2 = [];
|
|
3285
|
-
const re =
|
|
3482
|
+
const re = /^\s*command\s*=\s*["']([^"'\n]+)["']\s*$/gm;
|
|
3286
3483
|
let match;
|
|
3287
3484
|
while (match = re.exec(config)) {
|
|
3288
|
-
|
|
3485
|
+
const name = registrationName(match[1]);
|
|
3486
|
+
if (name)
|
|
3487
|
+
registered2.push(name);
|
|
3289
3488
|
}
|
|
3290
3489
|
return [...new Set(registered2)];
|
|
3291
3490
|
}
|
|
@@ -3296,12 +3495,9 @@ function getRegisteredHooksForTarget(scope = "global", target = "claude") {
|
|
|
3296
3495
|
for (const eventKey of Object.keys(settings.hooks)) {
|
|
3297
3496
|
for (const entry of settings.hooks[eventKey]) {
|
|
3298
3497
|
for (const hook of entry.hooks || []) {
|
|
3299
|
-
const
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
if (match) {
|
|
3303
|
-
registered.push(match[1]);
|
|
3304
|
-
}
|
|
3498
|
+
const name = registrationName(hook.command);
|
|
3499
|
+
if (name)
|
|
3500
|
+
registered.push(name);
|
|
3305
3501
|
}
|
|
3306
3502
|
}
|
|
3307
3503
|
}
|
|
@@ -3315,7 +3511,8 @@ function removeHook(name, scope = "global", target = "claude") {
|
|
|
3315
3511
|
if (target === "all") {
|
|
3316
3512
|
const claudeRemoved = removeHookForTarget(shortName, scope, "claude");
|
|
3317
3513
|
const geminiRemoved = removeHookForTarget(shortName, scope, "gemini");
|
|
3318
|
-
|
|
3514
|
+
const codexRemoved = removeHookForTarget(shortName, scope, "codex");
|
|
3515
|
+
return claudeRemoved || geminiRemoved || codexRemoved;
|
|
3319
3516
|
}
|
|
3320
3517
|
if (target === "codewith") {
|
|
3321
3518
|
return false;
|
|
@@ -3333,21 +3530,21 @@ function removeHookForTarget(name, scope, target) {
|
|
|
3333
3530
|
|
|
3334
3531
|
// src/lib/profiles.ts
|
|
3335
3532
|
init_app_home();
|
|
3336
|
-
import { existsSync as
|
|
3533
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5, readdirSync as readdirSync4, rmSync, cpSync as cpSync2 } from "fs";
|
|
3337
3534
|
import { join as join8 } from "path";
|
|
3338
3535
|
function getProfilesDir() {
|
|
3339
3536
|
const newDir = join8(getEffectiveDataRoot(), "profiles");
|
|
3340
3537
|
const oldDir = join8(getHomeDir(), ".hooks", "profiles");
|
|
3341
|
-
if (!
|
|
3342
|
-
|
|
3538
|
+
if (!existsSync9(newDir) && existsSync9(oldDir)) {
|
|
3539
|
+
mkdirSync6(newDir, { recursive: true });
|
|
3343
3540
|
cpSync2(oldDir, newDir, { recursive: true });
|
|
3344
3541
|
}
|
|
3345
3542
|
return newDir;
|
|
3346
3543
|
}
|
|
3347
3544
|
function ensureProfilesDir() {
|
|
3348
3545
|
const dir = getProfilesDir();
|
|
3349
|
-
if (!
|
|
3350
|
-
|
|
3546
|
+
if (!existsSync9(dir)) {
|
|
3547
|
+
mkdirSync6(dir, { recursive: true });
|
|
3351
3548
|
}
|
|
3352
3549
|
}
|
|
3353
3550
|
function profilePath(id) {
|
|
@@ -3370,30 +3567,30 @@ function createProfile(input) {
|
|
|
3370
3567
|
if (input.name) {
|
|
3371
3568
|
profile.name = input.name;
|
|
3372
3569
|
}
|
|
3373
|
-
|
|
3570
|
+
writeFileSync5(profilePath(id), JSON.stringify(profile, null, 2) + `
|
|
3374
3571
|
`);
|
|
3375
3572
|
return profile;
|
|
3376
3573
|
}
|
|
3377
3574
|
function getProfile(id) {
|
|
3378
3575
|
const path = profilePath(id);
|
|
3379
3576
|
try {
|
|
3380
|
-
if (!
|
|
3577
|
+
if (!existsSync9(path))
|
|
3381
3578
|
return null;
|
|
3382
|
-
return JSON.parse(
|
|
3579
|
+
return JSON.parse(readFileSync6(path, "utf-8"));
|
|
3383
3580
|
} catch {
|
|
3384
3581
|
return null;
|
|
3385
3582
|
}
|
|
3386
3583
|
}
|
|
3387
3584
|
function listProfiles() {
|
|
3388
3585
|
const dir = getProfilesDir();
|
|
3389
|
-
if (!
|
|
3586
|
+
if (!existsSync9(dir))
|
|
3390
3587
|
return [];
|
|
3391
3588
|
try {
|
|
3392
3589
|
const files = readdirSync4(dir).filter((f) => f.endsWith(".json"));
|
|
3393
3590
|
const profiles = [];
|
|
3394
3591
|
for (const file of files) {
|
|
3395
3592
|
try {
|
|
3396
|
-
const content =
|
|
3593
|
+
const content = readFileSync6(join8(dir, file), "utf-8");
|
|
3397
3594
|
profiles.push(JSON.parse(content));
|
|
3398
3595
|
} catch {}
|
|
3399
3596
|
}
|
|
@@ -3925,13 +4122,13 @@ function coerceForSqlite(value) {
|
|
|
3925
4122
|
return String(value);
|
|
3926
4123
|
}
|
|
3927
4124
|
// src/mcp/server.ts
|
|
3928
|
-
var __dirname3 =
|
|
4125
|
+
var __dirname3 = dirname7(fileURLToPath3(import.meta.url));
|
|
3929
4126
|
var pkg = { name: "@hasna/hooks", version: "0.0.0" };
|
|
3930
4127
|
try {
|
|
3931
4128
|
for (const rel of ["../../package.json", "../package.json", "../../../package.json"]) {
|
|
3932
4129
|
const p = join11(__dirname3, rel);
|
|
3933
|
-
if (
|
|
3934
|
-
pkg = JSON.parse(
|
|
4130
|
+
if (existsSync11(p)) {
|
|
4131
|
+
pkg = JSON.parse(readFileSync8(p, "utf-8"));
|
|
3935
4132
|
break;
|
|
3936
4133
|
}
|
|
3937
4134
|
}
|
|
@@ -3940,10 +4137,10 @@ async function runVerifiedHook(name, stdin, timeoutMs) {
|
|
|
3940
4137
|
const { resolveHook: resolveHook2, resolveScriptPath: resolveScriptPath2 } = await Promise.resolve().then(() => (init_resolve(), exports_resolve));
|
|
3941
4138
|
const resolved = resolveHook2(name);
|
|
3942
4139
|
const hookScript = resolveScriptPath2(name);
|
|
3943
|
-
if (!resolved || !hookScript || !
|
|
4140
|
+
if (!resolved || !hookScript || !existsSync11(hookScript)) {
|
|
3944
4141
|
throw new Error(`Hook '${name}' not found`);
|
|
3945
4142
|
}
|
|
3946
|
-
const content =
|
|
4143
|
+
const content = readFileSync8(hookScript);
|
|
3947
4144
|
const sha = sha256Of(content);
|
|
3948
4145
|
const check = checkScriptHash(name, sha);
|
|
3949
4146
|
if (!check.ok) {
|
|
@@ -4149,7 +4346,7 @@ function createHooksServer() {
|
|
|
4149
4346
|
const settingsPath = getSettingsPath(scope);
|
|
4150
4347
|
const issues = [];
|
|
4151
4348
|
const healthy = [];
|
|
4152
|
-
const settingsExist =
|
|
4349
|
+
const settingsExist = existsSync11(settingsPath);
|
|
4153
4350
|
if (!settingsExist) {
|
|
4154
4351
|
issues.push({ hook: "(settings)", issue: `${settingsPath} not found`, severity: "warning" });
|
|
4155
4352
|
}
|
|
@@ -4159,7 +4356,7 @@ function createHooksServer() {
|
|
|
4159
4356
|
const meta = custom ? resolveHookMeta(name) : getHook(name);
|
|
4160
4357
|
let hookHealthy = true;
|
|
4161
4358
|
if (custom) {
|
|
4162
|
-
if (!
|
|
4359
|
+
if (!existsSync11(custom.scriptPath)) {
|
|
4163
4360
|
issues.push({ hook: name, issue: `Custom hook script missing: ${custom.scriptPath}`, severity: "error" });
|
|
4164
4361
|
hookHealthy = false;
|
|
4165
4362
|
}
|
|
@@ -4169,14 +4366,14 @@ function createHooksServer() {
|
|
|
4169
4366
|
continue;
|
|
4170
4367
|
}
|
|
4171
4368
|
const hookDir = getHookPath(name);
|
|
4172
|
-
if (!
|
|
4369
|
+
if (!existsSync11(join11(hookDir, "src", "hook.ts"))) {
|
|
4173
4370
|
issues.push({ hook: name, issue: "Missing src/hook.ts in package", severity: "error" });
|
|
4174
4371
|
hookHealthy = false;
|
|
4175
4372
|
}
|
|
4176
4373
|
}
|
|
4177
4374
|
if (meta && settingsExist) {
|
|
4178
4375
|
try {
|
|
4179
|
-
const settings = JSON.parse(
|
|
4376
|
+
const settings = JSON.parse(readFileSync8(settingsPath, "utf-8"));
|
|
4180
4377
|
if (!hookRegisteredInSettings(settings, name, meta.event, meta.matcher)) {
|
|
4181
4378
|
const eventName = meta.event.split(":")[0];
|
|
4182
4379
|
issues.push({ hook: name, issue: `Not registered under correct event (${eventName})`, severity: "error" });
|
|
@@ -4215,8 +4412,8 @@ function createHooksServer() {
|
|
|
4215
4412
|
const hookPath = getHookPath(name);
|
|
4216
4413
|
const readmePath = join11(hookPath, "README.md");
|
|
4217
4414
|
let readme = "";
|
|
4218
|
-
if (
|
|
4219
|
-
readme =
|
|
4415
|
+
if (existsSync11(readmePath)) {
|
|
4416
|
+
readme = readFileSync8(readmePath, "utf-8");
|
|
4220
4417
|
}
|
|
4221
4418
|
if (verbose) {
|
|
4222
4419
|
return { content: [{ type: "text", text: JSON.stringify({ ...meta, readme }) }] };
|
|
@@ -4399,7 +4596,7 @@ function createHooksServer() {
|
|
|
4399
4596
|
const ctx = {
|
|
4400
4597
|
scope,
|
|
4401
4598
|
settings_path: settingsPath,
|
|
4402
|
-
settings_exists:
|
|
4599
|
+
settings_exists: existsSync11(settingsPath),
|
|
4403
4600
|
registered_hooks: hooks,
|
|
4404
4601
|
hook_count: hooks.length,
|
|
4405
4602
|
healthy,
|
|
@@ -4545,8 +4742,8 @@ function createHooksServer() {
|
|
|
4545
4742
|
const settingsPath = getSettingsPath(scope);
|
|
4546
4743
|
let settings = {};
|
|
4547
4744
|
try {
|
|
4548
|
-
if (
|
|
4549
|
-
settings = JSON.parse(
|
|
4745
|
+
if (existsSync11(settingsPath))
|
|
4746
|
+
settings = JSON.parse(readFileSync8(settingsPath, "utf-8"));
|
|
4550
4747
|
} catch {}
|
|
4551
4748
|
if (!settings.hooks)
|
|
4552
4749
|
settings.hooks = {};
|
|
@@ -4554,10 +4751,10 @@ function createHooksServer() {
|
|
|
4554
4751
|
if (!disabled.includes(name))
|
|
4555
4752
|
disabled.push(name);
|
|
4556
4753
|
settings.hooks.__disabled = disabled;
|
|
4557
|
-
const { writeFileSync:
|
|
4558
|
-
const { dirname:
|
|
4559
|
-
|
|
4560
|
-
|
|
4754
|
+
const { writeFileSync: writeFileSync7, mkdirSync: mkdirSync7 } = await import("fs");
|
|
4755
|
+
const { dirname: dirname8 } = await import("path");
|
|
4756
|
+
mkdirSync7(dirname8(settingsPath), { recursive: true });
|
|
4757
|
+
writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + `
|
|
4561
4758
|
`);
|
|
4562
4759
|
return { content: [{ type: "text", text: JSON.stringify({ hook: name, disabled: true, scope }) }] };
|
|
4563
4760
|
});
|
|
@@ -4568,15 +4765,15 @@ function createHooksServer() {
|
|
|
4568
4765
|
const settingsPath = getSettingsPath(scope);
|
|
4569
4766
|
let settings = {};
|
|
4570
4767
|
try {
|
|
4571
|
-
if (
|
|
4572
|
-
settings = JSON.parse(
|
|
4768
|
+
if (existsSync11(settingsPath))
|
|
4769
|
+
settings = JSON.parse(readFileSync8(settingsPath, "utf-8"));
|
|
4573
4770
|
} catch {}
|
|
4574
4771
|
if (settings.hooks?.__disabled) {
|
|
4575
4772
|
settings.hooks.__disabled = settings.hooks.__disabled.filter((n) => n !== name);
|
|
4576
4773
|
if (settings.hooks.__disabled.length === 0)
|
|
4577
4774
|
delete settings.hooks.__disabled;
|
|
4578
|
-
const { writeFileSync:
|
|
4579
|
-
|
|
4775
|
+
const { writeFileSync: writeFileSync7 } = await import("fs");
|
|
4776
|
+
writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + `
|
|
4580
4777
|
`);
|
|
4581
4778
|
}
|
|
4582
4779
|
return { content: [{ type: "text", text: JSON.stringify({ hook: name, disabled: false, scope }) }] };
|