@hasna/hooks 0.9.2 → 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/bin/hooks-mcp.js +98 -19
- package/bin/index.js +127 -25
- package/dist/index.js +98 -21
- package/dist/lib/installer.d.ts +4 -1
- package/dist/lib/mementos-options.d.ts +16 -0
- package/dist/lib/registration.d.ts +1 -0
- package/hooks/hook-mementos-context/README.md +28 -2
- package/package.json +1 -1
package/bin/hooks-mcp.js
CHANGED
|
@@ -2679,6 +2679,79 @@ import { fileURLToPath as fileURLToPath2 } from "url";
|
|
|
2679
2679
|
|
|
2680
2680
|
// src/lib/registration.ts
|
|
2681
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
|
+
}
|
|
2682
2755
|
function matchersOverlap(a, b) {
|
|
2683
2756
|
return a === b || a.includes(b) || b.includes(a);
|
|
2684
2757
|
}
|
|
@@ -3130,12 +3203,13 @@ function codewithStatusMessage(name) {
|
|
|
3130
3203
|
return `Running ${name}`;
|
|
3131
3204
|
}
|
|
3132
3205
|
}
|
|
3133
|
-
function buildCodewithTomlFragment(name, profile) {
|
|
3206
|
+
function buildCodewithTomlFragment(name, profile, mementos) {
|
|
3134
3207
|
const shortName = shortHookName(name);
|
|
3135
3208
|
const meta = resolveHookMeta(shortName);
|
|
3136
3209
|
if (!meta)
|
|
3137
3210
|
throw new Error(`Hook '${shortName}' not found`);
|
|
3138
|
-
|
|
3211
|
+
validateMementosRegistration(shortName, mementos);
|
|
3212
|
+
const command = (profile ? `hooks run ${shortName} --profile ${profile}` : `hooks run ${shortName}`) + mementosCommandSuffix(mementos);
|
|
3139
3213
|
const matcher = codewithMatcher(meta.matcher);
|
|
3140
3214
|
const fragments = [];
|
|
3141
3215
|
for (const event of getHookEvents(meta)) {
|
|
@@ -3217,14 +3291,14 @@ function detectRewriteConflict(name, scope, target, lookup = getHook) {
|
|
|
3217
3291
|
}
|
|
3218
3292
|
return;
|
|
3219
3293
|
}
|
|
3220
|
-
function installForTarget(name, scope, overwrite, target, profile, codewithMode = "fragment", codewithConfigPath) {
|
|
3294
|
+
function installForTarget(name, scope, overwrite, target, profile, codewithMode = "fragment", codewithConfigPath, mementos) {
|
|
3221
3295
|
const shortName = shortHookName(name);
|
|
3222
3296
|
if (!hookExists(shortName)) {
|
|
3223
3297
|
return { hook: shortName, success: false, error: `Hook '${shortName}' not found`, target };
|
|
3224
3298
|
}
|
|
3225
3299
|
if (target === "codewith") {
|
|
3226
3300
|
try {
|
|
3227
|
-
const fragment = buildCodewithTomlFragment(shortName, profile);
|
|
3301
|
+
const fragment = buildCodewithTomlFragment(shortName, profile, mementos);
|
|
3228
3302
|
if (codewithMode !== "write") {
|
|
3229
3303
|
return {
|
|
3230
3304
|
hook: shortName,
|
|
@@ -3281,7 +3355,7 @@ function installForTarget(name, scope, overwrite, target, profile, codewithMode
|
|
|
3281
3355
|
if (rewriteConflict) {
|
|
3282
3356
|
return { hook: shortName, success: false, error: `Refused: ${rewriteConflict}`, scope, target };
|
|
3283
3357
|
}
|
|
3284
|
-
registerHook(shortName, scope, target, profile);
|
|
3358
|
+
registerHook(shortName, scope, target, profile, mementos);
|
|
3285
3359
|
return {
|
|
3286
3360
|
hook: shortName,
|
|
3287
3361
|
success: true,
|
|
@@ -3300,7 +3374,12 @@ function installForTarget(name, scope, overwrite, target, profile, codewithMode
|
|
|
3300
3374
|
}
|
|
3301
3375
|
}
|
|
3302
3376
|
function installHook(name, options = {}) {
|
|
3303
|
-
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
|
+
}
|
|
3304
3383
|
if (!/^[\w-]+$/.test(name) || profile !== undefined && !/^[\w-]+$/.test(profile)) {
|
|
3305
3384
|
return { hook: name, success: false, error: "Hook and profile names must contain only letters, digits, underscores or hyphens.", target };
|
|
3306
3385
|
}
|
|
@@ -3318,7 +3397,7 @@ function installHook(name, options = {}) {
|
|
|
3318
3397
|
};
|
|
3319
3398
|
}
|
|
3320
3399
|
}
|
|
3321
|
-
const claudeResult = installForTarget(name, scope, overwrite, "claude", profile);
|
|
3400
|
+
const claudeResult = installForTarget(name, scope, overwrite, "claude", profile, "fragment", undefined, mementos);
|
|
3322
3401
|
if (!claudeResult.success) {
|
|
3323
3402
|
return {
|
|
3324
3403
|
...claudeResult,
|
|
@@ -3326,7 +3405,7 @@ function installHook(name, options = {}) {
|
|
|
3326
3405
|
target: "all"
|
|
3327
3406
|
};
|
|
3328
3407
|
}
|
|
3329
|
-
const codewithResult = installForTarget(name, scope, overwrite, "codewith", profile, codewithMode, codewithConfigPath);
|
|
3408
|
+
const codewithResult = installForTarget(name, scope, overwrite, "codewith", profile, codewithMode, codewithConfigPath, mementos);
|
|
3330
3409
|
if (!codewithResult.success) {
|
|
3331
3410
|
return {
|
|
3332
3411
|
...codewithResult,
|
|
@@ -3336,9 +3415,9 @@ function installHook(name, options = {}) {
|
|
|
3336
3415
|
}
|
|
3337
3416
|
return { ...claudeResult, target: "all" };
|
|
3338
3417
|
}
|
|
3339
|
-
return installForTarget(name, scope, overwrite, target, profile, codewithMode, codewithConfigPath);
|
|
3418
|
+
return installForTarget(name, scope, overwrite, target, profile, codewithMode, codewithConfigPath, mementos);
|
|
3340
3419
|
}
|
|
3341
|
-
function registerHook(name, scope = "global", target = "claude", profile) {
|
|
3420
|
+
function registerHook(name, scope = "global", target = "claude", profile, mementos) {
|
|
3342
3421
|
const meta = resolveHookMeta(name);
|
|
3343
3422
|
if (!meta)
|
|
3344
3423
|
return;
|
|
@@ -3356,8 +3435,9 @@ function registerHook(name, scope = "global", target = "claude", profile) {
|
|
|
3356
3435
|
const settings = readSettings(scope, target);
|
|
3357
3436
|
if (!settings.hooks)
|
|
3358
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;
|
|
3359
3439
|
removeHookFromAllEvents(settings, name);
|
|
3360
|
-
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);
|
|
3361
3441
|
for (const eventKey of uniqueEventKeys) {
|
|
3362
3442
|
if (!settings.hooks[eventKey])
|
|
3363
3443
|
settings.hooks[eventKey] = [];
|
|
@@ -3399,10 +3479,12 @@ function getRegisteredHooksForTarget(scope = "global", target = "claude") {
|
|
|
3399
3479
|
if (target === "codewith") {
|
|
3400
3480
|
const config = readCodewithConfig(scope);
|
|
3401
3481
|
const registered2 = [];
|
|
3402
|
-
const re =
|
|
3482
|
+
const re = /^\s*command\s*=\s*["']([^"'\n]+)["']\s*$/gm;
|
|
3403
3483
|
let match;
|
|
3404
3484
|
while (match = re.exec(config)) {
|
|
3405
|
-
|
|
3485
|
+
const name = registrationName(match[1]);
|
|
3486
|
+
if (name)
|
|
3487
|
+
registered2.push(name);
|
|
3406
3488
|
}
|
|
3407
3489
|
return [...new Set(registered2)];
|
|
3408
3490
|
}
|
|
@@ -3413,12 +3495,9 @@ function getRegisteredHooksForTarget(scope = "global", target = "claude") {
|
|
|
3413
3495
|
for (const eventKey of Object.keys(settings.hooks)) {
|
|
3414
3496
|
for (const entry of settings.hooks[eventKey]) {
|
|
3415
3497
|
for (const hook of entry.hooks || []) {
|
|
3416
|
-
const
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
if (match) {
|
|
3420
|
-
registered.push(match[1]);
|
|
3421
|
-
}
|
|
3498
|
+
const name = registrationName(hook.command);
|
|
3499
|
+
if (name)
|
|
3500
|
+
registered.push(name);
|
|
3422
3501
|
}
|
|
3423
3502
|
}
|
|
3424
3503
|
}
|
package/bin/index.js
CHANGED
|
@@ -4500,10 +4500,84 @@ var init_resolve = __esm(() => {
|
|
|
4500
4500
|
init_manifest();
|
|
4501
4501
|
});
|
|
4502
4502
|
|
|
4503
|
+
// src/lib/mementos-options.ts
|
|
4504
|
+
function validateMementosRegistration(name, value) {
|
|
4505
|
+
if (value === undefined)
|
|
4506
|
+
return;
|
|
4507
|
+
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") {
|
|
4508
|
+
throw new Error("Mementos options require the mementos-context hook and an explicit enable/disable choice.");
|
|
4509
|
+
}
|
|
4510
|
+
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))) {
|
|
4511
|
+
throw new Error("OpenRouter requires an enabled Mementos hook and a valid Secrets reference, never a key value.");
|
|
4512
|
+
}
|
|
4513
|
+
}
|
|
4514
|
+
function mementosRegistrationFromFlags(name, flags) {
|
|
4515
|
+
if (!flags.enableMementos && !flags.disableMementos && flags.openrouterSecretRef === undefined)
|
|
4516
|
+
return;
|
|
4517
|
+
if (Boolean(flags.enableMementos) === Boolean(flags.disableMementos)) {
|
|
4518
|
+
throw new Error("Choose exactly one of --enable-mementos or --disable-mementos.");
|
|
4519
|
+
}
|
|
4520
|
+
const value = { enabled: flags.enableMementos === true, ...flags.openrouterSecretRef !== undefined ? { secretRef: flags.openrouterSecretRef } : {} };
|
|
4521
|
+
validateMementosRegistration(name, value);
|
|
4522
|
+
return value;
|
|
4523
|
+
}
|
|
4524
|
+
function mementosCommandSuffix(value) {
|
|
4525
|
+
validateMementosRegistration("mementos-context", value);
|
|
4526
|
+
if (!value)
|
|
4527
|
+
return "";
|
|
4528
|
+
return value.enabled ? ` --enable-mementos${value.secretRef ? ` --openrouter-secret-ref ${value.secretRef}` : ""}` : " --disable-mementos";
|
|
4529
|
+
}
|
|
4530
|
+
function retainedMementosRegistration(commands) {
|
|
4531
|
+
const values = commands.filter((command) => /^hooks run mementos-context(?:\s|$)/.test(command)).map((command) => {
|
|
4532
|
+
const args = command.trim().split(/\s+/).slice(3);
|
|
4533
|
+
const flags = {};
|
|
4534
|
+
const seen = new Set;
|
|
4535
|
+
while (args.length) {
|
|
4536
|
+
const key = args.shift();
|
|
4537
|
+
if (seen.has(key))
|
|
4538
|
+
throw new Error("Ambiguous Mementos registration; specify its enablement explicitly.");
|
|
4539
|
+
seen.add(key);
|
|
4540
|
+
if (key === "--profile" && /^[\w-]+$/.test(args[0] ?? ""))
|
|
4541
|
+
args.shift();
|
|
4542
|
+
else if (key === "--enable-mementos")
|
|
4543
|
+
flags.enableMementos = true;
|
|
4544
|
+
else if (key === "--disable-mementos")
|
|
4545
|
+
flags.disableMementos = true;
|
|
4546
|
+
else if (key === "--openrouter-secret-ref" && args.length)
|
|
4547
|
+
flags.openrouterSecretRef = args.shift();
|
|
4548
|
+
else
|
|
4549
|
+
throw new Error("Unrecognized Mementos registration options; specify its enablement explicitly.");
|
|
4550
|
+
}
|
|
4551
|
+
return mementosRegistrationFromFlags("mementos-context", flags);
|
|
4552
|
+
});
|
|
4553
|
+
if (new Set(values.map((value) => JSON.stringify(value))).size > 1) {
|
|
4554
|
+
throw new Error("Conflicting Mementos registrations; specify its enablement explicitly.");
|
|
4555
|
+
}
|
|
4556
|
+
return values[0];
|
|
4557
|
+
}
|
|
4558
|
+
function mementosRunEnvironment(env, value) {
|
|
4559
|
+
validateMementosRegistration("mementos-context", value);
|
|
4560
|
+
if (!value)
|
|
4561
|
+
return env;
|
|
4562
|
+
return {
|
|
4563
|
+
...env,
|
|
4564
|
+
HOOKS_MEMENTOS_ENABLED: value.enabled && env.HOOKS_MEMENTOS_ENABLED !== "0" ? "1" : "0",
|
|
4565
|
+
...value.secretRef ? { HOOKS_MEMENTOS_OPENROUTER_SECRET_REF: value.secretRef } : {}
|
|
4566
|
+
};
|
|
4567
|
+
}
|
|
4568
|
+
|
|
4503
4569
|
// src/lib/registration.ts
|
|
4504
4570
|
function registrationName(command) {
|
|
4505
4571
|
if (typeof command !== "string")
|
|
4506
4572
|
return;
|
|
4573
|
+
if (/^hooks run mementos-context(?:\s|$)/.test(command.trim())) {
|
|
4574
|
+
try {
|
|
4575
|
+
retainedMementosRegistration([command.trim()]);
|
|
4576
|
+
return "mementos-context";
|
|
4577
|
+
} catch {
|
|
4578
|
+
return;
|
|
4579
|
+
}
|
|
4580
|
+
}
|
|
4507
4581
|
const match = command.trim().match(REGISTRATION_COMMAND_RE);
|
|
4508
4582
|
return match ? match[1] ?? match[2] : undefined;
|
|
4509
4583
|
}
|
|
@@ -5483,12 +5557,13 @@ function codewithStatusMessage(name) {
|
|
|
5483
5557
|
return `Running ${name}`;
|
|
5484
5558
|
}
|
|
5485
5559
|
}
|
|
5486
|
-
function buildCodewithTomlFragment(name, profile) {
|
|
5560
|
+
function buildCodewithTomlFragment(name, profile, mementos) {
|
|
5487
5561
|
const shortName = shortHookName(name);
|
|
5488
5562
|
const meta = resolveHookMeta(shortName);
|
|
5489
5563
|
if (!meta)
|
|
5490
5564
|
throw new Error(`Hook '${shortName}' not found`);
|
|
5491
|
-
|
|
5565
|
+
validateMementosRegistration(shortName, mementos);
|
|
5566
|
+
const command = (profile ? `hooks run ${shortName} --profile ${profile}` : `hooks run ${shortName}`) + mementosCommandSuffix(mementos);
|
|
5492
5567
|
const matcher = codewithMatcher(meta.matcher);
|
|
5493
5568
|
const fragments = [];
|
|
5494
5569
|
for (const event of getHookEvents(meta)) {
|
|
@@ -5570,14 +5645,14 @@ function detectRewriteConflict(name, scope, target, lookup = getHook) {
|
|
|
5570
5645
|
}
|
|
5571
5646
|
return;
|
|
5572
5647
|
}
|
|
5573
|
-
function installForTarget(name, scope, overwrite, target, profile, codewithMode = "fragment", codewithConfigPath) {
|
|
5648
|
+
function installForTarget(name, scope, overwrite, target, profile, codewithMode = "fragment", codewithConfigPath, mementos) {
|
|
5574
5649
|
const shortName = shortHookName(name);
|
|
5575
5650
|
if (!hookExists(shortName)) {
|
|
5576
5651
|
return { hook: shortName, success: false, error: `Hook '${shortName}' not found`, target };
|
|
5577
5652
|
}
|
|
5578
5653
|
if (target === "codewith") {
|
|
5579
5654
|
try {
|
|
5580
|
-
const fragment = buildCodewithTomlFragment(shortName, profile);
|
|
5655
|
+
const fragment = buildCodewithTomlFragment(shortName, profile, mementos);
|
|
5581
5656
|
if (codewithMode !== "write") {
|
|
5582
5657
|
return {
|
|
5583
5658
|
hook: shortName,
|
|
@@ -5634,7 +5709,7 @@ function installForTarget(name, scope, overwrite, target, profile, codewithMode
|
|
|
5634
5709
|
if (rewriteConflict) {
|
|
5635
5710
|
return { hook: shortName, success: false, error: `Refused: ${rewriteConflict}`, scope, target };
|
|
5636
5711
|
}
|
|
5637
|
-
registerHook(shortName, scope, target, profile);
|
|
5712
|
+
registerHook(shortName, scope, target, profile, mementos);
|
|
5638
5713
|
return {
|
|
5639
5714
|
hook: shortName,
|
|
5640
5715
|
success: true,
|
|
@@ -5653,7 +5728,12 @@ function installForTarget(name, scope, overwrite, target, profile, codewithMode
|
|
|
5653
5728
|
}
|
|
5654
5729
|
}
|
|
5655
5730
|
function installHook(name, options = {}) {
|
|
5656
|
-
const { scope = "global", overwrite = false, target = "claude", profile, codewithMode = "fragment", codewithConfigPath } = options;
|
|
5731
|
+
const { scope = "global", overwrite = false, target = "claude", profile, codewithMode = "fragment", codewithConfigPath, mementos } = options;
|
|
5732
|
+
try {
|
|
5733
|
+
validateMementosRegistration(name, mementos);
|
|
5734
|
+
} catch (error) {
|
|
5735
|
+
return { hook: name, success: false, target, error: error instanceof Error ? error.message : "Invalid Mementos options" };
|
|
5736
|
+
}
|
|
5657
5737
|
if (!/^[\w-]+$/.test(name) || profile !== undefined && !/^[\w-]+$/.test(profile)) {
|
|
5658
5738
|
return { hook: name, success: false, error: "Hook and profile names must contain only letters, digits, underscores or hyphens.", target };
|
|
5659
5739
|
}
|
|
@@ -5671,7 +5751,7 @@ function installHook(name, options = {}) {
|
|
|
5671
5751
|
};
|
|
5672
5752
|
}
|
|
5673
5753
|
}
|
|
5674
|
-
const claudeResult = installForTarget(name, scope, overwrite, "claude", profile);
|
|
5754
|
+
const claudeResult = installForTarget(name, scope, overwrite, "claude", profile, "fragment", undefined, mementos);
|
|
5675
5755
|
if (!claudeResult.success) {
|
|
5676
5756
|
return {
|
|
5677
5757
|
...claudeResult,
|
|
@@ -5679,7 +5759,7 @@ function installHook(name, options = {}) {
|
|
|
5679
5759
|
target: "all"
|
|
5680
5760
|
};
|
|
5681
5761
|
}
|
|
5682
|
-
const codewithResult = installForTarget(name, scope, overwrite, "codewith", profile, codewithMode, codewithConfigPath);
|
|
5762
|
+
const codewithResult = installForTarget(name, scope, overwrite, "codewith", profile, codewithMode, codewithConfigPath, mementos);
|
|
5683
5763
|
if (!codewithResult.success) {
|
|
5684
5764
|
return {
|
|
5685
5765
|
...codewithResult,
|
|
@@ -5689,9 +5769,9 @@ function installHook(name, options = {}) {
|
|
|
5689
5769
|
}
|
|
5690
5770
|
return { ...claudeResult, target: "all" };
|
|
5691
5771
|
}
|
|
5692
|
-
return installForTarget(name, scope, overwrite, target, profile, codewithMode, codewithConfigPath);
|
|
5772
|
+
return installForTarget(name, scope, overwrite, target, profile, codewithMode, codewithConfigPath, mementos);
|
|
5693
5773
|
}
|
|
5694
|
-
function registerHook(name, scope = "global", target = "claude", profile) {
|
|
5774
|
+
function registerHook(name, scope = "global", target = "claude", profile, mementos) {
|
|
5695
5775
|
const meta = resolveHookMeta(name);
|
|
5696
5776
|
if (!meta)
|
|
5697
5777
|
return;
|
|
@@ -5709,8 +5789,9 @@ function registerHook(name, scope = "global", target = "claude", profile) {
|
|
|
5709
5789
|
const settings = readSettings(scope, target);
|
|
5710
5790
|
if (!settings.hooks)
|
|
5711
5791
|
settings.hooks = {};
|
|
5792
|
+
const retained = name === "mementos-context" && mementos === undefined ? retainedMementosRegistration(Object.values(settings.hooks).flatMap((groups) => Array.isArray(groups) ? groups.flatMap((group) => Array.isArray(group.hooks) ? group.hooks.map((hook) => hook.command).filter((command) => typeof command === "string") : []) : [])) : undefined;
|
|
5712
5793
|
removeHookFromAllEvents(settings, name);
|
|
5713
|
-
const hookCommand = profile ? `hooks run ${name} --profile ${profile}` : `hooks run ${name}
|
|
5794
|
+
const hookCommand = (profile ? `hooks run ${name} --profile ${profile}` : `hooks run ${name}`) + mementosCommandSuffix(mementos ?? retained);
|
|
5714
5795
|
for (const eventKey of uniqueEventKeys) {
|
|
5715
5796
|
if (!settings.hooks[eventKey])
|
|
5716
5797
|
settings.hooks[eventKey] = [];
|
|
@@ -5755,10 +5836,12 @@ function getRegisteredHooksForTarget(scope = "global", target = "claude") {
|
|
|
5755
5836
|
if (target === "codewith") {
|
|
5756
5837
|
const config = readCodewithConfig(scope);
|
|
5757
5838
|
const registered2 = [];
|
|
5758
|
-
const re =
|
|
5839
|
+
const re = /^\s*command\s*=\s*["']([^"'\n]+)["']\s*$/gm;
|
|
5759
5840
|
let match;
|
|
5760
5841
|
while (match = re.exec(config)) {
|
|
5761
|
-
|
|
5842
|
+
const name = registrationName(match[1]);
|
|
5843
|
+
if (name)
|
|
5844
|
+
registered2.push(name);
|
|
5762
5845
|
}
|
|
5763
5846
|
return [...new Set(registered2)];
|
|
5764
5847
|
}
|
|
@@ -5769,12 +5852,9 @@ function getRegisteredHooksForTarget(scope = "global", target = "claude") {
|
|
|
5769
5852
|
for (const eventKey of Object.keys(settings.hooks)) {
|
|
5770
5853
|
for (const entry of settings.hooks[eventKey]) {
|
|
5771
5854
|
for (const hook of entry.hooks || []) {
|
|
5772
|
-
const
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
if (match) {
|
|
5776
|
-
registered.push(match[1]);
|
|
5777
|
-
}
|
|
5855
|
+
const name = registrationName(hook.command);
|
|
5856
|
+
if (name)
|
|
5857
|
+
registered.push(name);
|
|
5778
5858
|
}
|
|
5779
5859
|
}
|
|
5780
5860
|
}
|
|
@@ -5876,7 +5956,7 @@ function pruneStaleRegistrations(scope = "global") {
|
|
|
5876
5956
|
}
|
|
5877
5957
|
function removeCodewithHookEntry(configText, name) {
|
|
5878
5958
|
const sections = configText.split(/\n\s*\n/);
|
|
5879
|
-
const
|
|
5959
|
+
const commandRe = /^command\s*=\s*"([^"\n]+)"$/;
|
|
5880
5960
|
const entryHeaderRe = /^\[\[hooks\.([\w]+)\.hooks\]\]$/;
|
|
5881
5961
|
const eventHeaderRe = /^\[\[hooks\.([\w]+)\]\]$/;
|
|
5882
5962
|
const eventsWithEntries = new Set;
|
|
@@ -5889,7 +5969,7 @@ function removeCodewithHookEntry(configText, name) {
|
|
|
5889
5969
|
if (entryMatch) {
|
|
5890
5970
|
const event = entryMatch[1];
|
|
5891
5971
|
eventsWithEntries.add(event);
|
|
5892
|
-
if (lines.some((line) =>
|
|
5972
|
+
if (lines.some((line) => registrationName(commandRe.exec(line.trim())?.[1]) === name)) {
|
|
5893
5973
|
removed = true;
|
|
5894
5974
|
continue;
|
|
5895
5975
|
}
|
|
@@ -13135,7 +13215,15 @@ program2.command("init").description("Register a new agent profile with a unique
|
|
|
13135
13215
|
console.log(` hooks install gitguard --profile ${profile.agent_id}`);
|
|
13136
13216
|
console.log();
|
|
13137
13217
|
});
|
|
13138
|
-
program2.command("run").argument("<hook>", "Hook to run").option("--profile <id>", "Agent profile ID").description("Execute a hook (called by AI coding agents)").action(async (hook, options) => {
|
|
13218
|
+
program2.command("run").argument("<hook>", "Hook to run").option("--profile <id>", "Agent profile ID").option("--enable-mementos", "Enable mementos-context for this invocation (provider assistance remains separately opt-in)").option("--disable-mementos", "Disable mementos-context for this invocation").option("--openrouter-secret-ref <reference>", "Secrets reference for the enabled Mementos hook; never a key value").description("Execute a hook (called by AI coding agents)").action(async (hook, options) => {
|
|
13219
|
+
let mementos;
|
|
13220
|
+
try {
|
|
13221
|
+
mementos = mementosRegistrationFromFlags(hook, options);
|
|
13222
|
+
} catch (error) {
|
|
13223
|
+
console.error(JSON.stringify({ error: error instanceof Error ? error.message : "Invalid Mementos options" }));
|
|
13224
|
+
process.exitCode = 1;
|
|
13225
|
+
return;
|
|
13226
|
+
}
|
|
13139
13227
|
const { resolveHook: resolveHook2, resolveScriptPath: resolveScriptPath2 } = await Promise.resolve().then(() => (init_resolve(), exports_resolve));
|
|
13140
13228
|
const { sha256Of: sha256Of2, checkScriptHash: checkScriptHash2 } = await Promise.resolve().then(() => (init_store(), exports_store));
|
|
13141
13229
|
const { recordHookRun: recordHookRun2, resolveEventType: resolveEventType2 } = await Promise.resolve().then(() => (init_db_writer(), exports_db_writer));
|
|
@@ -13195,7 +13283,7 @@ program2.command("run").argument("<hook>", "Hook to run").option("--profile <id>
|
|
|
13195
13283
|
content,
|
|
13196
13284
|
args,
|
|
13197
13285
|
stdin: hookStdin,
|
|
13198
|
-
env: process.env,
|
|
13286
|
+
env: mementosRunEnvironment(process.env, mementos),
|
|
13199
13287
|
timeout
|
|
13200
13288
|
}));
|
|
13201
13289
|
} catch (err) {
|
|
@@ -13255,7 +13343,7 @@ program2.command("run").argument("<hook>", "Hook to run").option("--profile <id>
|
|
|
13255
13343
|
process.stderr.write(stderr);
|
|
13256
13344
|
process.exit(exitCode);
|
|
13257
13345
|
});
|
|
13258
|
-
program2.command("install").alias("add").argument("[hooks...]", "Hooks to install").option("-o, --overwrite", "Overwrite existing hooks", false).option("-a, --all", "Install all available hooks", false).option("-c, --category <category>", "Install all hooks in a category").option("-g, --global", "Install globally (~/.claude/settings.json)", false).option("-p, --project", "Install for current project (.claude/settings.json)", false).option("-t, --target <target>", "Agent target: claude, gemini, codewith, codex, all (default: claude)", "claude").option("--profile <id>", "Agent profile ID to scope hooks to").option("--dry-run", "Preview what would be installed without writing to settings", false).option("--apply-codewith", "Explicitly append Codewith TOML to a config file (prefer configs for managed configs)", false).option("--codewith-config <path>", "Explicit Codewith config path required with --apply-codewith").option("-j, --json", "Output as JSON", false).description("Install one or more hooks (registry names, local paths, git URLs, manifest URLs, or <name>@<version> pinned installs)").action(async (hooks, options) => {
|
|
13346
|
+
program2.command("install").alias("add").argument("[hooks...]", "Hooks to install").option("-o, --overwrite", "Overwrite existing hooks", false).option("-a, --all", "Install all available hooks", false).option("-c, --category <category>", "Install all hooks in a category").option("-g, --global", "Install globally (~/.claude/settings.json)", false).option("-p, --project", "Install for current project (.claude/settings.json)", false).option("-t, --target <target>", "Agent target: claude, gemini, codewith, codex, all (default: claude)", "claude").option("--profile <id>", "Agent profile ID to scope hooks to").option("--enable-mementos", "Persistently enable mementos-context (provider assistance remains separately opt-in)").option("--disable-mementos", "Persistently disable mementos-context").option("--openrouter-secret-ref <reference>", "Secrets reference for the enabled Mementos hook; never a key value").option("--dry-run", "Preview what would be installed without writing to settings", false).option("--apply-codewith", "Explicitly append Codewith TOML to a config file (prefer configs for managed configs)", false).option("--codewith-config <path>", "Explicit Codewith config path required with --apply-codewith").option("-j, --json", "Output as JSON", false).description("Install one or more hooks (registry names, local paths, git URLs, manifest URLs, or <name>@<version> pinned installs)").action(async (hooks, options) => {
|
|
13259
13347
|
const scope = resolveScope(options);
|
|
13260
13348
|
const target = resolveTarget(options);
|
|
13261
13349
|
let toInstall = hooks;
|
|
@@ -13279,6 +13367,18 @@ program2.command("install").alias("add").argument("[hooks...]", "Hooks to instal
|
|
|
13279
13367
|
render(/* @__PURE__ */ jsxDEV8(App, {}, undefined, false, undefined, this));
|
|
13280
13368
|
return;
|
|
13281
13369
|
}
|
|
13370
|
+
let mementos;
|
|
13371
|
+
try {
|
|
13372
|
+
mementos = mementosRegistrationFromFlags(toInstall.length === 1 ? toInstall[0] : "", options);
|
|
13373
|
+
} catch (error) {
|
|
13374
|
+
const message = error instanceof Error ? error.message : "Invalid Mementos options";
|
|
13375
|
+
if (options.json)
|
|
13376
|
+
console.log(JSON.stringify({ error: message, applied: false }));
|
|
13377
|
+
else
|
|
13378
|
+
console.error(message);
|
|
13379
|
+
process.exitCode = 1;
|
|
13380
|
+
return;
|
|
13381
|
+
}
|
|
13282
13382
|
if (options.applyCodewith && (target === "codewith" || target === "all") && !options.codewithConfig) {
|
|
13283
13383
|
const message = "--apply-codewith requires --codewith-config <path>; refusing to write default ~/.codewith/config.toml.";
|
|
13284
13384
|
if (options.json) {
|
|
@@ -13317,6 +13417,7 @@ program2.command("install").alias("add").argument("[hooks...]", "Hooks to instal
|
|
|
13317
13417
|
console.log(JSON.stringify({
|
|
13318
13418
|
dryRun: true,
|
|
13319
13419
|
would_install: known,
|
|
13420
|
+
...mementos ? { mementos } : {},
|
|
13320
13421
|
custom_sources: customSources,
|
|
13321
13422
|
pinned: pinnedRequests.map((p) => `${p.pinned.name}@${p.pinned.version}`),
|
|
13322
13423
|
unknown,
|
|
@@ -13421,7 +13522,8 @@ Dry run \u2014 would install (${scope}, ${target}):
|
|
|
13421
13522
|
target,
|
|
13422
13523
|
profile: options.profile,
|
|
13423
13524
|
codewithMode: options.applyCodewith ? "write" : "fragment",
|
|
13424
|
-
codewithConfigPath: options.codewithConfig
|
|
13525
|
+
codewithConfigPath: options.codewithConfig,
|
|
13526
|
+
mementos
|
|
13425
13527
|
});
|
|
13426
13528
|
results.push(result);
|
|
13427
13529
|
}
|
package/dist/index.js
CHANGED
|
@@ -7445,7 +7445,78 @@ function resolveHook(name) {
|
|
|
7445
7445
|
// src/lib/installer.ts
|
|
7446
7446
|
init_manifest();
|
|
7447
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
|
+
|
|
7448
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
|
+
}
|
|
7449
7520
|
function matchersOverlap(a, b) {
|
|
7450
7521
|
return a === b || a.includes(b) || b.includes(a);
|
|
7451
7522
|
}
|
|
@@ -7711,12 +7782,13 @@ function codewithStatusMessage(name) {
|
|
|
7711
7782
|
return `Running ${name}`;
|
|
7712
7783
|
}
|
|
7713
7784
|
}
|
|
7714
|
-
function buildCodewithTomlFragment(name, profile) {
|
|
7785
|
+
function buildCodewithTomlFragment(name, profile, mementos) {
|
|
7715
7786
|
const shortName = shortHookName(name);
|
|
7716
7787
|
const meta = resolveHookMeta(shortName);
|
|
7717
7788
|
if (!meta)
|
|
7718
7789
|
throw new Error(`Hook '${shortName}' not found`);
|
|
7719
|
-
|
|
7790
|
+
validateMementosRegistration(shortName, mementos);
|
|
7791
|
+
const command = (profile ? `hooks run ${shortName} --profile ${profile}` : `hooks run ${shortName}`) + mementosCommandSuffix(mementos);
|
|
7720
7792
|
const matcher = codewithMatcher(meta.matcher);
|
|
7721
7793
|
const fragments = [];
|
|
7722
7794
|
for (const event of getHookEvents(meta)) {
|
|
@@ -7798,14 +7870,14 @@ function detectRewriteConflict(name, scope, target, lookup = getHook) {
|
|
|
7798
7870
|
}
|
|
7799
7871
|
return;
|
|
7800
7872
|
}
|
|
7801
|
-
function installForTarget(name, scope, overwrite, target, profile, codewithMode = "fragment", codewithConfigPath) {
|
|
7873
|
+
function installForTarget(name, scope, overwrite, target, profile, codewithMode = "fragment", codewithConfigPath, mementos) {
|
|
7802
7874
|
const shortName = shortHookName(name);
|
|
7803
7875
|
if (!hookExists(shortName)) {
|
|
7804
7876
|
return { hook: shortName, success: false, error: `Hook '${shortName}' not found`, target };
|
|
7805
7877
|
}
|
|
7806
7878
|
if (target === "codewith") {
|
|
7807
7879
|
try {
|
|
7808
|
-
const fragment = buildCodewithTomlFragment(shortName, profile);
|
|
7880
|
+
const fragment = buildCodewithTomlFragment(shortName, profile, mementos);
|
|
7809
7881
|
if (codewithMode !== "write") {
|
|
7810
7882
|
return {
|
|
7811
7883
|
hook: shortName,
|
|
@@ -7862,7 +7934,7 @@ function installForTarget(name, scope, overwrite, target, profile, codewithMode
|
|
|
7862
7934
|
if (rewriteConflict) {
|
|
7863
7935
|
return { hook: shortName, success: false, error: `Refused: ${rewriteConflict}`, scope, target };
|
|
7864
7936
|
}
|
|
7865
|
-
registerHook(shortName, scope, target, profile);
|
|
7937
|
+
registerHook(shortName, scope, target, profile, mementos);
|
|
7866
7938
|
return {
|
|
7867
7939
|
hook: shortName,
|
|
7868
7940
|
success: true,
|
|
@@ -7881,7 +7953,12 @@ function installForTarget(name, scope, overwrite, target, profile, codewithMode
|
|
|
7881
7953
|
}
|
|
7882
7954
|
}
|
|
7883
7955
|
function installHook(name, options = {}) {
|
|
7884
|
-
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
|
+
}
|
|
7885
7962
|
if (!/^[\w-]+$/.test(name) || profile !== undefined && !/^[\w-]+$/.test(profile)) {
|
|
7886
7963
|
return { hook: name, success: false, error: "Hook and profile names must contain only letters, digits, underscores or hyphens.", target };
|
|
7887
7964
|
}
|
|
@@ -7899,7 +7976,7 @@ function installHook(name, options = {}) {
|
|
|
7899
7976
|
};
|
|
7900
7977
|
}
|
|
7901
7978
|
}
|
|
7902
|
-
const claudeResult = installForTarget(name, scope, overwrite, "claude", profile);
|
|
7979
|
+
const claudeResult = installForTarget(name, scope, overwrite, "claude", profile, "fragment", undefined, mementos);
|
|
7903
7980
|
if (!claudeResult.success) {
|
|
7904
7981
|
return {
|
|
7905
7982
|
...claudeResult,
|
|
@@ -7907,7 +7984,7 @@ function installHook(name, options = {}) {
|
|
|
7907
7984
|
target: "all"
|
|
7908
7985
|
};
|
|
7909
7986
|
}
|
|
7910
|
-
const codewithResult = installForTarget(name, scope, overwrite, "codewith", profile, codewithMode, codewithConfigPath);
|
|
7987
|
+
const codewithResult = installForTarget(name, scope, overwrite, "codewith", profile, codewithMode, codewithConfigPath, mementos);
|
|
7911
7988
|
if (!codewithResult.success) {
|
|
7912
7989
|
return {
|
|
7913
7990
|
...codewithResult,
|
|
@@ -7917,9 +7994,9 @@ function installHook(name, options = {}) {
|
|
|
7917
7994
|
}
|
|
7918
7995
|
return { ...claudeResult, target: "all" };
|
|
7919
7996
|
}
|
|
7920
|
-
return installForTarget(name, scope, overwrite, target, profile, codewithMode, codewithConfigPath);
|
|
7997
|
+
return installForTarget(name, scope, overwrite, target, profile, codewithMode, codewithConfigPath, mementos);
|
|
7921
7998
|
}
|
|
7922
|
-
function registerHook(name, scope = "global", target = "claude", profile) {
|
|
7999
|
+
function registerHook(name, scope = "global", target = "claude", profile, mementos) {
|
|
7923
8000
|
const meta = resolveHookMeta(name);
|
|
7924
8001
|
if (!meta)
|
|
7925
8002
|
return;
|
|
@@ -7937,8 +8014,9 @@ function registerHook(name, scope = "global", target = "claude", profile) {
|
|
|
7937
8014
|
const settings = readSettings(scope, target);
|
|
7938
8015
|
if (!settings.hooks)
|
|
7939
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;
|
|
7940
8018
|
removeHookFromAllEvents(settings, name);
|
|
7941
|
-
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);
|
|
7942
8020
|
for (const eventKey of uniqueEventKeys) {
|
|
7943
8021
|
if (!settings.hooks[eventKey])
|
|
7944
8022
|
settings.hooks[eventKey] = [];
|
|
@@ -7983,10 +8061,12 @@ function getRegisteredHooksForTarget(scope = "global", target = "claude") {
|
|
|
7983
8061
|
if (target === "codewith") {
|
|
7984
8062
|
const config = readCodewithConfig(scope);
|
|
7985
8063
|
const registered2 = [];
|
|
7986
|
-
const re =
|
|
8064
|
+
const re = /^\s*command\s*=\s*["']([^"'\n]+)["']\s*$/gm;
|
|
7987
8065
|
let match;
|
|
7988
8066
|
while (match = re.exec(config)) {
|
|
7989
|
-
|
|
8067
|
+
const name = registrationName(match[1]);
|
|
8068
|
+
if (name)
|
|
8069
|
+
registered2.push(name);
|
|
7990
8070
|
}
|
|
7991
8071
|
return [...new Set(registered2)];
|
|
7992
8072
|
}
|
|
@@ -7997,12 +8077,9 @@ function getRegisteredHooksForTarget(scope = "global", target = "claude") {
|
|
|
7997
8077
|
for (const eventKey of Object.keys(settings.hooks)) {
|
|
7998
8078
|
for (const entry of settings.hooks[eventKey]) {
|
|
7999
8079
|
for (const hook of entry.hooks || []) {
|
|
8000
|
-
const
|
|
8001
|
-
|
|
8002
|
-
|
|
8003
|
-
if (match) {
|
|
8004
|
-
registered.push(match[1]);
|
|
8005
|
-
}
|
|
8080
|
+
const name = registrationName(hook.command);
|
|
8081
|
+
if (name)
|
|
8082
|
+
registered.push(name);
|
|
8006
8083
|
}
|
|
8007
8084
|
}
|
|
8008
8085
|
}
|
|
@@ -8036,7 +8113,7 @@ function removeHookForTarget(name, scope, target) {
|
|
|
8036
8113
|
var JSON_SETTINGS_TARGETS = ["claude", "gemini", "codex"];
|
|
8037
8114
|
function removeCodewithHookEntry(configText, name) {
|
|
8038
8115
|
const sections = configText.split(/\n\s*\n/);
|
|
8039
|
-
const
|
|
8116
|
+
const commandRe = /^command\s*=\s*"([^"\n]+)"$/;
|
|
8040
8117
|
const entryHeaderRe = /^\[\[hooks\.([\w]+)\.hooks\]\]$/;
|
|
8041
8118
|
const eventHeaderRe = /^\[\[hooks\.([\w]+)\]\]$/;
|
|
8042
8119
|
const eventsWithEntries = new Set;
|
|
@@ -8049,7 +8126,7 @@ function removeCodewithHookEntry(configText, name) {
|
|
|
8049
8126
|
if (entryMatch) {
|
|
8050
8127
|
const event = entryMatch[1];
|
|
8051
8128
|
eventsWithEntries.add(event);
|
|
8052
|
-
if (lines.some((line) =>
|
|
8129
|
+
if (lines.some((line) => registrationName(commandRe.exec(line.trim())?.[1]) === name)) {
|
|
8053
8130
|
removed = true;
|
|
8054
8131
|
continue;
|
|
8055
8132
|
}
|
package/dist/lib/installer.d.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { type HookEvent, type HookMeta } from "./registry.js";
|
|
13
13
|
import { type StaleRegistration } from "./registration.js";
|
|
14
|
+
import { type MementosRegistration } from "./mementos-options.js";
|
|
14
15
|
export type Scope = "global" | "project";
|
|
15
16
|
export type Target = "claude" | "gemini" | "codewith" | "codex" | "all";
|
|
16
17
|
type SingleTarget = Exclude<Target, "all">;
|
|
@@ -29,6 +30,8 @@ export interface InstallResult {
|
|
|
29
30
|
configPath?: string;
|
|
30
31
|
}
|
|
31
32
|
export interface InstallOptions {
|
|
33
|
+
/** Explicit persistent opt-in for mementos-context; omitted updates retain its current choice. */
|
|
34
|
+
mementos?: MementosRegistration;
|
|
32
35
|
scope?: Scope;
|
|
33
36
|
overwrite?: boolean;
|
|
34
37
|
target?: Target;
|
|
@@ -47,7 +50,7 @@ export declare function getHookPath(name: string): string;
|
|
|
47
50
|
export declare function hookExists(name: string): boolean;
|
|
48
51
|
/** Whether a hook's event can be registered for the given target */
|
|
49
52
|
export declare function isEventSupported(internalEvent: HookEvent, target: SingleTarget): boolean;
|
|
50
|
-
export declare function buildCodewithTomlFragment(name: string, profile?: string): string;
|
|
53
|
+
export declare function buildCodewithTomlFragment(name: string, profile?: string, mementos?: MementosRegistration): string;
|
|
51
54
|
/**
|
|
52
55
|
* The one conflict class that BLOCKS an install: two PreToolUse hooks on
|
|
53
56
|
* overlapping matchers that both rewrite the tool input
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Persistent opt-in lives in the native hook command, never in a credential file. */
|
|
2
|
+
export interface MementosRegistration {
|
|
3
|
+
enabled: boolean;
|
|
4
|
+
secretRef?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface MementosFlags {
|
|
7
|
+
enableMementos?: boolean;
|
|
8
|
+
disableMementos?: boolean;
|
|
9
|
+
openrouterSecretRef?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function validateMementosRegistration(name: string, value?: MementosRegistration): void;
|
|
12
|
+
export declare function mementosRegistrationFromFlags(name: string, flags: MementosFlags): MementosRegistration | undefined;
|
|
13
|
+
export declare function mementosCommandSuffix(value?: MementosRegistration): string;
|
|
14
|
+
/** Preserve explicit activation across ordinary install --overwrite / update. */
|
|
15
|
+
export declare function retainedMementosRegistration(commands: string[]): MementosRegistration | undefined;
|
|
16
|
+
export declare function mementosRunEnvironment(env: NodeJS.ProcessEnv, value?: MementosRegistration): NodeJS.ProcessEnv;
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* an array of { matcher?, hooks: [...] } entries — so a composite event such
|
|
7
7
|
* as 'PreToolUse:Bash' must be split before lookup, never used as a key.
|
|
8
8
|
*/
|
|
9
|
+
export declare function registrationName(command: unknown): string | undefined;
|
|
9
10
|
/** A settings registration whose hook name cannot resolve. */
|
|
10
11
|
export interface StaleRegistration {
|
|
11
12
|
/** Settings file the registration lives in. */
|
|
@@ -22,8 +22,22 @@ retains the existing Claude/Codewith install group; select Codex explicitly.
|
|
|
22
22
|
Use `hooks list --registered --target codex` and
|
|
23
23
|
`hooks remove mementos-context --target codex` to inspect or remove registration.
|
|
24
24
|
|
|
25
|
-
The hook
|
|
26
|
-
|
|
25
|
+
The hook is off by default. To save a one-time opt-in in the native registration:
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
hooks install mementos-context --target codex --overwrite --enable-mementos
|
|
29
|
+
# Review the changed definition in Codex /hooks before trusting it.
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
New sessions now run the hook without shell exports. Its project is the session's
|
|
33
|
+
working directory, which must resolve to an existing Mementos project. Routine
|
|
34
|
+
JSON-target registration updates retain this choice. To save a persistent opt-out:
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
hooks install mementos-context --target codex --overwrite --disable-mementos
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
For a temporary opt-in instead, start a fresh Codex CLI from a shell with:
|
|
27
41
|
|
|
28
42
|
```sh
|
|
29
43
|
export HOOKS_MEMENTOS_ENABLED=1
|
|
@@ -45,6 +59,18 @@ mementos decisions enable --retrieval
|
|
|
45
59
|
export HOOKS_MEMENTOS_OPENROUTER_SECRET_REF=example/openrouter/key
|
|
46
60
|
```
|
|
47
61
|
|
|
62
|
+
To persist the reference with activation, replace that export with:
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
hooks install mementos-context --target codex --overwrite --enable-mementos \
|
|
66
|
+
--openrouter-secret-ref example/openrouter/key
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The native command stores only the reference. Existing hook definitions are
|
|
70
|
+
preserved, and native trust is still required after a command changes. Set
|
|
71
|
+
`HOOKS_MEMENTOS_ENABLED=0` to opt out for one session even when its registration
|
|
72
|
+
is enabled. A persistent `--disable-mementos` always disables execution.
|
|
73
|
+
|
|
48
74
|
The reference must name an existing key available through the optional Secrets
|
|
49
75
|
CLI. The hook invokes `secrets exec <reference> --as OPENROUTER_API_KEY -- mementos
|
|
50
76
|
prompt-context ...`; the key reaches only the Mementos consumer. Do not place key
|
package/package.json
CHANGED