@hasna/hooks 0.9.1 → 0.9.2
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 +197 -79
- package/bin/index.js +304 -144
- package/bin/serve.js +11 -0
- package/dist/index.js +204 -83
- package/dist/lib/codex-settings.d.ts +3 -0
- package/dist/lib/installer.d.ts +1 -1
- package/hooks/hook-mementos-context/README.md +80 -0
- package/hooks/hook-mementos-context/src/hook.ts +116 -0
- package/package.json +5 -4
package/bin/index.js
CHANGED
|
@@ -2246,6 +2246,17 @@ var init_registry = __esm(() => {
|
|
|
2246
2246
|
matcher: "",
|
|
2247
2247
|
tags: ["knowledge", "context", "codewith", "deterministic", "session-start", "prompt", "subagent"]
|
|
2248
2248
|
},
|
|
2249
|
+
{
|
|
2250
|
+
name: "mementos-context",
|
|
2251
|
+
displayName: "Mementos Prompt Context",
|
|
2252
|
+
description: "Retrieves scoped memories for a user prompt, with optional relevance judgments",
|
|
2253
|
+
version: "0.1.0",
|
|
2254
|
+
category: "Context Management",
|
|
2255
|
+
event: "UserPromptSubmit",
|
|
2256
|
+
matcher: "",
|
|
2257
|
+
timeoutSeconds: 12,
|
|
2258
|
+
tags: ["mementos", "memory", "prompt", "codex", "claude", "codewith", "opt-in"]
|
|
2259
|
+
},
|
|
2249
2260
|
{
|
|
2250
2261
|
name: "autoformat",
|
|
2251
2262
|
displayName: "Auto Format",
|
|
@@ -5232,6 +5243,78 @@ var init_store = __esm(() => {
|
|
|
5232
5243
|
};
|
|
5233
5244
|
});
|
|
5234
5245
|
|
|
5246
|
+
// src/lib/codex-settings.ts
|
|
5247
|
+
import { closeSync, existsSync as existsSync10, lstatSync, mkdirSync as mkdirSync4, openSync, readFileSync as readFileSync4, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
|
|
5248
|
+
import { dirname as dirname3 } from "path";
|
|
5249
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
5250
|
+
function current(path) {
|
|
5251
|
+
try {
|
|
5252
|
+
const stat = lstatSync(path);
|
|
5253
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 1048576)
|
|
5254
|
+
throw new Error("invalid_file");
|
|
5255
|
+
return readFileSync4(path, "utf8");
|
|
5256
|
+
} catch (error) {
|
|
5257
|
+
if (error.code === "ENOENT")
|
|
5258
|
+
return null;
|
|
5259
|
+
throw new Error("Cannot safely read Codex hooks.json; no settings were changed.");
|
|
5260
|
+
}
|
|
5261
|
+
}
|
|
5262
|
+
function readCodexSettings(path) {
|
|
5263
|
+
const raw = current(path);
|
|
5264
|
+
let settings;
|
|
5265
|
+
try {
|
|
5266
|
+
settings = raw === null ? {} : JSON.parse(raw);
|
|
5267
|
+
if (!settings || typeof settings !== "object" || Array.isArray(settings))
|
|
5268
|
+
throw new Error;
|
|
5269
|
+
if (settings.hooks !== undefined) {
|
|
5270
|
+
if (!settings.hooks || typeof settings.hooks !== "object" || Array.isArray(settings.hooks))
|
|
5271
|
+
throw new Error;
|
|
5272
|
+
for (const entries of Object.values(settings.hooks)) {
|
|
5273
|
+
if (!Array.isArray(entries) || entries.some((entry) => !entry || !Array.isArray(entry.hooks) || entry.hooks.some((hook) => !hook || typeof hook !== "object")))
|
|
5274
|
+
throw new Error;
|
|
5275
|
+
}
|
|
5276
|
+
}
|
|
5277
|
+
} catch {
|
|
5278
|
+
throw new Error("Invalid Codex hooks.json; repair it before installing or removing hooks.");
|
|
5279
|
+
}
|
|
5280
|
+
snapshots.set(settings, { path, raw });
|
|
5281
|
+
return settings;
|
|
5282
|
+
}
|
|
5283
|
+
function writeCodexSettings(path, settings) {
|
|
5284
|
+
const snapshot = snapshots.get(settings);
|
|
5285
|
+
if (!snapshot || snapshot.path !== path)
|
|
5286
|
+
throw new Error("Codex settings require a complete current read before writing.");
|
|
5287
|
+
mkdirSync4(dirname3(path), { recursive: true });
|
|
5288
|
+
const lock = `${path}.hooks-write.lock`;
|
|
5289
|
+
let fd;
|
|
5290
|
+
try {
|
|
5291
|
+
fd = openSync(lock, "wx", 384);
|
|
5292
|
+
} catch {
|
|
5293
|
+
throw new Error("Codex settings are being edited; retry after the active writer finishes.");
|
|
5294
|
+
}
|
|
5295
|
+
const temp = `${path}.${randomUUID3()}.tmp`;
|
|
5296
|
+
try {
|
|
5297
|
+
if (current(path) !== snapshot.raw)
|
|
5298
|
+
throw new Error("Codex settings changed since reading; re-read before retrying.");
|
|
5299
|
+
const next = JSON.stringify(settings, null, 2) + `
|
|
5300
|
+
`;
|
|
5301
|
+
writeFileSync3(temp, next, { flag: "wx", mode: 384 });
|
|
5302
|
+
if (current(path) !== snapshot.raw)
|
|
5303
|
+
throw new Error("Codex settings changed since reading; re-read before retrying.");
|
|
5304
|
+
renameSync2(temp, path);
|
|
5305
|
+
snapshots.set(settings, { path, raw: next });
|
|
5306
|
+
} finally {
|
|
5307
|
+
if (existsSync10(temp))
|
|
5308
|
+
unlinkSync(temp);
|
|
5309
|
+
closeSync(fd);
|
|
5310
|
+
unlinkSync(lock);
|
|
5311
|
+
}
|
|
5312
|
+
}
|
|
5313
|
+
var snapshots;
|
|
5314
|
+
var init_codex_settings = __esm(() => {
|
|
5315
|
+
snapshots = new WeakMap;
|
|
5316
|
+
});
|
|
5317
|
+
|
|
5235
5318
|
// src/lib/installer.ts
|
|
5236
5319
|
var exports_installer = {};
|
|
5237
5320
|
__export(exports_installer, {
|
|
@@ -5252,8 +5335,8 @@ __export(exports_installer, {
|
|
|
5252
5335
|
detectRewriteConflict: () => detectRewriteConflict,
|
|
5253
5336
|
buildCodewithTomlFragment: () => buildCodewithTomlFragment
|
|
5254
5337
|
});
|
|
5255
|
-
import { existsSync as
|
|
5256
|
-
import { join as join10, dirname as
|
|
5338
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync5, readdirSync as readdirSync3, rmdirSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
5339
|
+
import { join as join10, dirname as dirname4 } from "path";
|
|
5257
5340
|
import { homedir as homedir4 } from "os";
|
|
5258
5341
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5259
5342
|
function normalizeHookName(name) {
|
|
@@ -5263,12 +5346,19 @@ function shortHookName(name) {
|
|
|
5263
5346
|
return name.startsWith("hook-") ? name.slice("hook-".length) : name;
|
|
5264
5347
|
}
|
|
5265
5348
|
function removeHookEntriesByName(entries, hookName) {
|
|
5266
|
-
return entries.
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5349
|
+
return entries.flatMap((entry) => {
|
|
5350
|
+
if (!Array.isArray(entry.hooks))
|
|
5351
|
+
return [entry];
|
|
5352
|
+
const kept = entry.hooks.filter((h) => {
|
|
5353
|
+
const match = h.command?.match(/^hooks run ([\w-]+)/);
|
|
5354
|
+
return !(match && match[1] === hookName);
|
|
5355
|
+
});
|
|
5356
|
+
return kept.length === entry.hooks.length ? [entry] : kept.length ? [{ ...entry, hooks: kept }] : [];
|
|
5357
|
+
});
|
|
5270
5358
|
}
|
|
5271
5359
|
function getTargetSettingsDir(target) {
|
|
5360
|
+
if (target === "codex")
|
|
5361
|
+
return ".codex";
|
|
5272
5362
|
if (target === "codewith")
|
|
5273
5363
|
return ".codewith";
|
|
5274
5364
|
if (target === "gemini")
|
|
@@ -5276,6 +5366,8 @@ function getTargetSettingsDir(target) {
|
|
|
5276
5366
|
return ".claude";
|
|
5277
5367
|
}
|
|
5278
5368
|
function getGlobalSettingsPathOverride(target) {
|
|
5369
|
+
if (target === "codex")
|
|
5370
|
+
return process.env.HASNA_HOOKS_CODEX_SETTINGS_PATH;
|
|
5279
5371
|
if (target === "claude")
|
|
5280
5372
|
return process.env.HASNA_HOOKS_CLAUDE_SETTINGS_PATH;
|
|
5281
5373
|
if (target === "gemini")
|
|
@@ -5292,6 +5384,8 @@ function getSettingsPath(scope = "global", target = "claude", codewithConfigPath
|
|
|
5292
5384
|
if (globalOverride)
|
|
5293
5385
|
return globalOverride;
|
|
5294
5386
|
const dir = getTargetSettingsDir(target);
|
|
5387
|
+
if (target === "codex")
|
|
5388
|
+
return join10(scope === "project" ? process.cwd() : homedir4(), dir, "hooks.json");
|
|
5295
5389
|
if (scope === "project") {
|
|
5296
5390
|
return target === "codewith" ? join10(process.cwd(), dir, "config.toml") : join10(process.cwd(), dir, "settings.json");
|
|
5297
5391
|
}
|
|
@@ -5305,13 +5399,15 @@ function getHookPath(name) {
|
|
|
5305
5399
|
return join10(HOOKS_DIR, normalizeHookName(shortName));
|
|
5306
5400
|
}
|
|
5307
5401
|
function hookExists(name) {
|
|
5308
|
-
return
|
|
5402
|
+
return existsSync11(getHookPath(name));
|
|
5309
5403
|
}
|
|
5310
5404
|
function readSettings(scope = "global", target = "claude") {
|
|
5311
5405
|
const path = getSettingsPath(scope, target);
|
|
5406
|
+
if (target === "codex")
|
|
5407
|
+
return readCodexSettings(path);
|
|
5312
5408
|
try {
|
|
5313
|
-
if (
|
|
5314
|
-
return JSON.parse(
|
|
5409
|
+
if (existsSync11(path)) {
|
|
5410
|
+
return JSON.parse(readFileSync5(path, "utf-8"));
|
|
5315
5411
|
}
|
|
5316
5412
|
} catch (error) {
|
|
5317
5413
|
console.warn(`[hooks] Failed to read settings at ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -5320,11 +5416,13 @@ function readSettings(scope = "global", target = "claude") {
|
|
|
5320
5416
|
}
|
|
5321
5417
|
function writeSettings(settings, scope = "global", target = "claude") {
|
|
5322
5418
|
const path = getSettingsPath(scope, target);
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
|
|
5419
|
+
if (target === "codex")
|
|
5420
|
+
return writeCodexSettings(path, settings);
|
|
5421
|
+
const dir = dirname4(path);
|
|
5422
|
+
if (!existsSync11(dir)) {
|
|
5423
|
+
mkdirSync5(dir, { recursive: true });
|
|
5326
5424
|
}
|
|
5327
|
-
|
|
5425
|
+
writeFileSync4(path, JSON.stringify(settings, null, 2) + `
|
|
5328
5426
|
`);
|
|
5329
5427
|
}
|
|
5330
5428
|
function getTargetEventName(internalEvent, target) {
|
|
@@ -5347,6 +5445,8 @@ function codewithMatcher(matcher) {
|
|
|
5347
5445
|
}
|
|
5348
5446
|
function codewithTimeout(name) {
|
|
5349
5447
|
switch (name) {
|
|
5448
|
+
case "mementos-context":
|
|
5449
|
+
return 12;
|
|
5350
5450
|
case "knowledge-context":
|
|
5351
5451
|
return 6;
|
|
5352
5452
|
case "session-start":
|
|
@@ -5413,21 +5513,21 @@ function buildCodewithTomlFragment(name, profile) {
|
|
|
5413
5513
|
function readCodewithConfig(scope, configPath) {
|
|
5414
5514
|
const path = getSettingsPath(scope, "codewith", configPath);
|
|
5415
5515
|
try {
|
|
5416
|
-
return
|
|
5516
|
+
return existsSync11(path) ? readFileSync5(path, "utf-8") : "";
|
|
5417
5517
|
} catch {
|
|
5418
5518
|
return "";
|
|
5419
5519
|
}
|
|
5420
5520
|
}
|
|
5421
5521
|
function appendCodewithFragment(fragment, scope, configPath) {
|
|
5422
5522
|
const path = getSettingsPath(scope, "codewith", configPath);
|
|
5423
|
-
const dir =
|
|
5424
|
-
if (!
|
|
5425
|
-
|
|
5426
|
-
const existing =
|
|
5523
|
+
const dir = dirname4(path);
|
|
5524
|
+
if (!existsSync11(dir))
|
|
5525
|
+
mkdirSync5(dir, { recursive: true });
|
|
5526
|
+
const existing = existsSync11(path) ? readFileSync5(path, "utf-8") : "";
|
|
5427
5527
|
const sep2 = existing.trim() ? `
|
|
5428
5528
|
|
|
5429
5529
|
` : "";
|
|
5430
|
-
|
|
5530
|
+
writeFileSync4(path, `${existing.replace(/\s*$/, "")}${sep2}${fragment}`);
|
|
5431
5531
|
return path;
|
|
5432
5532
|
}
|
|
5433
5533
|
function detectConflict(name, scope, target) {
|
|
@@ -5524,18 +5624,25 @@ function installForTarget(name, scope, overwrite, target, profile, codewithMode
|
|
|
5524
5624
|
};
|
|
5525
5625
|
}
|
|
5526
5626
|
}
|
|
5527
|
-
const registered = getRegisteredHooksForTarget(scope, target);
|
|
5528
|
-
if (registered.includes(shortName) && !overwrite) {
|
|
5529
|
-
return { hook: shortName, success: false, error: "Already installed. Use --overwrite to replace.", scope, target };
|
|
5530
|
-
}
|
|
5531
|
-
const conflict = detectConflict(shortName, scope, target);
|
|
5532
|
-
const rewriteConflict = detectRewriteConflict(shortName, scope, target);
|
|
5533
|
-
if (rewriteConflict) {
|
|
5534
|
-
return { hook: shortName, success: false, error: `Refused: ${rewriteConflict}`, scope, target };
|
|
5535
|
-
}
|
|
5536
5627
|
try {
|
|
5628
|
+
const registered = getRegisteredHooksForTarget(scope, target);
|
|
5629
|
+
if (registered.includes(shortName) && !overwrite) {
|
|
5630
|
+
return { hook: shortName, success: false, error: "Already installed. Use --overwrite to replace.", scope, target };
|
|
5631
|
+
}
|
|
5632
|
+
const conflict = detectConflict(shortName, scope, target);
|
|
5633
|
+
const rewriteConflict = detectRewriteConflict(shortName, scope, target);
|
|
5634
|
+
if (rewriteConflict) {
|
|
5635
|
+
return { hook: shortName, success: false, error: `Refused: ${rewriteConflict}`, scope, target };
|
|
5636
|
+
}
|
|
5537
5637
|
registerHook(shortName, scope, target, profile);
|
|
5538
|
-
return {
|
|
5638
|
+
return {
|
|
5639
|
+
hook: shortName,
|
|
5640
|
+
success: true,
|
|
5641
|
+
scope,
|
|
5642
|
+
target,
|
|
5643
|
+
...conflict ? { conflict } : {},
|
|
5644
|
+
...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." } : {}
|
|
5645
|
+
};
|
|
5539
5646
|
} catch (error) {
|
|
5540
5647
|
return {
|
|
5541
5648
|
hook: shortName,
|
|
@@ -5547,6 +5654,9 @@ function installForTarget(name, scope, overwrite, target, profile, codewithMode
|
|
|
5547
5654
|
}
|
|
5548
5655
|
function installHook(name, options = {}) {
|
|
5549
5656
|
const { scope = "global", overwrite = false, target = "claude", profile, codewithMode = "fragment", codewithConfigPath } = options;
|
|
5657
|
+
if (!/^[\w-]+$/.test(name) || profile !== undefined && !/^[\w-]+$/.test(profile)) {
|
|
5658
|
+
return { hook: name, success: false, error: "Hook and profile names must contain only letters, digits, underscores or hyphens.", target };
|
|
5659
|
+
}
|
|
5550
5660
|
if (target === "all") {
|
|
5551
5661
|
const shortName = shortHookName(name);
|
|
5552
5662
|
const meta = resolveHookMeta(shortName);
|
|
@@ -5678,7 +5788,8 @@ function removeHook(name, scope = "global", target = "claude") {
|
|
|
5678
5788
|
if (target === "all") {
|
|
5679
5789
|
const claudeRemoved = removeHookForTarget(shortName, scope, "claude");
|
|
5680
5790
|
const geminiRemoved = removeHookForTarget(shortName, scope, "gemini");
|
|
5681
|
-
|
|
5791
|
+
const codexRemoved = removeHookForTarget(shortName, scope, "codex");
|
|
5792
|
+
return claudeRemoved || geminiRemoved || codexRemoved;
|
|
5682
5793
|
}
|
|
5683
5794
|
if (target === "codewith") {
|
|
5684
5795
|
return false;
|
|
@@ -5695,9 +5806,9 @@ function removeHookForTarget(name, scope, target) {
|
|
|
5695
5806
|
}
|
|
5696
5807
|
function readSettingsAt(path) {
|
|
5697
5808
|
try {
|
|
5698
|
-
if (!
|
|
5809
|
+
if (!existsSync11(path))
|
|
5699
5810
|
return;
|
|
5700
|
-
const raw =
|
|
5811
|
+
const raw = readFileSync5(path, "utf-8");
|
|
5701
5812
|
return { raw, settings: JSON.parse(raw) };
|
|
5702
5813
|
} catch {
|
|
5703
5814
|
return;
|
|
@@ -5730,7 +5841,9 @@ function pruneStaleRegistrations(scope = "global") {
|
|
|
5730
5841
|
commands.add(finding.command);
|
|
5731
5842
|
staleCommandsByEvent.set(finding.event, commands);
|
|
5732
5843
|
}
|
|
5733
|
-
const settings = parsed.settings;
|
|
5844
|
+
const settings = target === "codex" ? readCodexSettings(path) : parsed.settings;
|
|
5845
|
+
if (target === "codex" && readFileSync5(path, "utf8") !== parsed.raw)
|
|
5846
|
+
throw new Error("Codex settings changed before pruning; retry.");
|
|
5734
5847
|
for (const [eventKey, commands] of staleCommandsByEvent) {
|
|
5735
5848
|
const entries = settings.hooks?.[eventKey];
|
|
5736
5849
|
if (!Array.isArray(entries))
|
|
@@ -5755,7 +5868,7 @@ function pruneStaleRegistrations(scope = "global") {
|
|
|
5755
5868
|
if (settings.hooks && Object.keys(settings.hooks).length === 0)
|
|
5756
5869
|
delete settings.hooks;
|
|
5757
5870
|
const backupPath = `${path}.bak`;
|
|
5758
|
-
|
|
5871
|
+
writeFileSync4(backupPath, parsed.raw);
|
|
5759
5872
|
writeSettings(settings, scope, target);
|
|
5760
5873
|
results.push({ file: path, removed: stale, backupPath });
|
|
5761
5874
|
}
|
|
@@ -5817,10 +5930,10 @@ function codewithHasHookEntryInText(configText, name) {
|
|
|
5817
5930
|
}
|
|
5818
5931
|
function codewithHasHookEntry(name, scope = "global") {
|
|
5819
5932
|
const path = getSettingsPath(scope, "codewith");
|
|
5820
|
-
if (!
|
|
5933
|
+
if (!existsSync11(path))
|
|
5821
5934
|
return false;
|
|
5822
5935
|
try {
|
|
5823
|
-
return codewithHasHookEntryInText(
|
|
5936
|
+
return codewithHasHookEntryInText(readFileSync5(path, "utf-8"), name);
|
|
5824
5937
|
} catch {
|
|
5825
5938
|
return false;
|
|
5826
5939
|
}
|
|
@@ -5834,7 +5947,7 @@ function removeStoreDirFailClosed(dir) {
|
|
|
5834
5947
|
}
|
|
5835
5948
|
for (const entry of entries) {
|
|
5836
5949
|
if (!entry.isDirectory()) {
|
|
5837
|
-
|
|
5950
|
+
unlinkSync2(join10(dir, entry.name));
|
|
5838
5951
|
}
|
|
5839
5952
|
}
|
|
5840
5953
|
rmdirSync(dir);
|
|
@@ -5844,14 +5957,14 @@ function uninstallHook(name, scope = "global", target = "claude") {
|
|
|
5844
5957
|
const custom = readCustomManifest(shortName);
|
|
5845
5958
|
const bundledMeta = custom ? undefined : getHook(shortName);
|
|
5846
5959
|
const existsInTarget = (t) => getRegisteredHooksForTarget("global", t).includes(shortName) || getRegisteredHooksForTarget("project", t).includes(shortName);
|
|
5847
|
-
const registeredClaudeOrGemini = target === "all" ?
|
|
5960
|
+
const registeredClaudeOrGemini = target === "all" ? JSON_SETTINGS_TARGETS.some(existsInTarget) : false;
|
|
5848
5961
|
const registeredGlobal = registeredClaudeOrGemini || getRegisteredHooksForTarget("global", target === "all" ? "claude" : target).includes(shortName);
|
|
5849
5962
|
const registeredProject = registeredClaudeOrGemini || getRegisteredHooksForTarget("project", target === "all" ? "claude" : target).includes(shortName);
|
|
5850
5963
|
const codewithOnly = (target === "codewith" || target === "all") && codewithHasHookEntry(shortName, scope);
|
|
5851
5964
|
if (!custom && !bundledMeta && !registeredGlobal && !registeredProject && !codewithOnly) {
|
|
5852
5965
|
return { name: shortName, removed: false, source: null, settingsScopes: [], storeDirRemoved: false, pinRemoved: false, dbRecordRemoved: false, registrationsRemaining: [], error: `Hook '${shortName}' not found` };
|
|
5853
5966
|
}
|
|
5854
|
-
const writableTargets = target === "all" ?
|
|
5967
|
+
const writableTargets = target === "all" ? JSON_SETTINGS_TARGETS : target === "codewith" ? [] : [target];
|
|
5855
5968
|
const scopes = [];
|
|
5856
5969
|
for (const s of ["global", "project"]) {
|
|
5857
5970
|
for (const t of writableTargets) {
|
|
@@ -5865,11 +5978,11 @@ function uninstallHook(name, scope = "global", target = "claude") {
|
|
|
5865
5978
|
const registrationsRemaining = [];
|
|
5866
5979
|
if (target === "codewith" || target === "all") {
|
|
5867
5980
|
const codewithPath = getSettingsPath(scope, "codewith");
|
|
5868
|
-
if (
|
|
5869
|
-
const before =
|
|
5981
|
+
if (existsSync11(codewithPath)) {
|
|
5982
|
+
const before = readFileSync5(codewithPath, "utf-8");
|
|
5870
5983
|
const after = removeCodewithHookEntry(before, shortName);
|
|
5871
5984
|
if (after.removed) {
|
|
5872
|
-
|
|
5985
|
+
writeFileSync4(codewithPath, after.text, "utf-8");
|
|
5873
5986
|
}
|
|
5874
5987
|
if (codewithHasHookEntryInText(after.removed ? after.text : before, shortName)) {
|
|
5875
5988
|
registrationsRemaining.push("codewith");
|
|
@@ -5912,8 +6025,9 @@ var init_installer = __esm(() => {
|
|
|
5912
6025
|
init_manifest();
|
|
5913
6026
|
init_registration();
|
|
5914
6027
|
init_store();
|
|
5915
|
-
|
|
5916
|
-
|
|
6028
|
+
init_codex_settings();
|
|
6029
|
+
__dirname2 = dirname4(fileURLToPath2(import.meta.url));
|
|
6030
|
+
HOOKS_DIR = existsSync11(join10(__dirname2, "..", "..", "hooks", "hook-gitguard")) ? join10(__dirname2, "..", "..", "hooks") : join10(__dirname2, "..", "hooks");
|
|
5917
6031
|
EVENT_MAP = {
|
|
5918
6032
|
claude: {
|
|
5919
6033
|
PreToolUse: "PreToolUse",
|
|
@@ -5922,7 +6036,7 @@ var init_installer = __esm(() => {
|
|
|
5922
6036
|
Notification: "Notification",
|
|
5923
6037
|
SessionStart: "SessionStart",
|
|
5924
6038
|
SessionEnd: "SessionEnd",
|
|
5925
|
-
UserPromptSubmit:
|
|
6039
|
+
UserPromptSubmit: "UserPromptSubmit",
|
|
5926
6040
|
SubagentStart: null
|
|
5927
6041
|
},
|
|
5928
6042
|
gemini: {
|
|
@@ -5944,10 +6058,20 @@ var init_installer = __esm(() => {
|
|
|
5944
6058
|
SessionEnd: null,
|
|
5945
6059
|
UserPromptSubmit: "UserPromptSubmit",
|
|
5946
6060
|
SubagentStart: "SubagentStart"
|
|
6061
|
+
},
|
|
6062
|
+
codex: {
|
|
6063
|
+
PreToolUse: "PreToolUse",
|
|
6064
|
+
PostToolUse: "PostToolUse",
|
|
6065
|
+
Stop: "Stop",
|
|
6066
|
+
Notification: null,
|
|
6067
|
+
SessionStart: "SessionStart",
|
|
6068
|
+
SessionEnd: "SessionEnd",
|
|
6069
|
+
UserPromptSubmit: "UserPromptSubmit",
|
|
6070
|
+
SubagentStart: "SubagentStart"
|
|
5947
6071
|
}
|
|
5948
6072
|
};
|
|
5949
6073
|
getInstalledHooks = getRegisteredHooks;
|
|
5950
|
-
JSON_SETTINGS_TARGETS = ["claude", "gemini"];
|
|
6074
|
+
JSON_SETTINGS_TARGETS = ["claude", "gemini", "codex"];
|
|
5951
6075
|
});
|
|
5952
6076
|
|
|
5953
6077
|
// src/lib/redact.ts
|
|
@@ -6016,21 +6140,21 @@ var init_redact = __esm(() => {
|
|
|
6016
6140
|
});
|
|
6017
6141
|
|
|
6018
6142
|
// src/lib/profiles.ts
|
|
6019
|
-
import { existsSync as
|
|
6143
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5, readdirSync as readdirSync4, rmSync, cpSync as cpSync2 } from "fs";
|
|
6020
6144
|
import { join as join11 } from "path";
|
|
6021
6145
|
function getProfilesDir() {
|
|
6022
6146
|
const newDir = join11(getEffectiveDataRoot(), "profiles");
|
|
6023
6147
|
const oldDir = join11(getHomeDir(), ".hooks", "profiles");
|
|
6024
|
-
if (!
|
|
6025
|
-
|
|
6148
|
+
if (!existsSync12(newDir) && existsSync12(oldDir)) {
|
|
6149
|
+
mkdirSync6(newDir, { recursive: true });
|
|
6026
6150
|
cpSync2(oldDir, newDir, { recursive: true });
|
|
6027
6151
|
}
|
|
6028
6152
|
return newDir;
|
|
6029
6153
|
}
|
|
6030
6154
|
function ensureProfilesDir() {
|
|
6031
6155
|
const dir = getProfilesDir();
|
|
6032
|
-
if (!
|
|
6033
|
-
|
|
6156
|
+
if (!existsSync12(dir)) {
|
|
6157
|
+
mkdirSync6(dir, { recursive: true });
|
|
6034
6158
|
}
|
|
6035
6159
|
}
|
|
6036
6160
|
function profilePath(id) {
|
|
@@ -6053,30 +6177,30 @@ function createProfile(input) {
|
|
|
6053
6177
|
if (input.name) {
|
|
6054
6178
|
profile.name = input.name;
|
|
6055
6179
|
}
|
|
6056
|
-
|
|
6180
|
+
writeFileSync5(profilePath(id), JSON.stringify(profile, null, 2) + `
|
|
6057
6181
|
`);
|
|
6058
6182
|
return profile;
|
|
6059
6183
|
}
|
|
6060
6184
|
function getProfile(id) {
|
|
6061
6185
|
const path = profilePath(id);
|
|
6062
6186
|
try {
|
|
6063
|
-
if (!
|
|
6187
|
+
if (!existsSync12(path))
|
|
6064
6188
|
return null;
|
|
6065
|
-
return JSON.parse(
|
|
6189
|
+
return JSON.parse(readFileSync6(path, "utf-8"));
|
|
6066
6190
|
} catch {
|
|
6067
6191
|
return null;
|
|
6068
6192
|
}
|
|
6069
6193
|
}
|
|
6070
6194
|
function listProfiles() {
|
|
6071
6195
|
const dir = getProfilesDir();
|
|
6072
|
-
if (!
|
|
6196
|
+
if (!existsSync12(dir))
|
|
6073
6197
|
return [];
|
|
6074
6198
|
try {
|
|
6075
6199
|
const files = readdirSync4(dir).filter((f) => f.endsWith(".json"));
|
|
6076
6200
|
const profiles = [];
|
|
6077
6201
|
for (const file of files) {
|
|
6078
6202
|
try {
|
|
6079
|
-
const content =
|
|
6203
|
+
const content = readFileSync6(join11(dir, file), "utf-8");
|
|
6080
6204
|
profiles.push(JSON.parse(content));
|
|
6081
6205
|
} catch {}
|
|
6082
6206
|
}
|
|
@@ -6090,7 +6214,7 @@ function touchProfile(id) {
|
|
|
6090
6214
|
if (!profile)
|
|
6091
6215
|
return;
|
|
6092
6216
|
profile.last_seen_at = new Date().toISOString();
|
|
6093
|
-
|
|
6217
|
+
writeFileSync5(profilePath(id), JSON.stringify(profile, null, 2) + `
|
|
6094
6218
|
`);
|
|
6095
6219
|
}
|
|
6096
6220
|
function exportProfiles() {
|
|
@@ -6106,11 +6230,11 @@ function importProfiles(profiles) {
|
|
|
6106
6230
|
continue;
|
|
6107
6231
|
}
|
|
6108
6232
|
const path = profilePath(profile.agent_id);
|
|
6109
|
-
if (
|
|
6233
|
+
if (existsSync12(path)) {
|
|
6110
6234
|
skipped++;
|
|
6111
6235
|
continue;
|
|
6112
6236
|
}
|
|
6113
|
-
|
|
6237
|
+
writeFileSync5(path, JSON.stringify(profile, null, 2) + `
|
|
6114
6238
|
`);
|
|
6115
6239
|
imported++;
|
|
6116
6240
|
}
|
|
@@ -6123,7 +6247,7 @@ var init_profiles = __esm(() => {
|
|
|
6123
6247
|
// ../../node_modules/.bun/@hasna+contracts@1.0.2+e0db24ae03127387/node_modules/@hasna/contracts/dist/client/transport.js
|
|
6124
6248
|
import { isIP as isIP2 } from "net";
|
|
6125
6249
|
import { spawnSync } from "child_process";
|
|
6126
|
-
import { closeSync, fstatSync, openSync, readFileSync as
|
|
6250
|
+
import { closeSync as closeSync2, fstatSync, openSync as openSync2, readFileSync as readFileSync7 } from "fs";
|
|
6127
6251
|
import { O_NOFOLLOW, O_NONBLOCK, O_RDONLY } from "constants";
|
|
6128
6252
|
import { createRequire as createRequire2 } from "module";
|
|
6129
6253
|
import { hostname as osHostname } from "os";
|
|
@@ -6227,7 +6351,7 @@ function readAppConfigFile(path) {
|
|
|
6227
6351
|
};
|
|
6228
6352
|
let fd = -1;
|
|
6229
6353
|
try {
|
|
6230
|
-
fd =
|
|
6354
|
+
fd = openSync2(path, O_RDONLY | O_NOFOLLOW | O_NONBLOCK);
|
|
6231
6355
|
} catch (error) {
|
|
6232
6356
|
const code = error.code;
|
|
6233
6357
|
if (code === "ENOENT" || code === "ENOTDIR")
|
|
@@ -6248,7 +6372,7 @@ function readAppConfigFile(path) {
|
|
|
6248
6372
|
unsafe("the file is not owned by the current user");
|
|
6249
6373
|
if (before.size > MAX_CREDENTIAL_FILE_BYTES2)
|
|
6250
6374
|
unsafe("the file exceeds the size limit");
|
|
6251
|
-
const bytes =
|
|
6375
|
+
const bytes = readFileSync7(fd);
|
|
6252
6376
|
const after = fstatSync(fd);
|
|
6253
6377
|
if (!configFileReadsCoherent(before, after)) {
|
|
6254
6378
|
unsafe("the file changed while being read");
|
|
@@ -6256,7 +6380,7 @@ function readAppConfigFile(path) {
|
|
|
6256
6380
|
return parseEnvFile(bytes.toString("utf8"));
|
|
6257
6381
|
} finally {
|
|
6258
6382
|
if (fd !== -1)
|
|
6259
|
-
|
|
6383
|
+
closeSync2(fd);
|
|
6260
6384
|
}
|
|
6261
6385
|
}
|
|
6262
6386
|
function readCredentialFile(path, apiKeyKeys) {
|
|
@@ -7124,8 +7248,8 @@ var init_db_writer = __esm(() => {
|
|
|
7124
7248
|
});
|
|
7125
7249
|
|
|
7126
7250
|
// src/lib/hook-env.ts
|
|
7127
|
-
import { dirname as
|
|
7128
|
-
import { existsSync as
|
|
7251
|
+
import { dirname as dirname5, isAbsolute as isAbsolute3 } from "path";
|
|
7252
|
+
import { existsSync as existsSync13, realpathSync as realpathSync2, statSync } from "fs";
|
|
7129
7253
|
import { tmpdir } from "os";
|
|
7130
7254
|
function isDeniedEnvName(name) {
|
|
7131
7255
|
const upper = name.toUpperCase();
|
|
@@ -7173,9 +7297,9 @@ function isUnsafePathEntry(entry, home) {
|
|
|
7173
7297
|
}
|
|
7174
7298
|
function sanitizeHookPath(pathValue, home) {
|
|
7175
7299
|
const baseline = PATH_BASELINE.slice();
|
|
7176
|
-
if (process.platform === "darwin" &&
|
|
7300
|
+
if (process.platform === "darwin" && existsSync13("/opt/homebrew/bin"))
|
|
7177
7301
|
baseline.push("/opt/homebrew/bin");
|
|
7178
|
-
const bunDir =
|
|
7302
|
+
const bunDir = dirname5(process.execPath);
|
|
7179
7303
|
const seen = new Set;
|
|
7180
7304
|
const out = [];
|
|
7181
7305
|
for (const dir of baseline) {
|
|
@@ -7299,8 +7423,8 @@ __export(exports_run, {
|
|
|
7299
7423
|
HookTimeoutError: () => HookTimeoutError
|
|
7300
7424
|
});
|
|
7301
7425
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
7302
|
-
import { rmSync as rmSync2, writeFileSync as
|
|
7303
|
-
import { basename as basename2, dirname as
|
|
7426
|
+
import { rmSync as rmSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
7427
|
+
import { basename as basename2, dirname as dirname6, isAbsolute as isAbsolute4, join as join13 } from "path";
|
|
7304
7428
|
function extensionOf(scriptPath) {
|
|
7305
7429
|
const base = basename2(scriptPath);
|
|
7306
7430
|
const dot = base.lastIndexOf(".");
|
|
@@ -7369,7 +7493,7 @@ async function readPipeWithDeadline(stream, deadlineMs) {
|
|
|
7369
7493
|
return chunks.join("");
|
|
7370
7494
|
}
|
|
7371
7495
|
async function executeVerifiedScript(options) {
|
|
7372
|
-
const scriptDir =
|
|
7496
|
+
const scriptDir = dirname6(options.scriptPath);
|
|
7373
7497
|
const interpreter = interpreterFor(options.name, options.scriptPath, options.content);
|
|
7374
7498
|
const tempPath = join13(scriptDir, `.hooks-verified-${randomBytes2(12).toString("hex")}.${interpreter.tempExt}`);
|
|
7375
7499
|
let proc = null;
|
|
@@ -7379,7 +7503,7 @@ async function executeVerifiedScript(options) {
|
|
|
7379
7503
|
killGroup(proc, { leaderAlive: proc !== null && proc.exitCode === null });
|
|
7380
7504
|
}, options.timeout) : null;
|
|
7381
7505
|
try {
|
|
7382
|
-
|
|
7506
|
+
writeFileSync6(tempPath, options.content, { flag: "wx", mode: 384 });
|
|
7383
7507
|
proc = Bun.spawn([...interpreter.command, isAbsolute4(tempPath) ? tempPath : `./${tempPath}`, ...options.args ?? []], {
|
|
7384
7508
|
stdin: new Response(options.stdin),
|
|
7385
7509
|
stdout: "pipe",
|
|
@@ -7462,15 +7586,15 @@ __export(exports_custom_install, {
|
|
|
7462
7586
|
installCustomSource: () => installCustomSource,
|
|
7463
7587
|
ensureCustomDir: () => ensureCustomDir
|
|
7464
7588
|
});
|
|
7465
|
-
import { existsSync as
|
|
7589
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync7, mkdtempSync, readFileSync as readFileSync8, rmSync as rmSync3 } from "fs";
|
|
7466
7590
|
import { join as join14 } from "path";
|
|
7467
7591
|
import { tmpdir as tmpdir2 } from "os";
|
|
7468
7592
|
function manifestAt(dir) {
|
|
7469
7593
|
const path = join14(dir, "manifest.json");
|
|
7470
|
-
if (!
|
|
7594
|
+
if (!existsSync14(path))
|
|
7471
7595
|
return;
|
|
7472
7596
|
try {
|
|
7473
|
-
return parseManifest(
|
|
7597
|
+
return parseManifest(readFileSync8(path, "utf-8"));
|
|
7474
7598
|
} catch {
|
|
7475
7599
|
return;
|
|
7476
7600
|
}
|
|
@@ -7513,7 +7637,7 @@ function isCustomSource(value) {
|
|
|
7513
7637
|
return true;
|
|
7514
7638
|
if (value.startsWith("http://") || value.startsWith("https://"))
|
|
7515
7639
|
return true;
|
|
7516
|
-
return
|
|
7640
|
+
return existsSync14(value);
|
|
7517
7641
|
}
|
|
7518
7642
|
async function installCustomSource(source) {
|
|
7519
7643
|
if (isGitUrl(source)) {
|
|
@@ -7527,11 +7651,11 @@ async function installCustomSource(source) {
|
|
|
7527
7651
|
if (source.startsWith("http://") || source.startsWith("https://")) {
|
|
7528
7652
|
return installManifestUrl(source);
|
|
7529
7653
|
}
|
|
7530
|
-
if (!
|
|
7654
|
+
if (!existsSync14(source)) {
|
|
7531
7655
|
throw new Error(`No such local path: ${source}`);
|
|
7532
7656
|
}
|
|
7533
7657
|
if (source.endsWith("manifest.json")) {
|
|
7534
|
-
const manifest = parseManifest(
|
|
7658
|
+
const manifest = parseManifest(readFileSync8(source, "utf-8"));
|
|
7535
7659
|
return writeFromManifest(manifest, source.substring(0, source.lastIndexOf("/")), source, "local");
|
|
7536
7660
|
}
|
|
7537
7661
|
return copyManifestDir(source, source, "local");
|
|
@@ -7562,7 +7686,7 @@ async function installManifestUrl(url) {
|
|
|
7562
7686
|
}
|
|
7563
7687
|
function ensureCustomDir() {
|
|
7564
7688
|
const dir = getCustomHooksDir();
|
|
7565
|
-
|
|
7689
|
+
mkdirSync7(dir, { recursive: true });
|
|
7566
7690
|
return dir;
|
|
7567
7691
|
}
|
|
7568
7692
|
var init_custom_install = __esm(() => {
|
|
@@ -7581,12 +7705,12 @@ __export(exports_sync, {
|
|
|
7581
7705
|
commitSyncArtifacts: () => commitSyncArtifacts
|
|
7582
7706
|
});
|
|
7583
7707
|
import { createHash as createHash2 } from "crypto";
|
|
7584
|
-
import { readFileSync as
|
|
7708
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
7585
7709
|
function sha256OfText(text) {
|
|
7586
7710
|
return createHash2("sha256").update(text).digest("hex");
|
|
7587
7711
|
}
|
|
7588
7712
|
function sha256OfPath(path) {
|
|
7589
|
-
return createHash2("sha256").update(
|
|
7713
|
+
return createHash2("sha256").update(readFileSync9(path)).digest("hex");
|
|
7590
7714
|
}
|
|
7591
7715
|
function collectBundledCatalog() {
|
|
7592
7716
|
const out = [];
|
|
@@ -8536,13 +8660,13 @@ __export(exports_serve, {
|
|
|
8536
8660
|
SERVE_HOST: () => SERVE_HOST,
|
|
8537
8661
|
DEFAULT_SERVE_PORT: () => DEFAULT_SERVE_PORT
|
|
8538
8662
|
});
|
|
8539
|
-
import { readFileSync as
|
|
8540
|
-
import { dirname as
|
|
8663
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
8664
|
+
import { dirname as dirname7, join as join15 } from "path";
|
|
8541
8665
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
8542
8666
|
function packageVersion() {
|
|
8543
8667
|
try {
|
|
8544
|
-
const pkgPath = join15(
|
|
8545
|
-
return JSON.parse(
|
|
8668
|
+
const pkgPath = join15(dirname7(fileURLToPath3(import.meta.url)), "..", "package.json");
|
|
8669
|
+
return JSON.parse(readFileSync10(pkgPath, "utf-8")).version || "0.0.0";
|
|
8546
8670
|
} catch {
|
|
8547
8671
|
return "0.0.0";
|
|
8548
8672
|
}
|
|
@@ -8843,17 +8967,17 @@ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
|
8843
8967
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8844
8968
|
import { z as z2 } from "zod";
|
|
8845
8969
|
import { createServer } from "http";
|
|
8846
|
-
import { existsSync as
|
|
8847
|
-
import { join as join16, dirname as
|
|
8970
|
+
import { existsSync as existsSync15, readFileSync as readFileSync11 } from "fs";
|
|
8971
|
+
import { join as join16, dirname as dirname8 } from "path";
|
|
8848
8972
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
8849
8973
|
async function runVerifiedHook(name, stdin, timeoutMs) {
|
|
8850
8974
|
const { resolveHook: resolveHook2, resolveScriptPath: resolveScriptPath2 } = await Promise.resolve().then(() => (init_resolve(), exports_resolve));
|
|
8851
8975
|
const resolved = resolveHook2(name);
|
|
8852
8976
|
const hookScript = resolveScriptPath2(name);
|
|
8853
|
-
if (!resolved || !hookScript || !
|
|
8977
|
+
if (!resolved || !hookScript || !existsSync15(hookScript)) {
|
|
8854
8978
|
throw new Error(`Hook '${name}' not found`);
|
|
8855
8979
|
}
|
|
8856
|
-
const content =
|
|
8980
|
+
const content = readFileSync11(hookScript);
|
|
8857
8981
|
const sha = sha256Of(content);
|
|
8858
8982
|
const check = checkScriptHash(name, sha);
|
|
8859
8983
|
if (!check.ok) {
|
|
@@ -9058,7 +9182,7 @@ function createHooksServer() {
|
|
|
9058
9182
|
const settingsPath = getSettingsPath(scope);
|
|
9059
9183
|
const issues = [];
|
|
9060
9184
|
const healthy = [];
|
|
9061
|
-
const settingsExist =
|
|
9185
|
+
const settingsExist = existsSync15(settingsPath);
|
|
9062
9186
|
if (!settingsExist) {
|
|
9063
9187
|
issues.push({ hook: "(settings)", issue: `${settingsPath} not found`, severity: "warning" });
|
|
9064
9188
|
}
|
|
@@ -9068,7 +9192,7 @@ function createHooksServer() {
|
|
|
9068
9192
|
const meta = custom ? resolveHookMeta(name) : getHook(name);
|
|
9069
9193
|
let hookHealthy = true;
|
|
9070
9194
|
if (custom) {
|
|
9071
|
-
if (!
|
|
9195
|
+
if (!existsSync15(custom.scriptPath)) {
|
|
9072
9196
|
issues.push({ hook: name, issue: `Custom hook script missing: ${custom.scriptPath}`, severity: "error" });
|
|
9073
9197
|
hookHealthy = false;
|
|
9074
9198
|
}
|
|
@@ -9078,14 +9202,14 @@ function createHooksServer() {
|
|
|
9078
9202
|
continue;
|
|
9079
9203
|
}
|
|
9080
9204
|
const hookDir = getHookPath(name);
|
|
9081
|
-
if (!
|
|
9205
|
+
if (!existsSync15(join16(hookDir, "src", "hook.ts"))) {
|
|
9082
9206
|
issues.push({ hook: name, issue: "Missing src/hook.ts in package", severity: "error" });
|
|
9083
9207
|
hookHealthy = false;
|
|
9084
9208
|
}
|
|
9085
9209
|
}
|
|
9086
9210
|
if (meta && settingsExist) {
|
|
9087
9211
|
try {
|
|
9088
|
-
const settings = JSON.parse(
|
|
9212
|
+
const settings = JSON.parse(readFileSync11(settingsPath, "utf-8"));
|
|
9089
9213
|
if (!hookRegisteredInSettings(settings, name, meta.event, meta.matcher)) {
|
|
9090
9214
|
const eventName = meta.event.split(":")[0];
|
|
9091
9215
|
issues.push({ hook: name, issue: `Not registered under correct event (${eventName})`, severity: "error" });
|
|
@@ -9124,8 +9248,8 @@ function createHooksServer() {
|
|
|
9124
9248
|
const hookPath = getHookPath(name);
|
|
9125
9249
|
const readmePath = join16(hookPath, "README.md");
|
|
9126
9250
|
let readme = "";
|
|
9127
|
-
if (
|
|
9128
|
-
readme =
|
|
9251
|
+
if (existsSync15(readmePath)) {
|
|
9252
|
+
readme = readFileSync11(readmePath, "utf-8");
|
|
9129
9253
|
}
|
|
9130
9254
|
if (verbose) {
|
|
9131
9255
|
return { content: [{ type: "text", text: JSON.stringify({ ...meta, readme }) }] };
|
|
@@ -9308,7 +9432,7 @@ function createHooksServer() {
|
|
|
9308
9432
|
const ctx = {
|
|
9309
9433
|
scope,
|
|
9310
9434
|
settings_path: settingsPath,
|
|
9311
|
-
settings_exists:
|
|
9435
|
+
settings_exists: existsSync15(settingsPath),
|
|
9312
9436
|
registered_hooks: hooks,
|
|
9313
9437
|
hook_count: hooks.length,
|
|
9314
9438
|
healthy,
|
|
@@ -9454,8 +9578,8 @@ function createHooksServer() {
|
|
|
9454
9578
|
const settingsPath = getSettingsPath(scope);
|
|
9455
9579
|
let settings = {};
|
|
9456
9580
|
try {
|
|
9457
|
-
if (
|
|
9458
|
-
settings = JSON.parse(
|
|
9581
|
+
if (existsSync15(settingsPath))
|
|
9582
|
+
settings = JSON.parse(readFileSync11(settingsPath, "utf-8"));
|
|
9459
9583
|
} catch {}
|
|
9460
9584
|
if (!settings.hooks)
|
|
9461
9585
|
settings.hooks = {};
|
|
@@ -9463,10 +9587,10 @@ function createHooksServer() {
|
|
|
9463
9587
|
if (!disabled.includes(name))
|
|
9464
9588
|
disabled.push(name);
|
|
9465
9589
|
settings.hooks.__disabled = disabled;
|
|
9466
|
-
const { writeFileSync:
|
|
9467
|
-
const { dirname:
|
|
9468
|
-
|
|
9469
|
-
|
|
9590
|
+
const { writeFileSync: writeFileSync7, mkdirSync: mkdirSync8 } = await import("fs");
|
|
9591
|
+
const { dirname: dirname9 } = await import("path");
|
|
9592
|
+
mkdirSync8(dirname9(settingsPath), { recursive: true });
|
|
9593
|
+
writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + `
|
|
9470
9594
|
`);
|
|
9471
9595
|
return { content: [{ type: "text", text: JSON.stringify({ hook: name, disabled: true, scope }) }] };
|
|
9472
9596
|
});
|
|
@@ -9477,15 +9601,15 @@ function createHooksServer() {
|
|
|
9477
9601
|
const settingsPath = getSettingsPath(scope);
|
|
9478
9602
|
let settings = {};
|
|
9479
9603
|
try {
|
|
9480
|
-
if (
|
|
9481
|
-
settings = JSON.parse(
|
|
9604
|
+
if (existsSync15(settingsPath))
|
|
9605
|
+
settings = JSON.parse(readFileSync11(settingsPath, "utf-8"));
|
|
9482
9606
|
} catch {}
|
|
9483
9607
|
if (settings.hooks?.__disabled) {
|
|
9484
9608
|
settings.hooks.__disabled = settings.hooks.__disabled.filter((n) => n !== name);
|
|
9485
9609
|
if (settings.hooks.__disabled.length === 0)
|
|
9486
9610
|
delete settings.hooks.__disabled;
|
|
9487
|
-
const { writeFileSync:
|
|
9488
|
-
|
|
9611
|
+
const { writeFileSync: writeFileSync7 } = await import("fs");
|
|
9612
|
+
writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + `
|
|
9489
9613
|
`);
|
|
9490
9614
|
}
|
|
9491
9615
|
return { content: [{ type: "text", text: JSON.stringify({ hook: name, disabled: false, scope }) }] };
|
|
@@ -9848,13 +9972,13 @@ var init_server = __esm(() => {
|
|
|
9848
9972
|
init_redact();
|
|
9849
9973
|
init_authority();
|
|
9850
9974
|
init_storage();
|
|
9851
|
-
__dirname3 =
|
|
9975
|
+
__dirname3 = dirname8(fileURLToPath4(import.meta.url));
|
|
9852
9976
|
pkg = { name: "@hasna/hooks", version: "0.0.0" };
|
|
9853
9977
|
try {
|
|
9854
9978
|
for (const rel of ["../../package.json", "../package.json", "../../../package.json"]) {
|
|
9855
9979
|
const p = join16(__dirname3, rel);
|
|
9856
|
-
if (
|
|
9857
|
-
pkg = JSON.parse(
|
|
9980
|
+
if (existsSync15(p)) {
|
|
9981
|
+
pkg = JSON.parse(readFileSync11(p, "utf-8"));
|
|
9858
9982
|
break;
|
|
9859
9983
|
}
|
|
9860
9984
|
}
|
|
@@ -11596,8 +11720,8 @@ var {
|
|
|
11596
11720
|
|
|
11597
11721
|
// src/cli/index.tsx
|
|
11598
11722
|
import chalk2 from "chalk";
|
|
11599
|
-
import { existsSync as
|
|
11600
|
-
import { join as join17, dirname as
|
|
11723
|
+
import { existsSync as existsSync16, readFileSync as readFileSync12, rmSync as rmSync4 } from "fs";
|
|
11724
|
+
import { join as join17, dirname as dirname9 } from "path";
|
|
11601
11725
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
11602
11726
|
|
|
11603
11727
|
// src/cli/components/App.tsx
|
|
@@ -12439,7 +12563,7 @@ function InstallProgress({
|
|
|
12439
12563
|
onComplete
|
|
12440
12564
|
}) {
|
|
12441
12565
|
const [results, setResults] = useState6([]);
|
|
12442
|
-
const [
|
|
12566
|
+
const [current2, setCurrent] = useState6(0);
|
|
12443
12567
|
const [installing, setInstalling] = useState6(true);
|
|
12444
12568
|
useEffect5(() => {
|
|
12445
12569
|
const install = async () => {
|
|
@@ -12463,12 +12587,12 @@ function InstallProgress({
|
|
|
12463
12587
|
marginBottom: 1,
|
|
12464
12588
|
children: /* @__PURE__ */ jsxDEV6(Text9, {
|
|
12465
12589
|
bold: true,
|
|
12466
|
-
children: installing ? `Installing hooks (${
|
|
12590
|
+
children: installing ? `Installing hooks (${current2 + 1}/${hooks.length})...` : "Installation complete!"
|
|
12467
12591
|
}, undefined, false, undefined, this)
|
|
12468
12592
|
}, undefined, false, undefined, this),
|
|
12469
12593
|
hooks.map((name, i) => {
|
|
12470
12594
|
const result = results[i];
|
|
12471
|
-
const isCurrent = i ===
|
|
12595
|
+
const isCurrent = i === current2 && installing;
|
|
12472
12596
|
const meta = getHook(name);
|
|
12473
12597
|
return /* @__PURE__ */ jsxDEV6(Box7, {
|
|
12474
12598
|
flexDirection: "column",
|
|
@@ -12785,9 +12909,9 @@ init_local_opt_in();
|
|
|
12785
12909
|
init_transport2();
|
|
12786
12910
|
init_db();
|
|
12787
12911
|
import { jsxDEV as jsxDEV8 } from "react/jsx-dev-runtime";
|
|
12788
|
-
var __dirname4 =
|
|
12789
|
-
var pkgPath =
|
|
12790
|
-
var pkg2 = JSON.parse(
|
|
12912
|
+
var __dirname4 = dirname9(fileURLToPath5(import.meta.url));
|
|
12913
|
+
var pkgPath = existsSync16(join17(__dirname4, "..", "package.json")) ? join17(__dirname4, "..", "package.json") : join17(__dirname4, "..", "..", "package.json");
|
|
12914
|
+
var pkg2 = JSON.parse(readFileSync12(pkgPath, "utf-8"));
|
|
12791
12915
|
var program2 = new Command;
|
|
12792
12916
|
function resolveScope(options) {
|
|
12793
12917
|
if (options.project)
|
|
@@ -12797,12 +12921,45 @@ function resolveScope(options) {
|
|
|
12797
12921
|
function resolveTarget(options) {
|
|
12798
12922
|
if (options.target === "gemini")
|
|
12799
12923
|
return "gemini";
|
|
12924
|
+
if (options.target === "codex")
|
|
12925
|
+
return "codex";
|
|
12800
12926
|
if (options.target === "codewith")
|
|
12801
12927
|
return "codewith";
|
|
12802
12928
|
if (options.target === "all")
|
|
12803
12929
|
return "all";
|
|
12804
12930
|
return "claude";
|
|
12805
12931
|
}
|
|
12932
|
+
function resolveConcreteTarget(options) {
|
|
12933
|
+
if (options.target === "gemini")
|
|
12934
|
+
return "gemini";
|
|
12935
|
+
if (options.target === "codex")
|
|
12936
|
+
return "codex";
|
|
12937
|
+
if (options.target === "codewith")
|
|
12938
|
+
return "codewith";
|
|
12939
|
+
return "claude";
|
|
12940
|
+
}
|
|
12941
|
+
function formatSettingsPath(scope, target) {
|
|
12942
|
+
if (target === "all")
|
|
12943
|
+
return "target-specific settings";
|
|
12944
|
+
const actual = getSettingsPath(scope, target);
|
|
12945
|
+
if (scope === "project") {
|
|
12946
|
+
if (target === "codewith")
|
|
12947
|
+
return ".codewith/config.toml";
|
|
12948
|
+
if (target === "gemini")
|
|
12949
|
+
return ".gemini/settings.json";
|
|
12950
|
+
if (target === "codex")
|
|
12951
|
+
return ".codex/hooks.json";
|
|
12952
|
+
return ".claude/settings.json";
|
|
12953
|
+
}
|
|
12954
|
+
if (target === "codewith") {
|
|
12955
|
+
return process.env.HASNA_HOOKS_CODEWITH_CONFIG_PATH ? "$HASNA_HOOKS_CODEWITH_CONFIG_PATH" : "~/.codewith/config.toml";
|
|
12956
|
+
}
|
|
12957
|
+
if (target === "gemini")
|
|
12958
|
+
return "~/.gemini/settings.json";
|
|
12959
|
+
if (target === "codex")
|
|
12960
|
+
return "~/.codex/hooks.json";
|
|
12961
|
+
return actual === getSettingsPath("global", "claude") ? "~/.claude/settings.json" : actual;
|
|
12962
|
+
}
|
|
12806
12963
|
function parseLimit(value, fallback, max) {
|
|
12807
12964
|
const parsed = value ? parseInt(value, 10) : fallback;
|
|
12808
12965
|
if (!Number.isFinite(parsed) || parsed <= 0)
|
|
@@ -12834,7 +12991,7 @@ async function openLocalStoreOrExit(json2) {
|
|
|
12834
12991
|
function readToken(tokenFile) {
|
|
12835
12992
|
if (tokenFile) {
|
|
12836
12993
|
try {
|
|
12837
|
-
return
|
|
12994
|
+
return readFileSync12(tokenFile, "utf-8").trim();
|
|
12838
12995
|
} catch {
|
|
12839
12996
|
return;
|
|
12840
12997
|
}
|
|
@@ -12988,11 +13145,11 @@ program2.command("run").argument("<hook>", "Hook to run").option("--profile <id>
|
|
|
12988
13145
|
process.exit(1);
|
|
12989
13146
|
}
|
|
12990
13147
|
const hookScript = resolveScriptPath2(hook);
|
|
12991
|
-
if (!hookScript || !
|
|
13148
|
+
if (!hookScript || !existsSync16(hookScript)) {
|
|
12992
13149
|
console.error(JSON.stringify({ error: `Hook script not found: ${hookScript ?? hook}` }));
|
|
12993
13150
|
process.exit(1);
|
|
12994
13151
|
}
|
|
12995
|
-
const content =
|
|
13152
|
+
const content = readFileSync12(hookScript);
|
|
12996
13153
|
const sha = sha256Of2(content);
|
|
12997
13154
|
const check = checkScriptHash2(hook, sha);
|
|
12998
13155
|
if (!check.ok) {
|
|
@@ -13098,7 +13255,7 @@ program2.command("run").argument("<hook>", "Hook to run").option("--profile <id>
|
|
|
13098
13255
|
process.stderr.write(stderr);
|
|
13099
13256
|
process.exit(exitCode);
|
|
13100
13257
|
});
|
|
13101
|
-
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, 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) => {
|
|
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) => {
|
|
13102
13259
|
const scope = resolveScope(options);
|
|
13103
13260
|
const target = resolveTarget(options);
|
|
13104
13261
|
let toInstall = hooks;
|
|
@@ -13277,7 +13434,8 @@ Dry run \u2014 would install (${scope}, ${target}):
|
|
|
13277
13434
|
success: results.filter((r) => r.success).length,
|
|
13278
13435
|
scope,
|
|
13279
13436
|
target,
|
|
13280
|
-
applied: results.some((r) => r.applied)
|
|
13437
|
+
applied: results.some((r) => r.applied),
|
|
13438
|
+
...target === "codex" ? { notes: results.filter((r) => r.success && r.note).map((r) => r.note) } : {}
|
|
13281
13439
|
}));
|
|
13282
13440
|
if (results.some((r) => !r.success)) {
|
|
13283
13441
|
process.exitCode = 1;
|
|
@@ -13285,7 +13443,7 @@ Dry run \u2014 would install (${scope}, ${target}):
|
|
|
13285
13443
|
return;
|
|
13286
13444
|
}
|
|
13287
13445
|
const successCount = results.filter((r) => r.success).length;
|
|
13288
|
-
const settingsFile = target === "codewith" ? options.applyCodewith ? options.codewithConfig : "TOML fragment only (configs should apply)" : scope
|
|
13446
|
+
const settingsFile = target === "codewith" ? options.applyCodewith ? options.codewithConfig : "TOML fragment only (configs should apply)" : formatSettingsPath(scope, target);
|
|
13289
13447
|
console.log(chalk2.bold(`
|
|
13290
13448
|
Installing hooks (${scope}, ${target})...
|
|
13291
13449
|
`));
|
|
@@ -13299,6 +13457,8 @@ Installing hooks (${scope}, ${target})...
|
|
|
13299
13457
|
if (result.conflict) {
|
|
13300
13458
|
console.log(chalk2.yellow(` \u26A0 Warning: ${result.conflict}`));
|
|
13301
13459
|
}
|
|
13460
|
+
if (result.note && target === "codex")
|
|
13461
|
+
console.log(chalk2.dim(` ${result.note}`));
|
|
13302
13462
|
if (result.fragment && target === "codewith") {
|
|
13303
13463
|
console.log(chalk2.dim(" Codewith TOML fragment:"));
|
|
13304
13464
|
console.log(chalk2.cyan(result.fragment.trimEnd().split(`
|
|
@@ -13325,12 +13485,12 @@ Installing hooks (${scope}, ${target})...
|
|
|
13325
13485
|
console.log(chalk2.dim(`
|
|
13326
13486
|
Registered in ${settingsFile}`));
|
|
13327
13487
|
});
|
|
13328
|
-
program2.command("list").alias("ls").option("-c, --category <category>", "Filter by category").option("-a, --all", "Show all available hooks", false).option("-i, --installed", "Show only installed hooks", false).option("-r, --registered", "Show registered hooks", false).option("-g, --global", "Check global settings", false).option("-p, --project", "Check project settings", false).option("-t, --target <target>", "Agent target: claude, gemini, codewith (default: claude)", "claude").option("-n, --limit <n>", "Max rows to show in compact output", "20").option("--verbose", "Show descriptions and full detail columns", false).option("-j, --json", "Output as JSON", false).description("List available or installed hooks (bundled + custom/registry)").action((options) => {
|
|
13488
|
+
program2.command("list").alias("ls").option("-c, --category <category>", "Filter by category").option("-a, --all", "Show all available hooks", false).option("-i, --installed", "Show only installed hooks", false).option("-r, --registered", "Show registered hooks", false).option("-g, --global", "Check global settings", false).option("-p, --project", "Check project settings", false).option("-t, --target <target>", "Agent target: claude, gemini, codewith, codex (default: claude)", "claude").option("-n, --limit <n>", "Max rows to show in compact output", "20").option("--verbose", "Show descriptions and full detail columns", false).option("-j, --json", "Output as JSON", false).description("List available or installed hooks (bundled + custom/registry)").action((options) => {
|
|
13329
13489
|
const scope = resolveScope(options);
|
|
13330
13490
|
const limit = options.all ? Number.MAX_SAFE_INTEGER : parseLimit(options.limit, 20, 200);
|
|
13331
13491
|
const storeHooks = listStoreHooks();
|
|
13332
13492
|
if (options.registered || options.installed) {
|
|
13333
|
-
const target = options
|
|
13493
|
+
const target = resolveConcreteTarget(options);
|
|
13334
13494
|
const registered = getRegisteredHooksForTarget(scope, target);
|
|
13335
13495
|
const installed = [...new Set([...registered, ...storeHooks.map((s) => s.meta.name)])];
|
|
13336
13496
|
const shown = options.registered ? registered : installed;
|
|
@@ -13453,7 +13613,7 @@ Found ${results.length} hook(s), showing ${visible.length}:
|
|
|
13453
13613
|
console.log(hookSummaryLine(h, { verbose: options.verbose }));
|
|
13454
13614
|
printDisclosureHint(results.length - visible.length, "hooks info <name>");
|
|
13455
13615
|
});
|
|
13456
|
-
program2.command("remove").alias("rm").argument("<hook>", "Hook to remove").option("-g, --global", "Remove from global settings", false).option("-p, --project", "Remove from project settings", false).option("-t, --target <target>", "Agent target: claude, gemini, codewith, all (default: claude)", "claude").option("-j, --json", "Output as JSON", false).description("Remove an installed hook (settings registration + store + lock + DB)").action(async (hook, options) => {
|
|
13616
|
+
program2.command("remove").alias("rm").argument("<hook>", "Hook to remove").option("-g, --global", "Remove from global settings", false).option("-p, --project", "Remove from project settings", false).option("-t, --target <target>", "Agent target: claude, gemini, codewith, codex, all (default: claude)", "claude").option("-j, --json", "Output as JSON", false).description("Remove an installed hook (settings registration + store + lock + DB)").action(async (hook, options) => {
|
|
13457
13617
|
const scope = resolveScope(options);
|
|
13458
13618
|
const target = resolveTarget(options);
|
|
13459
13619
|
const { uninstallHook: uninstallHook2 } = await Promise.resolve().then(() => (init_installer(), exports_installer));
|
|
@@ -13569,7 +13729,7 @@ program2.command("doctor").option("-g, --global", "Check global settings", false
|
|
|
13569
13729
|
});
|
|
13570
13730
|
}
|
|
13571
13731
|
const staleNames = new Set(stale.map((finding) => finding.hook));
|
|
13572
|
-
const settingsExist =
|
|
13732
|
+
const settingsExist = existsSync16(settingsPath);
|
|
13573
13733
|
if (!settingsExist) {
|
|
13574
13734
|
issues.push({ hook: "(settings)", issue: `${settingsPath} not found`, severity: "warning" });
|
|
13575
13735
|
}
|
|
@@ -13577,7 +13737,7 @@ program2.command("doctor").option("-g, --global", "Check global settings", false
|
|
|
13577
13737
|
let wiringCount = 0;
|
|
13578
13738
|
if (settingsExist) {
|
|
13579
13739
|
try {
|
|
13580
|
-
const settings = JSON.parse(
|
|
13740
|
+
const settings = JSON.parse(readFileSync12(settingsPath, "utf-8"));
|
|
13581
13741
|
wiringCount = countSettingsWiring(settings);
|
|
13582
13742
|
} catch {}
|
|
13583
13743
|
}
|
|
@@ -13588,7 +13748,7 @@ program2.command("doctor").option("-g, --global", "Check global settings", false
|
|
|
13588
13748
|
const meta = custom ? resolveHookMeta(name) : getHook(name);
|
|
13589
13749
|
let hookHealthy = true;
|
|
13590
13750
|
if (custom) {
|
|
13591
|
-
if (!
|
|
13751
|
+
if (!existsSync16(custom.scriptPath)) {
|
|
13592
13752
|
issues.push({ hook: name, issue: `Custom hook script missing: ${custom.scriptPath}`, severity: "error" });
|
|
13593
13753
|
hookHealthy = false;
|
|
13594
13754
|
}
|
|
@@ -13600,14 +13760,14 @@ program2.command("doctor").option("-g, --global", "Check global settings", false
|
|
|
13600
13760
|
}
|
|
13601
13761
|
const hookDir = getHookPath(name);
|
|
13602
13762
|
const hookScript = join17(hookDir, "src", "hook.ts");
|
|
13603
|
-
if (!
|
|
13763
|
+
if (!existsSync16(hookScript)) {
|
|
13604
13764
|
issues.push({ hook: name, issue: "Missing src/hook.ts in package", severity: "error" });
|
|
13605
13765
|
hookHealthy = false;
|
|
13606
13766
|
}
|
|
13607
13767
|
}
|
|
13608
13768
|
if (meta && settingsExist) {
|
|
13609
13769
|
try {
|
|
13610
|
-
const settings = JSON.parse(
|
|
13770
|
+
const settings = JSON.parse(readFileSync12(settingsPath, "utf-8"));
|
|
13611
13771
|
if (!hookRegisteredInSettings(settings, name, meta.event, meta.matcher)) {
|
|
13612
13772
|
const eventName = meta.event.split(":")[0];
|
|
13613
13773
|
issues.push({ hook: name, issue: `Not registered under correct event (${eventName})`, severity: "error" });
|
|
@@ -13812,8 +13972,8 @@ program2.command("docs").argument("[hook]", "Hook name (shows general docs if om
|
|
|
13812
13972
|
const hookPath = getHookPath(hook);
|
|
13813
13973
|
const readmePath = join17(hookPath, "README.md");
|
|
13814
13974
|
let readme = "";
|
|
13815
|
-
if (
|
|
13816
|
-
readme =
|
|
13975
|
+
if (existsSync16(readmePath)) {
|
|
13976
|
+
readme = readFileSync12(readmePath, "utf-8");
|
|
13817
13977
|
}
|
|
13818
13978
|
if (options.json) {
|
|
13819
13979
|
console.log(JSON.stringify({ ...meta, readme }));
|
|
@@ -13932,7 +14092,7 @@ ${meta.displayName} v${meta.version}
|
|
|
13932
14092
|
console.log();
|
|
13933
14093
|
});
|
|
13934
14094
|
program2.command("upgrade").option("-c, --check", "Check for updates without installing", false).option("-j, --json", "Output as JSON", false).description("Update the @hasna/hooks package to the latest version").action(async (options) => {
|
|
13935
|
-
const
|
|
14095
|
+
const current2 = pkg2.version;
|
|
13936
14096
|
let pm = "npm";
|
|
13937
14097
|
try {
|
|
13938
14098
|
const which = Bun.spawnSync(["which", "bun"]);
|
|
@@ -13950,13 +14110,13 @@ program2.command("upgrade").option("-c, --check", "Check for updates without ins
|
|
|
13950
14110
|
}
|
|
13951
14111
|
process.exit(1);
|
|
13952
14112
|
}
|
|
13953
|
-
const upToDate =
|
|
14113
|
+
const upToDate = current2 === latest2;
|
|
13954
14114
|
if (options.json) {
|
|
13955
|
-
console.log(JSON.stringify({ current, latest: latest2, upToDate }));
|
|
14115
|
+
console.log(JSON.stringify({ current: current2, latest: latest2, upToDate }));
|
|
13956
14116
|
} else if (upToDate) {
|
|
13957
|
-
console.log(chalk2.green(`\u2713 Already on latest version (${
|
|
14117
|
+
console.log(chalk2.green(`\u2713 Already on latest version (${current2})`));
|
|
13958
14118
|
} else {
|
|
13959
|
-
console.log(chalk2.yellow(`Update available: ${
|
|
14119
|
+
console.log(chalk2.yellow(`Update available: ${current2} \u2192 ${latest2}`));
|
|
13960
14120
|
console.log(chalk2.dim(` Run: hooks upgrade`));
|
|
13961
14121
|
}
|
|
13962
14122
|
return;
|
|
@@ -13977,7 +14137,7 @@ Upgrading @hasna/hooks (${pm})...
|
|
|
13977
14137
|
const exitCode = await proc.exited;
|
|
13978
14138
|
if (exitCode !== 0) {
|
|
13979
14139
|
if (options.json) {
|
|
13980
|
-
console.log(JSON.stringify({ current, updated: false, error: `${pm} exited with code ${exitCode}` }));
|
|
14140
|
+
console.log(JSON.stringify({ current: current2, updated: false, error: `${pm} exited with code ${exitCode}` }));
|
|
13981
14141
|
} else {
|
|
13982
14142
|
console.log(chalk2.red(`
|
|
13983
14143
|
\u2717 Upgrade failed (exit code ${exitCode})`));
|
|
@@ -13987,18 +14147,18 @@ Upgrading @hasna/hooks (${pm})...
|
|
|
13987
14147
|
const versionProc = Bun.spawnSync(["npm", "view", "@hasna/hooks", "version"]);
|
|
13988
14148
|
const latest = new TextDecoder().decode(versionProc.stdout).trim() || "unknown";
|
|
13989
14149
|
if (options.json) {
|
|
13990
|
-
console.log(JSON.stringify({ current, latest, updated: true }));
|
|
14150
|
+
console.log(JSON.stringify({ current: current2, latest, updated: true }));
|
|
13991
14151
|
} else {
|
|
13992
14152
|
console.log(chalk2.green(`
|
|
13993
|
-
\u2713 Upgraded: ${
|
|
14153
|
+
\u2713 Upgraded: ${current2} \u2192 ${latest}`));
|
|
13994
14154
|
}
|
|
13995
14155
|
});
|
|
13996
14156
|
program2.command("profile-export").description("Export all agent profiles as JSON (for backup/cross-machine setup)").option("-o, --output <file>", "Write to file instead of stdout").option("-j, --json", "Output as JSON (default: true)", false).action(async (options) => {
|
|
13997
14157
|
const profiles = exportProfiles();
|
|
13998
14158
|
const json2 = JSON.stringify(profiles, null, 2);
|
|
13999
14159
|
if (options.output) {
|
|
14000
|
-
const { writeFileSync:
|
|
14001
|
-
|
|
14160
|
+
const { writeFileSync: writeFileSync7 } = await import("fs");
|
|
14161
|
+
writeFileSync7(options.output, json2 + `
|
|
14002
14162
|
`);
|
|
14003
14163
|
console.log(chalk2.green(`\u2713 Exported ${profiles.length} profile(s) to ${options.output}`));
|
|
14004
14164
|
} else {
|
|
@@ -14010,9 +14170,9 @@ program2.command("profile-import").argument("<file>", "JSON file to import profi
|
|
|
14010
14170
|
if (file === "-") {
|
|
14011
14171
|
raw = await new Response(Bun.stdin.stream()).text();
|
|
14012
14172
|
} else {
|
|
14013
|
-
const { readFileSync:
|
|
14173
|
+
const { readFileSync: readFileSync13 } = await import("fs");
|
|
14014
14174
|
try {
|
|
14015
|
-
raw =
|
|
14175
|
+
raw = readFileSync13(file, "utf-8");
|
|
14016
14176
|
} catch {
|
|
14017
14177
|
if (options.json) {
|
|
14018
14178
|
console.log(JSON.stringify({ error: `Cannot read file: ${file}` }));
|