@hasna/hooks 0.1.0 → 0.2.0
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/LICENSE +191 -0
- package/README.md +41 -6
- package/bin/index.js +138 -102
- package/dist/index.js +91 -42
- package/hooks/hook-agentmessages/CLAUDE.md +1 -1
- package/hooks/hook-agentmessages/LICENSE +191 -21
- package/hooks/hook-agentmessages/README.md +3 -3
- package/hooks/hook-agentmessages/package.json +5 -5
- package/hooks/hook-autoformat/README.md +1 -1
- package/hooks/hook-autoformat/package.json +1 -1
- package/hooks/hook-autoformat/tsconfig.json +25 -0
- package/hooks/hook-autostage/README.md +1 -1
- package/hooks/hook-autostage/package.json +1 -1
- package/hooks/hook-branchprotect/README.md +2 -2
- package/hooks/hook-branchprotect/package.json +4 -4
- package/hooks/hook-branchprotect/src/cli.ts +1 -1
- package/hooks/hook-branchprotect/tsconfig.json +25 -0
- package/hooks/hook-checkbugs/LICENSE +191 -21
- package/hooks/hook-checkbugs/README.md +5 -5
- package/hooks/hook-checkbugs/package.json +4 -4
- package/hooks/hook-checkbugs/src/cli.ts +2 -2
- package/hooks/hook-checkbugs/tsconfig.json +15 -0
- package/hooks/hook-checkdocs/README.md +5 -5
- package/hooks/hook-checkdocs/package.json +4 -4
- package/hooks/hook-checkdocs/src/cli.ts +2 -2
- package/hooks/hook-checkdocs/tsconfig.json +15 -0
- package/hooks/hook-checkfiles/LICENSE +191 -21
- package/hooks/hook-checkfiles/README.md +5 -5
- package/hooks/hook-checkfiles/package.json +4 -4
- package/hooks/hook-checkfiles/src/cli.ts +2 -2
- package/hooks/hook-checkfiles/tsconfig.json +15 -0
- package/hooks/hook-checklint/LICENSE +191 -21
- package/hooks/hook-checklint/README.md +5 -5
- package/hooks/hook-checklint/package.json +4 -4
- package/hooks/hook-checklint/src/cli.ts +2 -2
- package/hooks/hook-checklint/tsconfig.json +15 -0
- package/hooks/hook-checkpoint/README.md +2 -2
- package/hooks/hook-checkpoint/package.json +4 -4
- package/hooks/hook-checkpoint/src/cli.ts +1 -1
- package/hooks/hook-checkpoint/tsconfig.json +25 -0
- package/hooks/hook-checksecurity/LICENSE +191 -21
- package/hooks/hook-checksecurity/README.md +8 -8
- package/hooks/hook-checksecurity/package.json +4 -4
- package/hooks/hook-checksecurity/src/cli.ts +2 -2
- package/hooks/hook-checksecurity/tsconfig.json +15 -0
- package/hooks/hook-checktasks/README.md +5 -5
- package/hooks/hook-checktasks/package.json +4 -4
- package/hooks/hook-checktasks/src/cli.ts +2 -2
- package/hooks/hook-checktasks/tsconfig.json +20 -0
- package/hooks/hook-checktests/LICENSE +191 -21
- package/hooks/hook-checktests/README.md +5 -5
- package/hooks/hook-checktests/package.json +4 -4
- package/hooks/hook-checktests/src/cli.ts +2 -2
- package/hooks/hook-checktests/tsconfig.json +15 -0
- package/hooks/hook-commandlog/README.md +1 -1
- package/hooks/hook-commandlog/package.json +1 -1
- package/hooks/hook-contextrefresh/README.md +2 -2
- package/hooks/hook-contextrefresh/package.json +4 -4
- package/hooks/hook-contextrefresh/src/cli.ts +1 -1
- package/hooks/hook-contextrefresh/tsconfig.json +25 -0
- package/hooks/hook-costwatch/README.md +1 -1
- package/hooks/hook-costwatch/package.json +1 -1
- package/hooks/hook-desktopnotify/README.md +1 -1
- package/hooks/hook-desktopnotify/package.json +1 -1
- package/hooks/hook-desktopnotify/tsconfig.json +25 -0
- package/hooks/hook-envsetup/README.md +1 -1
- package/hooks/hook-envsetup/package.json +1 -1
- package/hooks/hook-envsetup/tsconfig.json +25 -0
- package/hooks/hook-errornotify/README.md +1 -1
- package/hooks/hook-errornotify/package.json +1 -1
- package/hooks/hook-gitguard/README.md +2 -2
- package/hooks/hook-gitguard/package.json +4 -4
- package/hooks/hook-gitguard/src/cli.ts +1 -1
- package/hooks/hook-gitguard/tsconfig.json +25 -0
- package/hooks/hook-packageage/README.md +2 -2
- package/hooks/hook-packageage/package.json +4 -4
- package/hooks/hook-packageage/src/cli.ts +1 -1
- package/hooks/hook-packageage/tsconfig.json +25 -0
- package/hooks/hook-permissionguard/README.md +1 -1
- package/hooks/hook-permissionguard/package.json +1 -1
- package/hooks/hook-permissionguard/tsconfig.json +25 -0
- package/hooks/hook-phonenotify/README.md +2 -2
- package/hooks/hook-phonenotify/package.json +4 -4
- package/hooks/hook-phonenotify/src/cli.ts +1 -1
- package/hooks/hook-phonenotify/tsconfig.json +25 -0
- package/hooks/hook-precompact/README.md +2 -2
- package/hooks/hook-precompact/package.json +4 -4
- package/hooks/hook-precompact/src/cli.ts +1 -1
- package/hooks/hook-precompact/tsconfig.json +25 -0
- package/hooks/hook-promptguard/README.md +1 -1
- package/hooks/hook-promptguard/package.json +1 -1
- package/hooks/hook-protectfiles/README.md +1 -1
- package/hooks/hook-protectfiles/package.json +1 -1
- package/hooks/hook-protectfiles/tsconfig.json +25 -0
- package/hooks/hook-sessionlog/README.md +1 -1
- package/hooks/hook-sessionlog/package.json +1 -1
- package/hooks/hook-slacknotify/README.md +1 -1
- package/hooks/hook-slacknotify/package.json +1 -1
- package/hooks/hook-soundnotify/README.md +1 -1
- package/hooks/hook-soundnotify/package.json +1 -1
- package/hooks/hook-taskgate/README.md +1 -1
- package/hooks/hook-taskgate/package.json +1 -1
- package/hooks/hook-tddguard/README.md +1 -1
- package/hooks/hook-tddguard/package.json +1 -1
- package/package.json +18 -4
- package/.claude/settings.json +0 -24
- package/.npmrc.example +0 -2
- package/AGENTS.md +0 -54
- package/CLAUDE.md +0 -70
- package/CONTRIBUTING.md +0 -45
- package/hooks/hook-agentmessages/bin/cli.ts +0 -125
package/bin/index.js
CHANGED
|
@@ -1957,7 +1957,7 @@ var init_registry = __esm(() => {
|
|
|
1957
1957
|
{
|
|
1958
1958
|
name: "checkbugs",
|
|
1959
1959
|
displayName: "Check Bugs",
|
|
1960
|
-
description: "Checks for bugs via
|
|
1960
|
+
description: "Checks for bugs via headless agent",
|
|
1961
1961
|
version: "0.1.6",
|
|
1962
1962
|
category: "Code Quality",
|
|
1963
1963
|
event: "PostToolUse",
|
|
@@ -1987,7 +1987,7 @@ var init_registry = __esm(() => {
|
|
|
1987
1987
|
{
|
|
1988
1988
|
name: "checksecurity",
|
|
1989
1989
|
displayName: "Check Security",
|
|
1990
|
-
description: "Runs security checks via
|
|
1990
|
+
description: "Runs security checks via headless agents",
|
|
1991
1991
|
version: "0.1.6",
|
|
1992
1992
|
category: "Security",
|
|
1993
1993
|
event: "PostToolUse",
|
|
@@ -3842,30 +3842,46 @@ 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 normalizeHookName(name) {
|
|
3846
|
+
return name.startsWith("hook-") ? name : `hook-${name}`;
|
|
3847
|
+
}
|
|
3848
|
+
function shortHookName(name) {
|
|
3849
|
+
return normalizeHookName(name).replace("hook-", "");
|
|
3850
|
+
}
|
|
3851
|
+
function removeHookEntries(entries, hookCommand) {
|
|
3852
|
+
return entries.filter((entry) => !entry.hooks?.some((h) => h.command === hookCommand));
|
|
3853
|
+
}
|
|
3854
|
+
function getTargetSettingsDir(target) {
|
|
3855
|
+
if (target === "gemini")
|
|
3856
|
+
return ".gemini";
|
|
3857
|
+
return ".claude";
|
|
3858
|
+
}
|
|
3859
|
+
function getSettingsPath(scope = "global", target = "claude") {
|
|
3860
|
+
const dir = getTargetSettingsDir(target);
|
|
3846
3861
|
if (scope === "project") {
|
|
3847
|
-
return join(process.cwd(),
|
|
3862
|
+
return join(process.cwd(), dir, "settings.json");
|
|
3848
3863
|
}
|
|
3849
|
-
return join(homedir(),
|
|
3864
|
+
return join(homedir(), dir, "settings.json");
|
|
3850
3865
|
}
|
|
3851
3866
|
function getHookPath(name) {
|
|
3852
|
-
|
|
3853
|
-
return join(HOOKS_DIR, hookName);
|
|
3867
|
+
return join(HOOKS_DIR, normalizeHookName(name));
|
|
3854
3868
|
}
|
|
3855
3869
|
function hookExists(name) {
|
|
3856
3870
|
return existsSync(getHookPath(name));
|
|
3857
3871
|
}
|
|
3858
|
-
function readSettings(scope = "global") {
|
|
3859
|
-
const path = getSettingsPath(scope);
|
|
3872
|
+
function readSettings(scope = "global", target = "claude") {
|
|
3873
|
+
const path = getSettingsPath(scope, target);
|
|
3860
3874
|
try {
|
|
3861
3875
|
if (existsSync(path)) {
|
|
3862
3876
|
return JSON.parse(readFileSync(path, "utf-8"));
|
|
3863
3877
|
}
|
|
3864
|
-
} catch {
|
|
3878
|
+
} catch (error) {
|
|
3879
|
+
console.warn(`[hooks] Failed to read settings at ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
3880
|
+
}
|
|
3865
3881
|
return {};
|
|
3866
3882
|
}
|
|
3867
|
-
function writeSettings(settings, scope = "global") {
|
|
3868
|
-
const path = getSettingsPath(scope);
|
|
3883
|
+
function writeSettings(settings, scope = "global", target = "claude") {
|
|
3884
|
+
const path = getSettingsPath(scope, target);
|
|
3869
3885
|
const dir = dirname(path);
|
|
3870
3886
|
if (!existsSync(dir)) {
|
|
3871
3887
|
mkdirSync(dir, { recursive: true });
|
|
@@ -3873,40 +3889,51 @@ function writeSettings(settings, scope = "global") {
|
|
|
3873
3889
|
writeFileSync(path, JSON.stringify(settings, null, 2) + `
|
|
3874
3890
|
`);
|
|
3875
3891
|
}
|
|
3876
|
-
function
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3892
|
+
function getTargetEventName(internalEvent, target) {
|
|
3893
|
+
return EVENT_MAP[target]?.[internalEvent] || internalEvent;
|
|
3894
|
+
}
|
|
3895
|
+
function installForTarget(name, scope, overwrite, target) {
|
|
3896
|
+
const shortName = shortHookName(name);
|
|
3880
3897
|
if (!hookExists(shortName)) {
|
|
3881
|
-
return { hook: shortName, success: false, error: `Hook '${shortName}' not found
|
|
3898
|
+
return { hook: shortName, success: false, error: `Hook '${shortName}' not found`, target };
|
|
3882
3899
|
}
|
|
3883
|
-
const registered =
|
|
3900
|
+
const registered = getRegisteredHooksForTarget(scope, target);
|
|
3884
3901
|
if (registered.includes(shortName) && !overwrite) {
|
|
3885
|
-
return { hook: shortName, success: false, error: "Already installed. Use --overwrite to replace.", scope };
|
|
3902
|
+
return { hook: shortName, success: false, error: "Already installed. Use --overwrite to replace.", scope, target };
|
|
3886
3903
|
}
|
|
3887
3904
|
try {
|
|
3888
|
-
registerHook(shortName, scope);
|
|
3889
|
-
return { hook: shortName, success: true, scope };
|
|
3905
|
+
registerHook(shortName, scope, target);
|
|
3906
|
+
return { hook: shortName, success: true, scope, target };
|
|
3890
3907
|
} catch (error) {
|
|
3891
3908
|
return {
|
|
3892
3909
|
hook: shortName,
|
|
3893
3910
|
success: false,
|
|
3894
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
3911
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
3912
|
+
target
|
|
3895
3913
|
};
|
|
3896
3914
|
}
|
|
3897
3915
|
}
|
|
3898
|
-
function
|
|
3916
|
+
function installHook(name, options = {}) {
|
|
3917
|
+
const { scope = "global", overwrite = false, target = "claude" } = options;
|
|
3918
|
+
if (target === "all") {
|
|
3919
|
+
const claudeResult = installForTarget(name, scope, overwrite, "claude");
|
|
3920
|
+
installForTarget(name, scope, overwrite, "gemini");
|
|
3921
|
+
return { ...claudeResult, target: "all" };
|
|
3922
|
+
}
|
|
3923
|
+
return installForTarget(name, scope, overwrite, target);
|
|
3924
|
+
}
|
|
3925
|
+
function registerHook(name, scope = "global", target = "claude") {
|
|
3899
3926
|
const meta = getHook(name);
|
|
3900
3927
|
if (!meta)
|
|
3901
3928
|
return;
|
|
3902
|
-
const settings = readSettings(scope);
|
|
3929
|
+
const settings = readSettings(scope, target);
|
|
3903
3930
|
if (!settings.hooks)
|
|
3904
3931
|
settings.hooks = {};
|
|
3905
|
-
const eventKey = meta.event;
|
|
3932
|
+
const eventKey = getTargetEventName(meta.event, target);
|
|
3906
3933
|
if (!settings.hooks[eventKey])
|
|
3907
3934
|
settings.hooks[eventKey] = [];
|
|
3908
3935
|
const hookCommand = `hooks run ${name}`;
|
|
3909
|
-
settings.hooks[eventKey] = settings.hooks[eventKey]
|
|
3936
|
+
settings.hooks[eventKey] = removeHookEntries(settings.hooks[eventKey], hookCommand);
|
|
3910
3937
|
const entry = {
|
|
3911
3938
|
hooks: [{ type: "command", command: hookCommand }]
|
|
3912
3939
|
};
|
|
@@ -3914,30 +3941,30 @@ function registerHook(name, scope = "global") {
|
|
|
3914
3941
|
entry.matcher = meta.matcher;
|
|
3915
3942
|
}
|
|
3916
3943
|
settings.hooks[eventKey].push(entry);
|
|
3917
|
-
writeSettings(settings, scope);
|
|
3944
|
+
writeSettings(settings, scope, target);
|
|
3918
3945
|
}
|
|
3919
|
-
function unregisterHook(name, scope = "global") {
|
|
3946
|
+
function unregisterHook(name, scope = "global", target = "claude") {
|
|
3920
3947
|
const meta = getHook(name);
|
|
3921
3948
|
if (!meta)
|
|
3922
3949
|
return;
|
|
3923
|
-
const settings = readSettings(scope);
|
|
3950
|
+
const settings = readSettings(scope, target);
|
|
3924
3951
|
if (!settings.hooks)
|
|
3925
3952
|
return;
|
|
3926
|
-
const eventKey = meta.event;
|
|
3953
|
+
const eventKey = getTargetEventName(meta.event, target);
|
|
3927
3954
|
if (!settings.hooks[eventKey])
|
|
3928
3955
|
return;
|
|
3929
3956
|
const hookCommand = `hooks run ${name}`;
|
|
3930
|
-
settings.hooks[eventKey] = settings.hooks[eventKey]
|
|
3957
|
+
settings.hooks[eventKey] = removeHookEntries(settings.hooks[eventKey], hookCommand);
|
|
3931
3958
|
if (settings.hooks[eventKey].length === 0) {
|
|
3932
3959
|
delete settings.hooks[eventKey];
|
|
3933
3960
|
}
|
|
3934
3961
|
if (Object.keys(settings.hooks).length === 0) {
|
|
3935
3962
|
delete settings.hooks;
|
|
3936
3963
|
}
|
|
3937
|
-
writeSettings(settings, scope);
|
|
3964
|
+
writeSettings(settings, scope, target);
|
|
3938
3965
|
}
|
|
3939
|
-
function
|
|
3940
|
-
const settings = readSettings(scope);
|
|
3966
|
+
function getRegisteredHooksForTarget(scope = "global", target = "claude") {
|
|
3967
|
+
const settings = readSettings(scope, target);
|
|
3941
3968
|
if (!settings.hooks)
|
|
3942
3969
|
return [];
|
|
3943
3970
|
const registered = [];
|
|
@@ -3955,24 +3982,46 @@ function getRegisteredHooks(scope = "global") {
|
|
|
3955
3982
|
}
|
|
3956
3983
|
return [...new Set(registered)];
|
|
3957
3984
|
}
|
|
3958
|
-
function
|
|
3959
|
-
return
|
|
3985
|
+
function getRegisteredHooks(scope = "global") {
|
|
3986
|
+
return getRegisteredHooksForTarget(scope, "claude");
|
|
3987
|
+
}
|
|
3988
|
+
function removeHook(name, scope = "global", target = "claude") {
|
|
3989
|
+
const shortName = shortHookName(name);
|
|
3990
|
+
if (target === "all") {
|
|
3991
|
+
const claudeRemoved = removeHookForTarget(shortName, scope, "claude");
|
|
3992
|
+
const geminiRemoved = removeHookForTarget(shortName, scope, "gemini");
|
|
3993
|
+
return claudeRemoved || geminiRemoved;
|
|
3994
|
+
}
|
|
3995
|
+
return removeHookForTarget(shortName, scope, target);
|
|
3960
3996
|
}
|
|
3961
|
-
function
|
|
3962
|
-
const
|
|
3963
|
-
|
|
3964
|
-
const registered = getRegisteredHooks(scope);
|
|
3965
|
-
if (!registered.includes(shortName)) {
|
|
3997
|
+
function removeHookForTarget(name, scope, target) {
|
|
3998
|
+
const registered = getRegisteredHooksForTarget(scope, target);
|
|
3999
|
+
if (!registered.includes(name)) {
|
|
3966
4000
|
return false;
|
|
3967
4001
|
}
|
|
3968
|
-
unregisterHook(
|
|
4002
|
+
unregisterHook(name, scope, target);
|
|
3969
4003
|
return true;
|
|
3970
4004
|
}
|
|
3971
|
-
var __dirname2, HOOKS_DIR;
|
|
4005
|
+
var __dirname2, HOOKS_DIR, EVENT_MAP, getInstalledHooks;
|
|
3972
4006
|
var init_installer = __esm(() => {
|
|
3973
4007
|
init_registry();
|
|
3974
4008
|
__dirname2 = dirname(fileURLToPath(import.meta.url));
|
|
3975
4009
|
HOOKS_DIR = existsSync(join(__dirname2, "..", "..", "hooks", "hook-gitguard")) ? join(__dirname2, "..", "..", "hooks") : join(__dirname2, "..", "hooks");
|
|
4010
|
+
EVENT_MAP = {
|
|
4011
|
+
claude: {
|
|
4012
|
+
PreToolUse: "PreToolUse",
|
|
4013
|
+
PostToolUse: "PostToolUse",
|
|
4014
|
+
Stop: "Stop",
|
|
4015
|
+
Notification: "Notification"
|
|
4016
|
+
},
|
|
4017
|
+
gemini: {
|
|
4018
|
+
PreToolUse: "BeforeTool",
|
|
4019
|
+
PostToolUse: "AfterTool",
|
|
4020
|
+
Stop: "AfterAgent",
|
|
4021
|
+
Notification: "Notification"
|
|
4022
|
+
}
|
|
4023
|
+
};
|
|
4024
|
+
getInstalledHooks = getRegisteredHooks;
|
|
3976
4025
|
});
|
|
3977
4026
|
|
|
3978
4027
|
// src/mcp/server.ts
|
|
@@ -3989,13 +4038,24 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
3989
4038
|
import { z } from "zod";
|
|
3990
4039
|
import { createServer } from "http";
|
|
3991
4040
|
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
3992
|
-
import { join as join2 } from "path";
|
|
4041
|
+
import { join as join2, dirname as dirname2 } from "path";
|
|
4042
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4043
|
+
function formatInstallResults(results, extra) {
|
|
4044
|
+
const installed = results.filter((r) => r.success).map((r) => r.hook);
|
|
4045
|
+
const failed = results.filter((r) => !r.success).map((r) => ({ hook: r.hook, error: r.error }));
|
|
4046
|
+
return {
|
|
4047
|
+
content: [{
|
|
4048
|
+
type: "text",
|
|
4049
|
+
text: JSON.stringify({ installed, failed, total: results.length, success: installed.length, ...extra })
|
|
4050
|
+
}]
|
|
4051
|
+
};
|
|
4052
|
+
}
|
|
3993
4053
|
function createHooksServer() {
|
|
3994
4054
|
const server = new McpServer({
|
|
3995
4055
|
name: "@hasna/hooks",
|
|
3996
|
-
version:
|
|
4056
|
+
version: pkg.version
|
|
3997
4057
|
});
|
|
3998
|
-
server.tool("hooks_list", "List all available
|
|
4058
|
+
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
4059
|
if (category) {
|
|
4000
4060
|
const cat = CATEGORIES.find((c) => c.toLowerCase() === category.toLowerCase());
|
|
4001
4061
|
if (!cat) {
|
|
@@ -4022,24 +4082,13 @@ function createHooksServer() {
|
|
|
4022
4082
|
const projectInstalled = getRegisteredHooks("project").includes(meta.name);
|
|
4023
4083
|
return { content: [{ type: "text", text: JSON.stringify({ ...meta, global: globalInstalled, project: projectInstalled }) }] };
|
|
4024
4084
|
});
|
|
4025
|
-
server.tool("hooks_install", "Install one or more hooks by registering them in
|
|
4085
|
+
server.tool("hooks_install", "Install one or more hooks by registering them in agent settings", {
|
|
4026
4086
|
hooks: z.array(z.string()).describe("Hook names to install"),
|
|
4027
4087
|
scope: z.enum(["global", "project"]).default("global").describe("Install scope"),
|
|
4028
4088
|
overwrite: z.boolean().default(false).describe("Overwrite if already installed")
|
|
4029
4089
|
}, async ({ hooks, scope, overwrite }) => {
|
|
4030
4090
|
const results = hooks.map((name) => installHook(name, { scope, overwrite }));
|
|
4031
|
-
return {
|
|
4032
|
-
content: [{
|
|
4033
|
-
type: "text",
|
|
4034
|
-
text: JSON.stringify({
|
|
4035
|
-
installed: results.filter((r) => r.success).map((r) => r.hook),
|
|
4036
|
-
failed: results.filter((r) => !r.success).map((r) => ({ hook: r.hook, error: r.error })),
|
|
4037
|
-
total: results.length,
|
|
4038
|
-
success: results.filter((r) => r.success).length,
|
|
4039
|
-
scope
|
|
4040
|
-
})
|
|
4041
|
-
}]
|
|
4042
|
-
};
|
|
4091
|
+
return formatInstallResults(results, { scope });
|
|
4043
4092
|
});
|
|
4044
4093
|
server.tool("hooks_install_category", "Install all hooks in a category", {
|
|
4045
4094
|
category: z.string().describe("Category name"),
|
|
@@ -4052,37 +4101,16 @@ function createHooksServer() {
|
|
|
4052
4101
|
}
|
|
4053
4102
|
const hooks = getHooksByCategory(cat).map((h) => h.name);
|
|
4054
4103
|
const results = hooks.map((name) => installHook(name, { scope, overwrite }));
|
|
4055
|
-
return {
|
|
4056
|
-
content: [{
|
|
4057
|
-
type: "text",
|
|
4058
|
-
text: JSON.stringify({
|
|
4059
|
-
installed: results.filter((r) => r.success).map((r) => r.hook),
|
|
4060
|
-
failed: results.filter((r) => !r.success).map((r) => ({ hook: r.hook, error: r.error })),
|
|
4061
|
-
category: cat,
|
|
4062
|
-
scope
|
|
4063
|
-
})
|
|
4064
|
-
}]
|
|
4065
|
-
};
|
|
4104
|
+
return formatInstallResults(results, { category: cat, scope });
|
|
4066
4105
|
});
|
|
4067
4106
|
server.tool("hooks_install_all", "Install all available hooks", {
|
|
4068
4107
|
scope: z.enum(["global", "project"]).default("global").describe("Install scope"),
|
|
4069
4108
|
overwrite: z.boolean().default(false).describe("Overwrite if already installed")
|
|
4070
4109
|
}, async ({ scope, overwrite }) => {
|
|
4071
4110
|
const results = HOOKS.map((h) => installHook(h.name, { scope, overwrite }));
|
|
4072
|
-
return {
|
|
4073
|
-
content: [{
|
|
4074
|
-
type: "text",
|
|
4075
|
-
text: JSON.stringify({
|
|
4076
|
-
installed: results.filter((r) => r.success).map((r) => r.hook),
|
|
4077
|
-
failed: results.filter((r) => !r.success).map((r) => ({ hook: r.hook, error: r.error })),
|
|
4078
|
-
total: results.length,
|
|
4079
|
-
success: results.filter((r) => r.success).length,
|
|
4080
|
-
scope
|
|
4081
|
-
})
|
|
4082
|
-
}]
|
|
4083
|
-
};
|
|
4111
|
+
return formatInstallResults(results, { scope });
|
|
4084
4112
|
});
|
|
4085
|
-
server.tool("hooks_remove", "Remove (unregister) a hook from
|
|
4113
|
+
server.tool("hooks_remove", "Remove (unregister) a hook from agent settings", {
|
|
4086
4114
|
name: z.string().describe("Hook name to remove"),
|
|
4087
4115
|
scope: z.enum(["global", "project"]).default("global").describe("Scope to remove from")
|
|
4088
4116
|
}, async ({ name, scope }) => {
|
|
@@ -4153,7 +4181,7 @@ function createHooksServer() {
|
|
|
4153
4181
|
content: [{
|
|
4154
4182
|
type: "text",
|
|
4155
4183
|
text: JSON.stringify({
|
|
4156
|
-
overview: "
|
|
4184
|
+
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
4185
|
events: {
|
|
4158
4186
|
PreToolUse: "Fires before a tool executes. Can block the operation.",
|
|
4159
4187
|
PostToolUse: "Fires after a tool executes. Runs asynchronously.",
|
|
@@ -4209,7 +4237,7 @@ async function startSSEServer(port = MCP_PORT) {
|
|
|
4209
4237
|
await transport.handlePostMessage(req, res, body);
|
|
4210
4238
|
} else {
|
|
4211
4239
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
4212
|
-
res.end(JSON.stringify({ name: "@hasna/hooks", version:
|
|
4240
|
+
res.end(JSON.stringify({ name: "@hasna/hooks", version: pkg.version, transport: "sse", port }));
|
|
4213
4241
|
}
|
|
4214
4242
|
});
|
|
4215
4243
|
httpServer.listen(port, () => {
|
|
@@ -4222,10 +4250,12 @@ async function startStdioServer() {
|
|
|
4222
4250
|
const transport = new StdioServerTransport;
|
|
4223
4251
|
await server.connect(transport);
|
|
4224
4252
|
}
|
|
4225
|
-
var MCP_PORT = 39427;
|
|
4253
|
+
var __dirname3, pkg, MCP_PORT = 39427;
|
|
4226
4254
|
var init_server = __esm(() => {
|
|
4227
4255
|
init_registry();
|
|
4228
4256
|
init_installer();
|
|
4257
|
+
__dirname3 = dirname2(fileURLToPath2(import.meta.url));
|
|
4258
|
+
pkg = JSON.parse(readFileSync2(join2(__dirname3, "..", "..", "package.json"), "utf-8"));
|
|
4229
4259
|
});
|
|
4230
4260
|
|
|
4231
4261
|
// src/cli/index.tsx
|
|
@@ -4702,10 +4732,12 @@ function CategorySelect({ onSelect, onBack }) {
|
|
|
4702
4732
|
return /* @__PURE__ */ jsxDEV2(Box4, {
|
|
4703
4733
|
flexDirection: "column",
|
|
4704
4734
|
children: [
|
|
4705
|
-
/* @__PURE__ */ jsxDEV2(
|
|
4706
|
-
bold: true,
|
|
4735
|
+
/* @__PURE__ */ jsxDEV2(Box4, {
|
|
4707
4736
|
marginBottom: 1,
|
|
4708
|
-
children:
|
|
4737
|
+
children: /* @__PURE__ */ jsxDEV2(Text4, {
|
|
4738
|
+
bold: true,
|
|
4739
|
+
children: "Select a category:"
|
|
4740
|
+
}, undefined, false, undefined, this)
|
|
4709
4741
|
}, undefined, false, undefined, this),
|
|
4710
4742
|
/* @__PURE__ */ jsxDEV2(SelectInput_default, {
|
|
4711
4743
|
items,
|
|
@@ -5268,9 +5300,11 @@ function App({ initialHooks, overwrite = false }) {
|
|
|
5268
5300
|
view === "main" && /* @__PURE__ */ jsxDEV7(Box8, {
|
|
5269
5301
|
flexDirection: "column",
|
|
5270
5302
|
children: [
|
|
5271
|
-
/* @__PURE__ */ jsxDEV7(
|
|
5303
|
+
/* @__PURE__ */ jsxDEV7(Box8, {
|
|
5272
5304
|
marginBottom: 1,
|
|
5273
|
-
children:
|
|
5305
|
+
children: /* @__PURE__ */ jsxDEV7(Text10, {
|
|
5306
|
+
children: "What would you like to do?"
|
|
5307
|
+
}, undefined, false, undefined, this)
|
|
5274
5308
|
}, undefined, false, undefined, this),
|
|
5275
5309
|
/* @__PURE__ */ jsxDEV7(SelectInput_default, {
|
|
5276
5310
|
items: mainMenuItems,
|
|
@@ -5316,11 +5350,13 @@ function App({ initialHooks, overwrite = false }) {
|
|
|
5316
5350
|
view === "done" && /* @__PURE__ */ jsxDEV7(Box8, {
|
|
5317
5351
|
flexDirection: "column",
|
|
5318
5352
|
children: [
|
|
5319
|
-
/* @__PURE__ */ jsxDEV7(
|
|
5320
|
-
bold: true,
|
|
5321
|
-
color: "green",
|
|
5353
|
+
/* @__PURE__ */ jsxDEV7(Box8, {
|
|
5322
5354
|
marginBottom: 1,
|
|
5323
|
-
children:
|
|
5355
|
+
children: /* @__PURE__ */ jsxDEV7(Text10, {
|
|
5356
|
+
bold: true,
|
|
5357
|
+
color: "green",
|
|
5358
|
+
children: "Installation complete!"
|
|
5359
|
+
}, undefined, false, undefined, this)
|
|
5324
5360
|
}, undefined, false, undefined, this),
|
|
5325
5361
|
results.filter((r) => r.success).length > 0 && /* @__PURE__ */ jsxDEV7(Box8, {
|
|
5326
5362
|
flexDirection: "column",
|
|
@@ -5425,11 +5461,11 @@ function resolveScope(options) {
|
|
|
5425
5461
|
return "project";
|
|
5426
5462
|
return "global";
|
|
5427
5463
|
}
|
|
5428
|
-
program2.name("hooks").description("Install
|
|
5464
|
+
program2.name("hooks").description("Install hooks for AI coding agents").version("0.1.1");
|
|
5429
5465
|
program2.command("interactive", { isDefault: true }).alias("i").description("Interactive hook browser").action(() => {
|
|
5430
5466
|
render(/* @__PURE__ */ jsxDEV8(App, {}, undefined, false, undefined, this));
|
|
5431
5467
|
});
|
|
5432
|
-
program2.command("run").argument("<hook>", "Hook to run").description("Execute a hook (called by
|
|
5468
|
+
program2.command("run").argument("<hook>", "Hook to run").description("Execute a hook (called by AI coding agents)").action(async (hook) => {
|
|
5433
5469
|
const meta = getHook(hook);
|
|
5434
5470
|
if (!meta) {
|
|
5435
5471
|
console.error(JSON.stringify({ error: `Hook '${hook}' not found` }));
|
|
@@ -5512,7 +5548,7 @@ Installing hooks (${scope})...
|
|
|
5512
5548
|
console.log(chalk2.dim(`
|
|
5513
5549
|
Registered in ${settingsFile}`));
|
|
5514
5550
|
});
|
|
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
|
|
5551
|
+
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
5552
|
const scope = resolveScope(options);
|
|
5517
5553
|
if (options.registered || options.installed) {
|
|
5518
5554
|
const registered = getRegisteredHooks(scope);
|
|
@@ -5822,11 +5858,11 @@ ${meta.displayName} v${meta.version}
|
|
|
5822
5858
|
return;
|
|
5823
5859
|
}
|
|
5824
5860
|
const generalDocs = {
|
|
5825
|
-
overview: "
|
|
5861
|
+
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
5862
|
events: {
|
|
5827
5863
|
PreToolUse: 'Fires before a tool executes. Can block the operation by returning { "decision": "block" }.',
|
|
5828
5864
|
PostToolUse: "Fires after a tool executes. Runs asynchronously, cannot block.",
|
|
5829
|
-
Stop: "Fires when a
|
|
5865
|
+
Stop: "Fires when a session ends. Useful for notifications and cleanup.",
|
|
5830
5866
|
Notification: "Fires on notification events like context compaction."
|
|
5831
5867
|
},
|
|
5832
5868
|
installation: {
|
|
@@ -5848,7 +5884,7 @@ ${meta.displayName} v${meta.version}
|
|
|
5848
5884
|
howItWorks: {
|
|
5849
5885
|
install: "bun install -g @hasna/hooks",
|
|
5850
5886
|
register: "hooks install gitguard \u2192 writes to ~/.claude/settings.json",
|
|
5851
|
-
execution: "
|
|
5887
|
+
execution: "Agent runs 'hooks run gitguard' \u2192 executes hook from global package",
|
|
5852
5888
|
noFileCopy: "No files are copied to your project. Hooks run from the global @hasna/hooks package."
|
|
5853
5889
|
}
|
|
5854
5890
|
};
|
|
@@ -5894,7 +5930,7 @@ ${meta.displayName} v${meta.version}
|
|
|
5894
5930
|
console.log(` hooks docs --json Machine-readable documentation`);
|
|
5895
5931
|
console.log();
|
|
5896
5932
|
});
|
|
5897
|
-
program2.command("mcp").option("-s, --stdio", "Use stdio transport (for
|
|
5933
|
+
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
5934
|
if (options.stdio) {
|
|
5899
5935
|
const { startStdioServer: startStdioServer2 } = await Promise.resolve().then(() => (init_server(), exports_server));
|
|
5900
5936
|
await startStdioServer2();
|