@hasna/hooks 0.1.0 → 0.1.1
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/index.js +84 -41
- package/dist/index.js +72 -29
- package/package.json +2 -2
package/bin/index.js
CHANGED
|
@@ -3842,11 +3842,17 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
|
3842
3842
|
import { join, dirname } from "path";
|
|
3843
3843
|
import { homedir } from "os";
|
|
3844
3844
|
import { fileURLToPath } from "url";
|
|
3845
|
-
function
|
|
3845
|
+
function getTargetSettingsDir(target) {
|
|
3846
|
+
if (target === "gemini")
|
|
3847
|
+
return ".gemini";
|
|
3848
|
+
return ".claude";
|
|
3849
|
+
}
|
|
3850
|
+
function getSettingsPath(scope = "global", target = "claude") {
|
|
3851
|
+
const dir = getTargetSettingsDir(target);
|
|
3846
3852
|
if (scope === "project") {
|
|
3847
|
-
return join(process.cwd(),
|
|
3853
|
+
return join(process.cwd(), dir, "settings.json");
|
|
3848
3854
|
}
|
|
3849
|
-
return join(homedir(),
|
|
3855
|
+
return join(homedir(), dir, "settings.json");
|
|
3850
3856
|
}
|
|
3851
3857
|
function getHookPath(name) {
|
|
3852
3858
|
const hookName = name.startsWith("hook-") ? name : `hook-${name}`;
|
|
@@ -3855,8 +3861,8 @@ function getHookPath(name) {
|
|
|
3855
3861
|
function hookExists(name) {
|
|
3856
3862
|
return existsSync(getHookPath(name));
|
|
3857
3863
|
}
|
|
3858
|
-
function readSettings(scope = "global") {
|
|
3859
|
-
const path = getSettingsPath(scope);
|
|
3864
|
+
function readSettings(scope = "global", target = "claude") {
|
|
3865
|
+
const path = getSettingsPath(scope, target);
|
|
3860
3866
|
try {
|
|
3861
3867
|
if (existsSync(path)) {
|
|
3862
3868
|
return JSON.parse(readFileSync(path, "utf-8"));
|
|
@@ -3864,8 +3870,8 @@ function readSettings(scope = "global") {
|
|
|
3864
3870
|
} catch {}
|
|
3865
3871
|
return {};
|
|
3866
3872
|
}
|
|
3867
|
-
function writeSettings(settings, scope = "global") {
|
|
3868
|
-
const path = getSettingsPath(scope);
|
|
3873
|
+
function writeSettings(settings, scope = "global", target = "claude") {
|
|
3874
|
+
const path = getSettingsPath(scope, target);
|
|
3869
3875
|
const dir = dirname(path);
|
|
3870
3876
|
if (!existsSync(dir)) {
|
|
3871
3877
|
mkdirSync(dir, { recursive: true });
|
|
@@ -3873,36 +3879,48 @@ function writeSettings(settings, scope = "global") {
|
|
|
3873
3879
|
writeFileSync(path, JSON.stringify(settings, null, 2) + `
|
|
3874
3880
|
`);
|
|
3875
3881
|
}
|
|
3876
|
-
function
|
|
3877
|
-
|
|
3882
|
+
function getTargetEventName(internalEvent, target) {
|
|
3883
|
+
return EVENT_MAP[target]?.[internalEvent] || internalEvent;
|
|
3884
|
+
}
|
|
3885
|
+
function installForTarget(name, scope, overwrite, target) {
|
|
3878
3886
|
const hookName = name.startsWith("hook-") ? name : `hook-${name}`;
|
|
3879
3887
|
const shortName = hookName.replace("hook-", "");
|
|
3880
3888
|
if (!hookExists(shortName)) {
|
|
3881
|
-
return { hook: shortName, success: false, error: `Hook '${shortName}' not found
|
|
3889
|
+
return { hook: shortName, success: false, error: `Hook '${shortName}' not found`, target };
|
|
3882
3890
|
}
|
|
3883
|
-
const registered =
|
|
3891
|
+
const registered = getRegisteredHooksForTarget(scope, target);
|
|
3884
3892
|
if (registered.includes(shortName) && !overwrite) {
|
|
3885
|
-
return { hook: shortName, success: false, error: "Already installed. Use --overwrite to replace.", scope };
|
|
3893
|
+
return { hook: shortName, success: false, error: "Already installed. Use --overwrite to replace.", scope, target };
|
|
3886
3894
|
}
|
|
3887
3895
|
try {
|
|
3888
|
-
registerHook(shortName, scope);
|
|
3889
|
-
return { hook: shortName, success: true, scope };
|
|
3896
|
+
registerHook(shortName, scope, target);
|
|
3897
|
+
return { hook: shortName, success: true, scope, target };
|
|
3890
3898
|
} catch (error) {
|
|
3891
3899
|
return {
|
|
3892
3900
|
hook: shortName,
|
|
3893
3901
|
success: false,
|
|
3894
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
3902
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
3903
|
+
target
|
|
3895
3904
|
};
|
|
3896
3905
|
}
|
|
3897
3906
|
}
|
|
3898
|
-
function
|
|
3907
|
+
function installHook(name, options = {}) {
|
|
3908
|
+
const { scope = "global", overwrite = false, target = "claude" } = options;
|
|
3909
|
+
if (target === "all") {
|
|
3910
|
+
const claudeResult = installForTarget(name, scope, overwrite, "claude");
|
|
3911
|
+
installForTarget(name, scope, overwrite, "gemini");
|
|
3912
|
+
return { ...claudeResult, target: "all" };
|
|
3913
|
+
}
|
|
3914
|
+
return installForTarget(name, scope, overwrite, target);
|
|
3915
|
+
}
|
|
3916
|
+
function registerHook(name, scope = "global", target = "claude") {
|
|
3899
3917
|
const meta = getHook(name);
|
|
3900
3918
|
if (!meta)
|
|
3901
3919
|
return;
|
|
3902
|
-
const settings = readSettings(scope);
|
|
3920
|
+
const settings = readSettings(scope, target);
|
|
3903
3921
|
if (!settings.hooks)
|
|
3904
3922
|
settings.hooks = {};
|
|
3905
|
-
const eventKey = meta.event;
|
|
3923
|
+
const eventKey = getTargetEventName(meta.event, target);
|
|
3906
3924
|
if (!settings.hooks[eventKey])
|
|
3907
3925
|
settings.hooks[eventKey] = [];
|
|
3908
3926
|
const hookCommand = `hooks run ${name}`;
|
|
@@ -3914,16 +3932,16 @@ function registerHook(name, scope = "global") {
|
|
|
3914
3932
|
entry.matcher = meta.matcher;
|
|
3915
3933
|
}
|
|
3916
3934
|
settings.hooks[eventKey].push(entry);
|
|
3917
|
-
writeSettings(settings, scope);
|
|
3935
|
+
writeSettings(settings, scope, target);
|
|
3918
3936
|
}
|
|
3919
|
-
function unregisterHook(name, scope = "global") {
|
|
3937
|
+
function unregisterHook(name, scope = "global", target = "claude") {
|
|
3920
3938
|
const meta = getHook(name);
|
|
3921
3939
|
if (!meta)
|
|
3922
3940
|
return;
|
|
3923
|
-
const settings = readSettings(scope);
|
|
3941
|
+
const settings = readSettings(scope, target);
|
|
3924
3942
|
if (!settings.hooks)
|
|
3925
3943
|
return;
|
|
3926
|
-
const eventKey = meta.event;
|
|
3944
|
+
const eventKey = getTargetEventName(meta.event, target);
|
|
3927
3945
|
if (!settings.hooks[eventKey])
|
|
3928
3946
|
return;
|
|
3929
3947
|
const hookCommand = `hooks run ${name}`;
|
|
@@ -3934,10 +3952,10 @@ function unregisterHook(name, scope = "global") {
|
|
|
3934
3952
|
if (Object.keys(settings.hooks).length === 0) {
|
|
3935
3953
|
delete settings.hooks;
|
|
3936
3954
|
}
|
|
3937
|
-
writeSettings(settings, scope);
|
|
3955
|
+
writeSettings(settings, scope, target);
|
|
3938
3956
|
}
|
|
3939
|
-
function
|
|
3940
|
-
const settings = readSettings(scope);
|
|
3957
|
+
function getRegisteredHooksForTarget(scope = "global", target = "claude") {
|
|
3958
|
+
const settings = readSettings(scope, target);
|
|
3941
3959
|
if (!settings.hooks)
|
|
3942
3960
|
return [];
|
|
3943
3961
|
const registered = [];
|
|
@@ -3955,24 +3973,49 @@ function getRegisteredHooks(scope = "global") {
|
|
|
3955
3973
|
}
|
|
3956
3974
|
return [...new Set(registered)];
|
|
3957
3975
|
}
|
|
3976
|
+
function getRegisteredHooks(scope = "global") {
|
|
3977
|
+
return getRegisteredHooksForTarget(scope, "claude");
|
|
3978
|
+
}
|
|
3958
3979
|
function getInstalledHooks(scope = "global") {
|
|
3959
3980
|
return getRegisteredHooks(scope);
|
|
3960
3981
|
}
|
|
3961
|
-
function removeHook(name, scope = "global") {
|
|
3982
|
+
function removeHook(name, scope = "global", target = "claude") {
|
|
3962
3983
|
const hookName = name.startsWith("hook-") ? name : `hook-${name}`;
|
|
3963
3984
|
const shortName = hookName.replace("hook-", "");
|
|
3964
|
-
|
|
3965
|
-
|
|
3985
|
+
if (target === "all") {
|
|
3986
|
+
const claudeRemoved = removeHookForTarget(shortName, scope, "claude");
|
|
3987
|
+
const geminiRemoved = removeHookForTarget(shortName, scope, "gemini");
|
|
3988
|
+
return claudeRemoved || geminiRemoved;
|
|
3989
|
+
}
|
|
3990
|
+
return removeHookForTarget(shortName, scope, target);
|
|
3991
|
+
}
|
|
3992
|
+
function removeHookForTarget(name, scope, target) {
|
|
3993
|
+
const registered = getRegisteredHooksForTarget(scope, target);
|
|
3994
|
+
if (!registered.includes(name)) {
|
|
3966
3995
|
return false;
|
|
3967
3996
|
}
|
|
3968
|
-
unregisterHook(
|
|
3997
|
+
unregisterHook(name, scope, target);
|
|
3969
3998
|
return true;
|
|
3970
3999
|
}
|
|
3971
|
-
var __dirname2, HOOKS_DIR;
|
|
4000
|
+
var __dirname2, HOOKS_DIR, EVENT_MAP;
|
|
3972
4001
|
var init_installer = __esm(() => {
|
|
3973
4002
|
init_registry();
|
|
3974
4003
|
__dirname2 = dirname(fileURLToPath(import.meta.url));
|
|
3975
4004
|
HOOKS_DIR = existsSync(join(__dirname2, "..", "..", "hooks", "hook-gitguard")) ? join(__dirname2, "..", "..", "hooks") : join(__dirname2, "..", "hooks");
|
|
4005
|
+
EVENT_MAP = {
|
|
4006
|
+
claude: {
|
|
4007
|
+
PreToolUse: "PreToolUse",
|
|
4008
|
+
PostToolUse: "PostToolUse",
|
|
4009
|
+
Stop: "Stop",
|
|
4010
|
+
Notification: "Notification"
|
|
4011
|
+
},
|
|
4012
|
+
gemini: {
|
|
4013
|
+
PreToolUse: "BeforeTool",
|
|
4014
|
+
PostToolUse: "AfterTool",
|
|
4015
|
+
Stop: "AfterAgent",
|
|
4016
|
+
Notification: "Notification"
|
|
4017
|
+
}
|
|
4018
|
+
};
|
|
3976
4019
|
});
|
|
3977
4020
|
|
|
3978
4021
|
// src/mcp/server.ts
|
|
@@ -3995,7 +4038,7 @@ function createHooksServer() {
|
|
|
3995
4038
|
name: "@hasna/hooks",
|
|
3996
4039
|
version: "0.0.7"
|
|
3997
4040
|
});
|
|
3998
|
-
server.tool("hooks_list", "List all available
|
|
4041
|
+
server.tool("hooks_list", "List all available hooks, optionally filtered by category", { category: z.string().optional().describe("Filter by category name (e.g. 'Git Safety', 'Code Quality', 'Security', 'Notifications', 'Context Management')") }, async ({ category }) => {
|
|
3999
4042
|
if (category) {
|
|
4000
4043
|
const cat = CATEGORIES.find((c) => c.toLowerCase() === category.toLowerCase());
|
|
4001
4044
|
if (!cat) {
|
|
@@ -4022,7 +4065,7 @@ function createHooksServer() {
|
|
|
4022
4065
|
const projectInstalled = getRegisteredHooks("project").includes(meta.name);
|
|
4023
4066
|
return { content: [{ type: "text", text: JSON.stringify({ ...meta, global: globalInstalled, project: projectInstalled }) }] };
|
|
4024
4067
|
});
|
|
4025
|
-
server.tool("hooks_install", "Install one or more hooks by registering them in
|
|
4068
|
+
server.tool("hooks_install", "Install one or more hooks by registering them in agent settings", {
|
|
4026
4069
|
hooks: z.array(z.string()).describe("Hook names to install"),
|
|
4027
4070
|
scope: z.enum(["global", "project"]).default("global").describe("Install scope"),
|
|
4028
4071
|
overwrite: z.boolean().default(false).describe("Overwrite if already installed")
|
|
@@ -4082,7 +4125,7 @@ function createHooksServer() {
|
|
|
4082
4125
|
}]
|
|
4083
4126
|
};
|
|
4084
4127
|
});
|
|
4085
|
-
server.tool("hooks_remove", "Remove (unregister) a hook from
|
|
4128
|
+
server.tool("hooks_remove", "Remove (unregister) a hook from agent settings", {
|
|
4086
4129
|
name: z.string().describe("Hook name to remove"),
|
|
4087
4130
|
scope: z.enum(["global", "project"]).default("global").describe("Scope to remove from")
|
|
4088
4131
|
}, async ({ name, scope }) => {
|
|
@@ -4153,7 +4196,7 @@ function createHooksServer() {
|
|
|
4153
4196
|
content: [{
|
|
4154
4197
|
type: "text",
|
|
4155
4198
|
text: JSON.stringify({
|
|
4156
|
-
overview: "
|
|
4199
|
+
overview: "Hooks are scripts that run at specific points in an AI coding agent session. Install @hasna/hooks globally, then register hooks \u2014 no files are copied to your project.",
|
|
4157
4200
|
events: {
|
|
4158
4201
|
PreToolUse: "Fires before a tool executes. Can block the operation.",
|
|
4159
4202
|
PostToolUse: "Fires after a tool executes. Runs asynchronously.",
|
|
@@ -5425,11 +5468,11 @@ function resolveScope(options) {
|
|
|
5425
5468
|
return "project";
|
|
5426
5469
|
return "global";
|
|
5427
5470
|
}
|
|
5428
|
-
program2.name("hooks").description("Install
|
|
5471
|
+
program2.name("hooks").description("Install hooks for AI coding agents").version("0.1.1");
|
|
5429
5472
|
program2.command("interactive", { isDefault: true }).alias("i").description("Interactive hook browser").action(() => {
|
|
5430
5473
|
render(/* @__PURE__ */ jsxDEV8(App, {}, undefined, false, undefined, this));
|
|
5431
5474
|
});
|
|
5432
|
-
program2.command("run").argument("<hook>", "Hook to run").description("Execute a hook (called by
|
|
5475
|
+
program2.command("run").argument("<hook>", "Hook to run").description("Execute a hook (called by AI coding agents)").action(async (hook) => {
|
|
5433
5476
|
const meta = getHook(hook);
|
|
5434
5477
|
if (!meta) {
|
|
5435
5478
|
console.error(JSON.stringify({ error: `Hook '${hook}' not found` }));
|
|
@@ -5512,7 +5555,7 @@ Installing hooks (${scope})...
|
|
|
5512
5555
|
console.log(chalk2.dim(`
|
|
5513
5556
|
Registered in ${settingsFile}`));
|
|
5514
5557
|
});
|
|
5515
|
-
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
|
|
5558
|
+
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("-j, --json", "Output as JSON", false).description("List available or installed hooks").action((options) => {
|
|
5516
5559
|
const scope = resolveScope(options);
|
|
5517
5560
|
if (options.registered || options.installed) {
|
|
5518
5561
|
const registered = getRegisteredHooks(scope);
|
|
@@ -5822,11 +5865,11 @@ ${meta.displayName} v${meta.version}
|
|
|
5822
5865
|
return;
|
|
5823
5866
|
}
|
|
5824
5867
|
const generalDocs = {
|
|
5825
|
-
overview: "
|
|
5868
|
+
overview: "Hooks are scripts that run at specific points in an AI coding agent session. Install @hasna/hooks globally, then register hooks \u2014 no files are copied to your project.",
|
|
5826
5869
|
events: {
|
|
5827
5870
|
PreToolUse: 'Fires before a tool executes. Can block the operation by returning { "decision": "block" }.',
|
|
5828
5871
|
PostToolUse: "Fires after a tool executes. Runs asynchronously, cannot block.",
|
|
5829
|
-
Stop: "Fires when a
|
|
5872
|
+
Stop: "Fires when a session ends. Useful for notifications and cleanup.",
|
|
5830
5873
|
Notification: "Fires on notification events like context compaction."
|
|
5831
5874
|
},
|
|
5832
5875
|
installation: {
|
|
@@ -5848,7 +5891,7 @@ ${meta.displayName} v${meta.version}
|
|
|
5848
5891
|
howItWorks: {
|
|
5849
5892
|
install: "bun install -g @hasna/hooks",
|
|
5850
5893
|
register: "hooks install gitguard \u2192 writes to ~/.claude/settings.json",
|
|
5851
|
-
execution: "
|
|
5894
|
+
execution: "Agent runs 'hooks run gitguard' \u2192 executes hook from global package",
|
|
5852
5895
|
noFileCopy: "No files are copied to your project. Hooks run from the global @hasna/hooks package."
|
|
5853
5896
|
}
|
|
5854
5897
|
};
|
|
@@ -5894,7 +5937,7 @@ ${meta.displayName} v${meta.version}
|
|
|
5894
5937
|
console.log(` hooks docs --json Machine-readable documentation`);
|
|
5895
5938
|
console.log();
|
|
5896
5939
|
});
|
|
5897
|
-
program2.command("mcp").option("-s, --stdio", "Use stdio transport (for
|
|
5940
|
+
program2.command("mcp").option("-s, --stdio", "Use stdio transport (for agent MCP integration)", false).option("-p, --port <port>", "Port for SSE transport", "39427").description("Start MCP server for AI agent integration").action(async (options) => {
|
|
5898
5941
|
if (options.stdio) {
|
|
5899
5942
|
const { startStdioServer: startStdioServer2 } = await Promise.resolve().then(() => (init_server(), exports_server));
|
|
5900
5943
|
await startStdioServer2();
|
package/dist/index.js
CHANGED
|
@@ -331,11 +331,31 @@ import { homedir } from "os";
|
|
|
331
331
|
import { fileURLToPath } from "url";
|
|
332
332
|
var __dirname2 = dirname(fileURLToPath(import.meta.url));
|
|
333
333
|
var HOOKS_DIR = existsSync(join(__dirname2, "..", "..", "hooks", "hook-gitguard")) ? join(__dirname2, "..", "..", "hooks") : join(__dirname2, "..", "hooks");
|
|
334
|
-
|
|
334
|
+
var EVENT_MAP = {
|
|
335
|
+
claude: {
|
|
336
|
+
PreToolUse: "PreToolUse",
|
|
337
|
+
PostToolUse: "PostToolUse",
|
|
338
|
+
Stop: "Stop",
|
|
339
|
+
Notification: "Notification"
|
|
340
|
+
},
|
|
341
|
+
gemini: {
|
|
342
|
+
PreToolUse: "BeforeTool",
|
|
343
|
+
PostToolUse: "AfterTool",
|
|
344
|
+
Stop: "AfterAgent",
|
|
345
|
+
Notification: "Notification"
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
function getTargetSettingsDir(target) {
|
|
349
|
+
if (target === "gemini")
|
|
350
|
+
return ".gemini";
|
|
351
|
+
return ".claude";
|
|
352
|
+
}
|
|
353
|
+
function getSettingsPath(scope = "global", target = "claude") {
|
|
354
|
+
const dir = getTargetSettingsDir(target);
|
|
335
355
|
if (scope === "project") {
|
|
336
|
-
return join(process.cwd(),
|
|
356
|
+
return join(process.cwd(), dir, "settings.json");
|
|
337
357
|
}
|
|
338
|
-
return join(homedir(),
|
|
358
|
+
return join(homedir(), dir, "settings.json");
|
|
339
359
|
}
|
|
340
360
|
function getHookPath(name) {
|
|
341
361
|
const hookName = name.startsWith("hook-") ? name : `hook-${name}`;
|
|
@@ -344,8 +364,8 @@ function getHookPath(name) {
|
|
|
344
364
|
function hookExists(name) {
|
|
345
365
|
return existsSync(getHookPath(name));
|
|
346
366
|
}
|
|
347
|
-
function readSettings(scope = "global") {
|
|
348
|
-
const path = getSettingsPath(scope);
|
|
367
|
+
function readSettings(scope = "global", target = "claude") {
|
|
368
|
+
const path = getSettingsPath(scope, target);
|
|
349
369
|
try {
|
|
350
370
|
if (existsSync(path)) {
|
|
351
371
|
return JSON.parse(readFileSync(path, "utf-8"));
|
|
@@ -353,8 +373,8 @@ function readSettings(scope = "global") {
|
|
|
353
373
|
} catch {}
|
|
354
374
|
return {};
|
|
355
375
|
}
|
|
356
|
-
function writeSettings(settings, scope = "global") {
|
|
357
|
-
const path = getSettingsPath(scope);
|
|
376
|
+
function writeSettings(settings, scope = "global", target = "claude") {
|
|
377
|
+
const path = getSettingsPath(scope, target);
|
|
358
378
|
const dir = dirname(path);
|
|
359
379
|
if (!existsSync(dir)) {
|
|
360
380
|
mkdirSync(dir, { recursive: true });
|
|
@@ -362,36 +382,48 @@ function writeSettings(settings, scope = "global") {
|
|
|
362
382
|
writeFileSync(path, JSON.stringify(settings, null, 2) + `
|
|
363
383
|
`);
|
|
364
384
|
}
|
|
365
|
-
function
|
|
366
|
-
|
|
385
|
+
function getTargetEventName(internalEvent, target) {
|
|
386
|
+
return EVENT_MAP[target]?.[internalEvent] || internalEvent;
|
|
387
|
+
}
|
|
388
|
+
function installForTarget(name, scope, overwrite, target) {
|
|
367
389
|
const hookName = name.startsWith("hook-") ? name : `hook-${name}`;
|
|
368
390
|
const shortName = hookName.replace("hook-", "");
|
|
369
391
|
if (!hookExists(shortName)) {
|
|
370
|
-
return { hook: shortName, success: false, error: `Hook '${shortName}' not found
|
|
392
|
+
return { hook: shortName, success: false, error: `Hook '${shortName}' not found`, target };
|
|
371
393
|
}
|
|
372
|
-
const registered =
|
|
394
|
+
const registered = getRegisteredHooksForTarget(scope, target);
|
|
373
395
|
if (registered.includes(shortName) && !overwrite) {
|
|
374
|
-
return { hook: shortName, success: false, error: "Already installed. Use --overwrite to replace.", scope };
|
|
396
|
+
return { hook: shortName, success: false, error: "Already installed. Use --overwrite to replace.", scope, target };
|
|
375
397
|
}
|
|
376
398
|
try {
|
|
377
|
-
registerHook(shortName, scope);
|
|
378
|
-
return { hook: shortName, success: true, scope };
|
|
399
|
+
registerHook(shortName, scope, target);
|
|
400
|
+
return { hook: shortName, success: true, scope, target };
|
|
379
401
|
} catch (error) {
|
|
380
402
|
return {
|
|
381
403
|
hook: shortName,
|
|
382
404
|
success: false,
|
|
383
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
405
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
406
|
+
target
|
|
384
407
|
};
|
|
385
408
|
}
|
|
386
409
|
}
|
|
387
|
-
function
|
|
410
|
+
function installHook(name, options = {}) {
|
|
411
|
+
const { scope = "global", overwrite = false, target = "claude" } = options;
|
|
412
|
+
if (target === "all") {
|
|
413
|
+
const claudeResult = installForTarget(name, scope, overwrite, "claude");
|
|
414
|
+
installForTarget(name, scope, overwrite, "gemini");
|
|
415
|
+
return { ...claudeResult, target: "all" };
|
|
416
|
+
}
|
|
417
|
+
return installForTarget(name, scope, overwrite, target);
|
|
418
|
+
}
|
|
419
|
+
function registerHook(name, scope = "global", target = "claude") {
|
|
388
420
|
const meta = getHook(name);
|
|
389
421
|
if (!meta)
|
|
390
422
|
return;
|
|
391
|
-
const settings = readSettings(scope);
|
|
423
|
+
const settings = readSettings(scope, target);
|
|
392
424
|
if (!settings.hooks)
|
|
393
425
|
settings.hooks = {};
|
|
394
|
-
const eventKey = meta.event;
|
|
426
|
+
const eventKey = getTargetEventName(meta.event, target);
|
|
395
427
|
if (!settings.hooks[eventKey])
|
|
396
428
|
settings.hooks[eventKey] = [];
|
|
397
429
|
const hookCommand = `hooks run ${name}`;
|
|
@@ -403,16 +435,16 @@ function registerHook(name, scope = "global") {
|
|
|
403
435
|
entry.matcher = meta.matcher;
|
|
404
436
|
}
|
|
405
437
|
settings.hooks[eventKey].push(entry);
|
|
406
|
-
writeSettings(settings, scope);
|
|
438
|
+
writeSettings(settings, scope, target);
|
|
407
439
|
}
|
|
408
|
-
function unregisterHook(name, scope = "global") {
|
|
440
|
+
function unregisterHook(name, scope = "global", target = "claude") {
|
|
409
441
|
const meta = getHook(name);
|
|
410
442
|
if (!meta)
|
|
411
443
|
return;
|
|
412
|
-
const settings = readSettings(scope);
|
|
444
|
+
const settings = readSettings(scope, target);
|
|
413
445
|
if (!settings.hooks)
|
|
414
446
|
return;
|
|
415
|
-
const eventKey = meta.event;
|
|
447
|
+
const eventKey = getTargetEventName(meta.event, target);
|
|
416
448
|
if (!settings.hooks[eventKey])
|
|
417
449
|
return;
|
|
418
450
|
const hookCommand = `hooks run ${name}`;
|
|
@@ -423,13 +455,13 @@ function unregisterHook(name, scope = "global") {
|
|
|
423
455
|
if (Object.keys(settings.hooks).length === 0) {
|
|
424
456
|
delete settings.hooks;
|
|
425
457
|
}
|
|
426
|
-
writeSettings(settings, scope);
|
|
458
|
+
writeSettings(settings, scope, target);
|
|
427
459
|
}
|
|
428
460
|
function installHooks(names, options = {}) {
|
|
429
461
|
return names.map((name) => installHook(name, options));
|
|
430
462
|
}
|
|
431
|
-
function
|
|
432
|
-
const settings = readSettings(scope);
|
|
463
|
+
function getRegisteredHooksForTarget(scope = "global", target = "claude") {
|
|
464
|
+
const settings = readSettings(scope, target);
|
|
433
465
|
if (!settings.hooks)
|
|
434
466
|
return [];
|
|
435
467
|
const registered = [];
|
|
@@ -447,17 +479,28 @@ function getRegisteredHooks(scope = "global") {
|
|
|
447
479
|
}
|
|
448
480
|
return [...new Set(registered)];
|
|
449
481
|
}
|
|
482
|
+
function getRegisteredHooks(scope = "global") {
|
|
483
|
+
return getRegisteredHooksForTarget(scope, "claude");
|
|
484
|
+
}
|
|
450
485
|
function getInstalledHooks(scope = "global") {
|
|
451
486
|
return getRegisteredHooks(scope);
|
|
452
487
|
}
|
|
453
|
-
function removeHook(name, scope = "global") {
|
|
488
|
+
function removeHook(name, scope = "global", target = "claude") {
|
|
454
489
|
const hookName = name.startsWith("hook-") ? name : `hook-${name}`;
|
|
455
490
|
const shortName = hookName.replace("hook-", "");
|
|
456
|
-
|
|
457
|
-
|
|
491
|
+
if (target === "all") {
|
|
492
|
+
const claudeRemoved = removeHookForTarget(shortName, scope, "claude");
|
|
493
|
+
const geminiRemoved = removeHookForTarget(shortName, scope, "gemini");
|
|
494
|
+
return claudeRemoved || geminiRemoved;
|
|
495
|
+
}
|
|
496
|
+
return removeHookForTarget(shortName, scope, target);
|
|
497
|
+
}
|
|
498
|
+
function removeHookForTarget(name, scope, target) {
|
|
499
|
+
const registered = getRegisteredHooksForTarget(scope, target);
|
|
500
|
+
if (!registered.includes(name)) {
|
|
458
501
|
return false;
|
|
459
502
|
}
|
|
460
|
-
unregisterHook(
|
|
503
|
+
unregisterHook(name, scope, target);
|
|
461
504
|
return true;
|
|
462
505
|
}
|
|
463
506
|
export {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hasna/hooks",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Open source
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Open source hooks library for AI coding agents - Install safety, quality, and automation hooks with a single command",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"hooks": "bin/index.js"
|