@hasna/hooks 0.0.7 → 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 +240 -42
- package/dist/index.js +228 -30
- package/hooks/hook-autoformat/README.md +39 -0
- package/hooks/hook-autoformat/package.json +58 -0
- package/hooks/hook-autoformat/src/hook.ts +223 -0
- package/hooks/hook-autostage/README.md +70 -0
- package/hooks/hook-autostage/package.json +12 -0
- package/hooks/hook-autostage/src/hook.ts +167 -0
- package/hooks/hook-commandlog/README.md +45 -0
- package/hooks/hook-commandlog/package.json +12 -0
- package/hooks/hook-commandlog/src/hook.ts +92 -0
- package/hooks/hook-costwatch/README.md +61 -0
- package/hooks/hook-costwatch/package.json +12 -0
- package/hooks/hook-costwatch/src/hook.ts +178 -0
- package/hooks/hook-desktopnotify/README.md +50 -0
- package/hooks/hook-desktopnotify/package.json +57 -0
- package/hooks/hook-desktopnotify/src/hook.ts +112 -0
- package/hooks/hook-envsetup/README.md +40 -0
- package/hooks/hook-envsetup/package.json +58 -0
- package/hooks/hook-envsetup/src/hook.ts +197 -0
- package/hooks/hook-errornotify/README.md +66 -0
- package/hooks/hook-errornotify/package.json +12 -0
- package/hooks/hook-errornotify/src/hook.ts +197 -0
- package/hooks/hook-permissionguard/README.md +48 -0
- package/hooks/hook-permissionguard/package.json +58 -0
- package/hooks/hook-permissionguard/src/hook.ts +268 -0
- package/hooks/hook-promptguard/README.md +64 -0
- package/hooks/hook-promptguard/package.json +12 -0
- package/hooks/hook-promptguard/src/hook.ts +200 -0
- package/hooks/hook-protectfiles/README.md +62 -0
- package/hooks/hook-protectfiles/package.json +58 -0
- package/hooks/hook-protectfiles/src/hook.ts +267 -0
- package/hooks/hook-sessionlog/README.md +48 -0
- package/hooks/hook-sessionlog/package.json +12 -0
- package/hooks/hook-sessionlog/src/hook.ts +100 -0
- package/hooks/hook-slacknotify/README.md +62 -0
- package/hooks/hook-slacknotify/package.json +12 -0
- package/hooks/hook-slacknotify/src/hook.ts +146 -0
- package/hooks/hook-soundnotify/README.md +63 -0
- package/hooks/hook-soundnotify/package.json +12 -0
- package/hooks/hook-soundnotify/src/hook.ts +173 -0
- package/hooks/hook-taskgate/README.md +62 -0
- package/hooks/hook-taskgate/package.json +12 -0
- package/hooks/hook-taskgate/src/hook.ts +169 -0
- package/hooks/hook-tddguard/README.md +50 -0
- package/hooks/hook-tddguard/package.json +12 -0
- package/hooks/hook-tddguard/src/hook.ts +263 -0
- package/package.json +3 -3
package/bin/index.js
CHANGED
|
@@ -1886,7 +1886,12 @@ var init_registry = __esm(() => {
|
|
|
1886
1886
|
"Code Quality",
|
|
1887
1887
|
"Security",
|
|
1888
1888
|
"Notifications",
|
|
1889
|
-
"Context Management"
|
|
1889
|
+
"Context Management",
|
|
1890
|
+
"Workflow Automation",
|
|
1891
|
+
"Environment",
|
|
1892
|
+
"Permissions",
|
|
1893
|
+
"Observability",
|
|
1894
|
+
"Agent Teams"
|
|
1890
1895
|
];
|
|
1891
1896
|
HOOKS = [
|
|
1892
1897
|
{
|
|
@@ -2038,6 +2043,156 @@ var init_registry = __esm(() => {
|
|
|
2038
2043
|
event: "Notification",
|
|
2039
2044
|
matcher: "",
|
|
2040
2045
|
tags: ["context", "compaction", "state", "backup"]
|
|
2046
|
+
},
|
|
2047
|
+
{
|
|
2048
|
+
name: "autoformat",
|
|
2049
|
+
displayName: "Auto Format",
|
|
2050
|
+
description: "Runs project formatter (Prettier, Biome, Ruff, Black, gofmt) after file edits",
|
|
2051
|
+
version: "0.1.0",
|
|
2052
|
+
category: "Workflow Automation",
|
|
2053
|
+
event: "PostToolUse",
|
|
2054
|
+
matcher: "Edit|Write",
|
|
2055
|
+
tags: ["format", "prettier", "biome", "ruff", "black", "gofmt", "style"]
|
|
2056
|
+
},
|
|
2057
|
+
{
|
|
2058
|
+
name: "autostage",
|
|
2059
|
+
displayName: "Auto Stage",
|
|
2060
|
+
description: "Automatically git-stages files after Claude edits them",
|
|
2061
|
+
version: "0.1.0",
|
|
2062
|
+
category: "Workflow Automation",
|
|
2063
|
+
event: "PostToolUse",
|
|
2064
|
+
matcher: "Edit|Write",
|
|
2065
|
+
tags: ["git", "stage", "add", "auto"]
|
|
2066
|
+
},
|
|
2067
|
+
{
|
|
2068
|
+
name: "tddguard",
|
|
2069
|
+
displayName: "TDD Guard",
|
|
2070
|
+
description: "Blocks implementation edits unless corresponding test files exist",
|
|
2071
|
+
version: "0.1.0",
|
|
2072
|
+
category: "Workflow Automation",
|
|
2073
|
+
event: "PreToolUse",
|
|
2074
|
+
matcher: "Edit|Write",
|
|
2075
|
+
tags: ["tdd", "tests", "red-green-refactor", "enforcement"]
|
|
2076
|
+
},
|
|
2077
|
+
{
|
|
2078
|
+
name: "envsetup",
|
|
2079
|
+
displayName: "Env Setup",
|
|
2080
|
+
description: "Warns when nvm, virtualenv, asdf, or rbenv may need activation before commands",
|
|
2081
|
+
version: "0.1.0",
|
|
2082
|
+
category: "Environment",
|
|
2083
|
+
event: "PreToolUse",
|
|
2084
|
+
matcher: "Bash",
|
|
2085
|
+
tags: ["nvm", "virtualenv", "asdf", "rbenv", "environment", "python", "node"]
|
|
2086
|
+
},
|
|
2087
|
+
{
|
|
2088
|
+
name: "permissionguard",
|
|
2089
|
+
displayName: "Permission Guard",
|
|
2090
|
+
description: "Auto-approves safe read-only commands and blocks dangerous operations",
|
|
2091
|
+
version: "0.1.0",
|
|
2092
|
+
category: "Permissions",
|
|
2093
|
+
event: "PreToolUse",
|
|
2094
|
+
matcher: "Bash",
|
|
2095
|
+
tags: ["permission", "allowlist", "blocklist", "safety", "auto-approve"]
|
|
2096
|
+
},
|
|
2097
|
+
{
|
|
2098
|
+
name: "protectfiles",
|
|
2099
|
+
displayName: "Protect Files",
|
|
2100
|
+
description: "Blocks access to .env, secrets, SSH keys, and lock files",
|
|
2101
|
+
version: "0.1.0",
|
|
2102
|
+
category: "Permissions",
|
|
2103
|
+
event: "PreToolUse",
|
|
2104
|
+
matcher: "Edit|Write|Read|Bash",
|
|
2105
|
+
tags: ["security", "env", "secrets", "keys", "lock-files", "protect"]
|
|
2106
|
+
},
|
|
2107
|
+
{
|
|
2108
|
+
name: "promptguard",
|
|
2109
|
+
displayName: "Prompt Guard",
|
|
2110
|
+
description: "Blocks prompt injection attempts and credential access requests",
|
|
2111
|
+
version: "0.1.0",
|
|
2112
|
+
category: "Permissions",
|
|
2113
|
+
event: "PreToolUse",
|
|
2114
|
+
matcher: "",
|
|
2115
|
+
tags: ["prompt", "injection", "security", "validation", "guard"]
|
|
2116
|
+
},
|
|
2117
|
+
{
|
|
2118
|
+
name: "desktopnotify",
|
|
2119
|
+
displayName: "Desktop Notify",
|
|
2120
|
+
description: "Sends native desktop notifications via osascript (macOS) or notify-send (Linux)",
|
|
2121
|
+
version: "0.1.0",
|
|
2122
|
+
category: "Notifications",
|
|
2123
|
+
event: "Stop",
|
|
2124
|
+
matcher: "",
|
|
2125
|
+
tags: ["notification", "desktop", "macos", "linux", "native"]
|
|
2126
|
+
},
|
|
2127
|
+
{
|
|
2128
|
+
name: "slacknotify",
|
|
2129
|
+
displayName: "Slack Notify",
|
|
2130
|
+
description: "Sends Slack webhook notifications when Claude finishes",
|
|
2131
|
+
version: "0.1.0",
|
|
2132
|
+
category: "Notifications",
|
|
2133
|
+
event: "Stop",
|
|
2134
|
+
matcher: "",
|
|
2135
|
+
tags: ["notification", "slack", "webhook", "team"]
|
|
2136
|
+
},
|
|
2137
|
+
{
|
|
2138
|
+
name: "soundnotify",
|
|
2139
|
+
displayName: "Sound Notify",
|
|
2140
|
+
description: "Plays a system sound when Claude finishes (macOS/Linux)",
|
|
2141
|
+
version: "0.1.0",
|
|
2142
|
+
category: "Notifications",
|
|
2143
|
+
event: "Stop",
|
|
2144
|
+
matcher: "",
|
|
2145
|
+
tags: ["notification", "sound", "audio", "alert"]
|
|
2146
|
+
},
|
|
2147
|
+
{
|
|
2148
|
+
name: "sessionlog",
|
|
2149
|
+
displayName: "Session Log",
|
|
2150
|
+
description: "Logs every tool call to .claude/session-log-<date>.jsonl",
|
|
2151
|
+
version: "0.1.0",
|
|
2152
|
+
category: "Observability",
|
|
2153
|
+
event: "PostToolUse",
|
|
2154
|
+
matcher: "",
|
|
2155
|
+
tags: ["logging", "audit", "session", "history", "jsonl"]
|
|
2156
|
+
},
|
|
2157
|
+
{
|
|
2158
|
+
name: "commandlog",
|
|
2159
|
+
displayName: "Command Log",
|
|
2160
|
+
description: "Logs every bash command Claude runs to .claude/commands.log",
|
|
2161
|
+
version: "0.1.0",
|
|
2162
|
+
category: "Observability",
|
|
2163
|
+
event: "PostToolUse",
|
|
2164
|
+
matcher: "Bash",
|
|
2165
|
+
tags: ["logging", "bash", "commands", "audit"]
|
|
2166
|
+
},
|
|
2167
|
+
{
|
|
2168
|
+
name: "costwatch",
|
|
2169
|
+
displayName: "Cost Watch",
|
|
2170
|
+
description: "Estimates session token usage and warns when budget threshold is exceeded",
|
|
2171
|
+
version: "0.1.0",
|
|
2172
|
+
category: "Observability",
|
|
2173
|
+
event: "Stop",
|
|
2174
|
+
matcher: "",
|
|
2175
|
+
tags: ["cost", "tokens", "budget", "usage", "monitoring"]
|
|
2176
|
+
},
|
|
2177
|
+
{
|
|
2178
|
+
name: "errornotify",
|
|
2179
|
+
displayName: "Error Notify",
|
|
2180
|
+
description: "Detects tool failures and logs errors to .claude/errors.log",
|
|
2181
|
+
version: "0.1.0",
|
|
2182
|
+
category: "Observability",
|
|
2183
|
+
event: "PostToolUse",
|
|
2184
|
+
matcher: "",
|
|
2185
|
+
tags: ["errors", "failures", "logging", "debugging"]
|
|
2186
|
+
},
|
|
2187
|
+
{
|
|
2188
|
+
name: "taskgate",
|
|
2189
|
+
displayName: "Task Gate",
|
|
2190
|
+
description: "Validates task completion criteria before allowing tasks to be marked done",
|
|
2191
|
+
version: "0.1.0",
|
|
2192
|
+
category: "Agent Teams",
|
|
2193
|
+
event: "PostToolUse",
|
|
2194
|
+
matcher: "",
|
|
2195
|
+
tags: ["tasks", "completion", "gate", "quality", "agent-teams"]
|
|
2041
2196
|
}
|
|
2042
2197
|
];
|
|
2043
2198
|
});
|
|
@@ -3687,11 +3842,17 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
|
3687
3842
|
import { join, dirname } from "path";
|
|
3688
3843
|
import { homedir } from "os";
|
|
3689
3844
|
import { fileURLToPath } from "url";
|
|
3690
|
-
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);
|
|
3691
3852
|
if (scope === "project") {
|
|
3692
|
-
return join(process.cwd(),
|
|
3853
|
+
return join(process.cwd(), dir, "settings.json");
|
|
3693
3854
|
}
|
|
3694
|
-
return join(homedir(),
|
|
3855
|
+
return join(homedir(), dir, "settings.json");
|
|
3695
3856
|
}
|
|
3696
3857
|
function getHookPath(name) {
|
|
3697
3858
|
const hookName = name.startsWith("hook-") ? name : `hook-${name}`;
|
|
@@ -3700,8 +3861,8 @@ function getHookPath(name) {
|
|
|
3700
3861
|
function hookExists(name) {
|
|
3701
3862
|
return existsSync(getHookPath(name));
|
|
3702
3863
|
}
|
|
3703
|
-
function readSettings(scope = "global") {
|
|
3704
|
-
const path = getSettingsPath(scope);
|
|
3864
|
+
function readSettings(scope = "global", target = "claude") {
|
|
3865
|
+
const path = getSettingsPath(scope, target);
|
|
3705
3866
|
try {
|
|
3706
3867
|
if (existsSync(path)) {
|
|
3707
3868
|
return JSON.parse(readFileSync(path, "utf-8"));
|
|
@@ -3709,8 +3870,8 @@ function readSettings(scope = "global") {
|
|
|
3709
3870
|
} catch {}
|
|
3710
3871
|
return {};
|
|
3711
3872
|
}
|
|
3712
|
-
function writeSettings(settings, scope = "global") {
|
|
3713
|
-
const path = getSettingsPath(scope);
|
|
3873
|
+
function writeSettings(settings, scope = "global", target = "claude") {
|
|
3874
|
+
const path = getSettingsPath(scope, target);
|
|
3714
3875
|
const dir = dirname(path);
|
|
3715
3876
|
if (!existsSync(dir)) {
|
|
3716
3877
|
mkdirSync(dir, { recursive: true });
|
|
@@ -3718,36 +3879,48 @@ function writeSettings(settings, scope = "global") {
|
|
|
3718
3879
|
writeFileSync(path, JSON.stringify(settings, null, 2) + `
|
|
3719
3880
|
`);
|
|
3720
3881
|
}
|
|
3721
|
-
function
|
|
3722
|
-
|
|
3882
|
+
function getTargetEventName(internalEvent, target) {
|
|
3883
|
+
return EVENT_MAP[target]?.[internalEvent] || internalEvent;
|
|
3884
|
+
}
|
|
3885
|
+
function installForTarget(name, scope, overwrite, target) {
|
|
3723
3886
|
const hookName = name.startsWith("hook-") ? name : `hook-${name}`;
|
|
3724
3887
|
const shortName = hookName.replace("hook-", "");
|
|
3725
3888
|
if (!hookExists(shortName)) {
|
|
3726
|
-
return { hook: shortName, success: false, error: `Hook '${shortName}' not found
|
|
3889
|
+
return { hook: shortName, success: false, error: `Hook '${shortName}' not found`, target };
|
|
3727
3890
|
}
|
|
3728
|
-
const registered =
|
|
3891
|
+
const registered = getRegisteredHooksForTarget(scope, target);
|
|
3729
3892
|
if (registered.includes(shortName) && !overwrite) {
|
|
3730
|
-
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 };
|
|
3731
3894
|
}
|
|
3732
3895
|
try {
|
|
3733
|
-
registerHook(shortName, scope);
|
|
3734
|
-
return { hook: shortName, success: true, scope };
|
|
3896
|
+
registerHook(shortName, scope, target);
|
|
3897
|
+
return { hook: shortName, success: true, scope, target };
|
|
3735
3898
|
} catch (error) {
|
|
3736
3899
|
return {
|
|
3737
3900
|
hook: shortName,
|
|
3738
3901
|
success: false,
|
|
3739
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
3902
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
3903
|
+
target
|
|
3740
3904
|
};
|
|
3741
3905
|
}
|
|
3742
3906
|
}
|
|
3743
|
-
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") {
|
|
3744
3917
|
const meta = getHook(name);
|
|
3745
3918
|
if (!meta)
|
|
3746
3919
|
return;
|
|
3747
|
-
const settings = readSettings(scope);
|
|
3920
|
+
const settings = readSettings(scope, target);
|
|
3748
3921
|
if (!settings.hooks)
|
|
3749
3922
|
settings.hooks = {};
|
|
3750
|
-
const eventKey = meta.event;
|
|
3923
|
+
const eventKey = getTargetEventName(meta.event, target);
|
|
3751
3924
|
if (!settings.hooks[eventKey])
|
|
3752
3925
|
settings.hooks[eventKey] = [];
|
|
3753
3926
|
const hookCommand = `hooks run ${name}`;
|
|
@@ -3759,16 +3932,16 @@ function registerHook(name, scope = "global") {
|
|
|
3759
3932
|
entry.matcher = meta.matcher;
|
|
3760
3933
|
}
|
|
3761
3934
|
settings.hooks[eventKey].push(entry);
|
|
3762
|
-
writeSettings(settings, scope);
|
|
3935
|
+
writeSettings(settings, scope, target);
|
|
3763
3936
|
}
|
|
3764
|
-
function unregisterHook(name, scope = "global") {
|
|
3937
|
+
function unregisterHook(name, scope = "global", target = "claude") {
|
|
3765
3938
|
const meta = getHook(name);
|
|
3766
3939
|
if (!meta)
|
|
3767
3940
|
return;
|
|
3768
|
-
const settings = readSettings(scope);
|
|
3941
|
+
const settings = readSettings(scope, target);
|
|
3769
3942
|
if (!settings.hooks)
|
|
3770
3943
|
return;
|
|
3771
|
-
const eventKey = meta.event;
|
|
3944
|
+
const eventKey = getTargetEventName(meta.event, target);
|
|
3772
3945
|
if (!settings.hooks[eventKey])
|
|
3773
3946
|
return;
|
|
3774
3947
|
const hookCommand = `hooks run ${name}`;
|
|
@@ -3779,10 +3952,10 @@ function unregisterHook(name, scope = "global") {
|
|
|
3779
3952
|
if (Object.keys(settings.hooks).length === 0) {
|
|
3780
3953
|
delete settings.hooks;
|
|
3781
3954
|
}
|
|
3782
|
-
writeSettings(settings, scope);
|
|
3955
|
+
writeSettings(settings, scope, target);
|
|
3783
3956
|
}
|
|
3784
|
-
function
|
|
3785
|
-
const settings = readSettings(scope);
|
|
3957
|
+
function getRegisteredHooksForTarget(scope = "global", target = "claude") {
|
|
3958
|
+
const settings = readSettings(scope, target);
|
|
3786
3959
|
if (!settings.hooks)
|
|
3787
3960
|
return [];
|
|
3788
3961
|
const registered = [];
|
|
@@ -3800,24 +3973,49 @@ function getRegisteredHooks(scope = "global") {
|
|
|
3800
3973
|
}
|
|
3801
3974
|
return [...new Set(registered)];
|
|
3802
3975
|
}
|
|
3976
|
+
function getRegisteredHooks(scope = "global") {
|
|
3977
|
+
return getRegisteredHooksForTarget(scope, "claude");
|
|
3978
|
+
}
|
|
3803
3979
|
function getInstalledHooks(scope = "global") {
|
|
3804
3980
|
return getRegisteredHooks(scope);
|
|
3805
3981
|
}
|
|
3806
|
-
function removeHook(name, scope = "global") {
|
|
3982
|
+
function removeHook(name, scope = "global", target = "claude") {
|
|
3807
3983
|
const hookName = name.startsWith("hook-") ? name : `hook-${name}`;
|
|
3808
3984
|
const shortName = hookName.replace("hook-", "");
|
|
3809
|
-
|
|
3810
|
-
|
|
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)) {
|
|
3811
3995
|
return false;
|
|
3812
3996
|
}
|
|
3813
|
-
unregisterHook(
|
|
3997
|
+
unregisterHook(name, scope, target);
|
|
3814
3998
|
return true;
|
|
3815
3999
|
}
|
|
3816
|
-
var __dirname2, HOOKS_DIR;
|
|
4000
|
+
var __dirname2, HOOKS_DIR, EVENT_MAP;
|
|
3817
4001
|
var init_installer = __esm(() => {
|
|
3818
4002
|
init_registry();
|
|
3819
4003
|
__dirname2 = dirname(fileURLToPath(import.meta.url));
|
|
3820
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
|
+
};
|
|
3821
4019
|
});
|
|
3822
4020
|
|
|
3823
4021
|
// src/mcp/server.ts
|
|
@@ -3840,7 +4038,7 @@ function createHooksServer() {
|
|
|
3840
4038
|
name: "@hasna/hooks",
|
|
3841
4039
|
version: "0.0.7"
|
|
3842
4040
|
});
|
|
3843
|
-
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 }) => {
|
|
3844
4042
|
if (category) {
|
|
3845
4043
|
const cat = CATEGORIES.find((c) => c.toLowerCase() === category.toLowerCase());
|
|
3846
4044
|
if (!cat) {
|
|
@@ -3867,7 +4065,7 @@ function createHooksServer() {
|
|
|
3867
4065
|
const projectInstalled = getRegisteredHooks("project").includes(meta.name);
|
|
3868
4066
|
return { content: [{ type: "text", text: JSON.stringify({ ...meta, global: globalInstalled, project: projectInstalled }) }] };
|
|
3869
4067
|
});
|
|
3870
|
-
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", {
|
|
3871
4069
|
hooks: z.array(z.string()).describe("Hook names to install"),
|
|
3872
4070
|
scope: z.enum(["global", "project"]).default("global").describe("Install scope"),
|
|
3873
4071
|
overwrite: z.boolean().default(false).describe("Overwrite if already installed")
|
|
@@ -3927,7 +4125,7 @@ function createHooksServer() {
|
|
|
3927
4125
|
}]
|
|
3928
4126
|
};
|
|
3929
4127
|
});
|
|
3930
|
-
server.tool("hooks_remove", "Remove (unregister) a hook from
|
|
4128
|
+
server.tool("hooks_remove", "Remove (unregister) a hook from agent settings", {
|
|
3931
4129
|
name: z.string().describe("Hook name to remove"),
|
|
3932
4130
|
scope: z.enum(["global", "project"]).default("global").describe("Scope to remove from")
|
|
3933
4131
|
}, async ({ name, scope }) => {
|
|
@@ -3998,7 +4196,7 @@ function createHooksServer() {
|
|
|
3998
4196
|
content: [{
|
|
3999
4197
|
type: "text",
|
|
4000
4198
|
text: JSON.stringify({
|
|
4001
|
-
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.",
|
|
4002
4200
|
events: {
|
|
4003
4201
|
PreToolUse: "Fires before a tool executes. Can block the operation.",
|
|
4004
4202
|
PostToolUse: "Fires after a tool executes. Runs asynchronously.",
|
|
@@ -5270,11 +5468,11 @@ function resolveScope(options) {
|
|
|
5270
5468
|
return "project";
|
|
5271
5469
|
return "global";
|
|
5272
5470
|
}
|
|
5273
|
-
program2.name("hooks").description("Install
|
|
5471
|
+
program2.name("hooks").description("Install hooks for AI coding agents").version("0.1.1");
|
|
5274
5472
|
program2.command("interactive", { isDefault: true }).alias("i").description("Interactive hook browser").action(() => {
|
|
5275
5473
|
render(/* @__PURE__ */ jsxDEV8(App, {}, undefined, false, undefined, this));
|
|
5276
5474
|
});
|
|
5277
|
-
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) => {
|
|
5278
5476
|
const meta = getHook(hook);
|
|
5279
5477
|
if (!meta) {
|
|
5280
5478
|
console.error(JSON.stringify({ error: `Hook '${hook}' not found` }));
|
|
@@ -5357,7 +5555,7 @@ Installing hooks (${scope})...
|
|
|
5357
5555
|
console.log(chalk2.dim(`
|
|
5358
5556
|
Registered in ${settingsFile}`));
|
|
5359
5557
|
});
|
|
5360
|
-
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) => {
|
|
5361
5559
|
const scope = resolveScope(options);
|
|
5362
5560
|
if (options.registered || options.installed) {
|
|
5363
5561
|
const registered = getRegisteredHooks(scope);
|
|
@@ -5667,11 +5865,11 @@ ${meta.displayName} v${meta.version}
|
|
|
5667
5865
|
return;
|
|
5668
5866
|
}
|
|
5669
5867
|
const generalDocs = {
|
|
5670
|
-
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.",
|
|
5671
5869
|
events: {
|
|
5672
5870
|
PreToolUse: 'Fires before a tool executes. Can block the operation by returning { "decision": "block" }.',
|
|
5673
5871
|
PostToolUse: "Fires after a tool executes. Runs asynchronously, cannot block.",
|
|
5674
|
-
Stop: "Fires when a
|
|
5872
|
+
Stop: "Fires when a session ends. Useful for notifications and cleanup.",
|
|
5675
5873
|
Notification: "Fires on notification events like context compaction."
|
|
5676
5874
|
},
|
|
5677
5875
|
installation: {
|
|
@@ -5693,7 +5891,7 @@ ${meta.displayName} v${meta.version}
|
|
|
5693
5891
|
howItWorks: {
|
|
5694
5892
|
install: "bun install -g @hasna/hooks",
|
|
5695
5893
|
register: "hooks install gitguard \u2192 writes to ~/.claude/settings.json",
|
|
5696
|
-
execution: "
|
|
5894
|
+
execution: "Agent runs 'hooks run gitguard' \u2192 executes hook from global package",
|
|
5697
5895
|
noFileCopy: "No files are copied to your project. Hooks run from the global @hasna/hooks package."
|
|
5698
5896
|
}
|
|
5699
5897
|
};
|
|
@@ -5739,7 +5937,7 @@ ${meta.displayName} v${meta.version}
|
|
|
5739
5937
|
console.log(` hooks docs --json Machine-readable documentation`);
|
|
5740
5938
|
console.log();
|
|
5741
5939
|
});
|
|
5742
|
-
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) => {
|
|
5743
5941
|
if (options.stdio) {
|
|
5744
5942
|
const { startStdioServer: startStdioServer2 } = await Promise.resolve().then(() => (init_server(), exports_server));
|
|
5745
5943
|
await startStdioServer2();
|