@hasna/hooks 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/.hooks/index.ts +6 -0
  2. package/bin/index.js +1 -1
  3. package/dist/index.js +366 -0
  4. package/hooks/hook-agentmessages/bin/cli.ts +125 -0
  5. package/package.json +2 -2
  6. package/hooks/hook-agentmessages/src/check-messages.ts +0 -151
  7. package/hooks/hook-agentmessages/src/install.ts +0 -126
  8. package/hooks/hook-agentmessages/src/session-start.ts +0 -255
  9. package/hooks/hook-agentmessages/src/uninstall.ts +0 -89
  10. package/hooks/hook-branchprotect/src/cli.ts +0 -126
  11. package/hooks/hook-branchprotect/src/hook.ts +0 -88
  12. package/hooks/hook-branchprotect/tsconfig.json +0 -25
  13. package/hooks/hook-checkbugs/src/cli.ts +0 -628
  14. package/hooks/hook-checkbugs/src/hook.ts +0 -335
  15. package/hooks/hook-checkbugs/tsconfig.json +0 -15
  16. package/hooks/hook-checkdocs/src/cli.ts +0 -628
  17. package/hooks/hook-checkdocs/src/hook.ts +0 -310
  18. package/hooks/hook-checkdocs/tsconfig.json +0 -15
  19. package/hooks/hook-checkfiles/src/cli.ts +0 -545
  20. package/hooks/hook-checkfiles/src/hook.ts +0 -321
  21. package/hooks/hook-checkfiles/tsconfig.json +0 -15
  22. package/hooks/hook-checklint/src/cli-patch.ts +0 -32
  23. package/hooks/hook-checklint/src/cli.ts +0 -667
  24. package/hooks/hook-checklint/src/hook.ts +0 -473
  25. package/hooks/hook-checklint/tsconfig.json +0 -15
  26. package/hooks/hook-checkpoint/src/cli.ts +0 -191
  27. package/hooks/hook-checkpoint/src/hook.ts +0 -207
  28. package/hooks/hook-checkpoint/tsconfig.json +0 -25
  29. package/hooks/hook-checksecurity/src/cli.ts +0 -601
  30. package/hooks/hook-checksecurity/src/hook.ts +0 -334
  31. package/hooks/hook-checksecurity/tsconfig.json +0 -15
  32. package/hooks/hook-checktasks/src/cli.ts +0 -578
  33. package/hooks/hook-checktasks/src/hook.ts +0 -308
  34. package/hooks/hook-checktasks/tsconfig.json +0 -20
  35. package/hooks/hook-checktests/src/cli.ts +0 -627
  36. package/hooks/hook-checktests/src/hook.ts +0 -334
  37. package/hooks/hook-checktests/tsconfig.json +0 -15
  38. package/hooks/hook-contextrefresh/src/cli.ts +0 -152
  39. package/hooks/hook-contextrefresh/src/hook.ts +0 -148
  40. package/hooks/hook-contextrefresh/tsconfig.json +0 -25
  41. package/hooks/hook-gitguard/src/cli.ts +0 -159
  42. package/hooks/hook-gitguard/src/hook.ts +0 -129
  43. package/hooks/hook-gitguard/tsconfig.json +0 -25
  44. package/hooks/hook-packageage/src/cli.ts +0 -165
  45. package/hooks/hook-packageage/src/hook.ts +0 -177
  46. package/hooks/hook-packageage/tsconfig.json +0 -25
  47. package/hooks/hook-phonenotify/src/cli.ts +0 -196
  48. package/hooks/hook-phonenotify/src/hook.ts +0 -139
  49. package/hooks/hook-phonenotify/tsconfig.json +0 -25
  50. package/hooks/hook-precompact/src/cli.ts +0 -168
  51. package/hooks/hook-precompact/src/hook.ts +0 -122
  52. package/hooks/hook-precompact/tsconfig.json +0 -25
  53. package/src/cli/components/App.tsx +0 -191
  54. package/src/cli/components/CategorySelect.tsx +0 -37
  55. package/src/cli/components/DataTable.tsx +0 -133
  56. package/src/cli/components/Header.tsx +0 -18
  57. package/src/cli/components/HookSelect.tsx +0 -29
  58. package/src/cli/components/InstallProgress.tsx +0 -105
  59. package/src/cli/components/SearchView.tsx +0 -86
  60. package/src/cli/index.tsx +0 -218
  61. package/src/index.ts +0 -31
  62. package/src/lib/installer.ts +0 -288
  63. package/src/lib/registry.ts +0 -205
  64. package/tsconfig.json +0 -17
package/src/cli/index.tsx DELETED
@@ -1,218 +0,0 @@
1
- #!/usr/bin/env bun
2
- import React from "react";
3
- import { render } from "ink";
4
- import { Command } from "commander";
5
- import chalk from "chalk";
6
- import { App } from "./components/App.js";
7
- import {
8
- HOOKS,
9
- CATEGORIES,
10
- getHooksByCategory,
11
- searchHooks,
12
- getHook,
13
- } from "../lib/registry.js";
14
- import {
15
- installHook,
16
- getInstalledHooks,
17
- getRegisteredHooks,
18
- removeHook,
19
- } from "../lib/installer.js";
20
-
21
- const program = new Command();
22
-
23
- program
24
- .name("hooks")
25
- .description("Install Claude Code hooks for your project")
26
- .version("0.0.1");
27
-
28
- // Interactive mode (default)
29
- program
30
- .command("interactive", { isDefault: true })
31
- .alias("i")
32
- .description("Interactive hook browser")
33
- .action(() => {
34
- render(<App />);
35
- });
36
-
37
- // Install command
38
- program
39
- .command("install")
40
- .alias("add")
41
- .argument("[hooks...]", "Hooks to install")
42
- .option("-o, --overwrite", "Overwrite existing hooks", false)
43
- .description("Install one or more hooks")
44
- .action((hooks: string[], options) => {
45
- if (hooks.length === 0) {
46
- render(<App />);
47
- return;
48
- }
49
-
50
- console.log(chalk.bold("\nInstalling hooks...\n"));
51
-
52
- for (const name of hooks) {
53
- const result = installHook(name, { overwrite: options.overwrite });
54
- if (result.success) {
55
- const meta = getHook(name);
56
- console.log(chalk.green(`✓ ${name}`));
57
- if (meta) {
58
- console.log(
59
- chalk.dim(` ${meta.event}${meta.matcher ? ` [${meta.matcher}]` : ""} → hook-${name}`)
60
- );
61
- }
62
- } else {
63
- console.log(chalk.red(`✗ ${name}: ${result.error}`));
64
- }
65
- }
66
-
67
- console.log(chalk.dim("\nHooks installed to .hooks/ and registered in ~/.claude/settings.json"));
68
- });
69
-
70
- // List command
71
- program
72
- .command("list")
73
- .alias("ls")
74
- .option("-c, --category <category>", "Filter by category")
75
- .option("-a, --all", "Show all available hooks", false)
76
- .option("-i, --installed", "Show only installed hooks", false)
77
- .option("-r, --registered", "Show hooks registered in Claude settings", false)
78
- .description("List available or installed hooks")
79
- .action((options) => {
80
- if (options.registered) {
81
- const registered = getRegisteredHooks();
82
- if (registered.length === 0) {
83
- console.log(chalk.dim("No hooks registered in Claude settings"));
84
- return;
85
- }
86
- console.log(chalk.bold(`\nRegistered hooks (${registered.length}):\n`));
87
- for (const name of registered) {
88
- const meta = getHook(name);
89
- console.log(
90
- ` ${chalk.cyan(name)} ${chalk.dim(`[${meta?.event || "unknown"}]`)} - ${meta?.description || ""}`
91
- );
92
- }
93
- return;
94
- }
95
-
96
- if (options.installed) {
97
- const installed = getInstalledHooks();
98
- if (installed.length === 0) {
99
- console.log(chalk.dim("No hooks installed"));
100
- return;
101
- }
102
- console.log(chalk.bold(`\nInstalled hooks (${installed.length}):\n`));
103
- for (const name of installed) {
104
- console.log(` ${name}`);
105
- }
106
- return;
107
- }
108
-
109
- if (options.category) {
110
- const category = CATEGORIES.find(
111
- (c) => c.toLowerCase() === options.category.toLowerCase()
112
- );
113
- if (!category) {
114
- console.log(chalk.red(`Unknown category: ${options.category}`));
115
- console.log(chalk.dim(`Available: ${CATEGORIES.join(", ")}`));
116
- return;
117
- }
118
- const hooks = getHooksByCategory(category);
119
- console.log(chalk.bold(`\n${category} (${hooks.length}):\n`));
120
- for (const h of hooks) {
121
- console.log(
122
- ` ${chalk.cyan(h.name)} ${chalk.dim(`[${h.event}]`)} - ${h.description}`
123
- );
124
- }
125
- return;
126
- }
127
-
128
- // Show all by category
129
- console.log(chalk.bold(`\nAvailable hooks (${HOOKS.length}):\n`));
130
- for (const category of CATEGORIES) {
131
- const hooks = getHooksByCategory(category);
132
- console.log(chalk.bold(`${category} (${hooks.length}):`));
133
- for (const h of hooks) {
134
- console.log(
135
- ` ${chalk.cyan(h.name)} ${chalk.dim(`[${h.event}]`)} - ${h.description}`
136
- );
137
- }
138
- console.log();
139
- }
140
- });
141
-
142
- // Search command
143
- program
144
- .command("search")
145
- .argument("<query>", "Search term")
146
- .description("Search for hooks")
147
- .action((query: string) => {
148
- const results = searchHooks(query);
149
- if (results.length === 0) {
150
- console.log(chalk.dim(`No hooks found for "${query}"`));
151
- return;
152
- }
153
- console.log(chalk.bold(`\nFound ${results.length} hook(s):\n`));
154
- for (const h of results) {
155
- console.log(
156
- ` ${chalk.cyan(h.name)} ${chalk.dim(`[${h.event}] [${h.category}]`)}`
157
- );
158
- console.log(` ${h.description}`);
159
- }
160
- });
161
-
162
- // Remove command
163
- program
164
- .command("remove")
165
- .alias("rm")
166
- .argument("<hook>", "Hook to remove")
167
- .description("Remove an installed hook")
168
- .action((hook: string) => {
169
- const removed = removeHook(hook);
170
- if (removed) {
171
- console.log(chalk.green(`✓ Removed ${hook} (unregistered from Claude settings)`));
172
- } else {
173
- console.log(chalk.red(`✗ ${hook} is not installed`));
174
- }
175
- });
176
-
177
- // Categories command
178
- program
179
- .command("categories")
180
- .description("List all categories")
181
- .action(() => {
182
- console.log(chalk.bold("\nCategories:\n"));
183
- for (const category of CATEGORIES) {
184
- const count = getHooksByCategory(category).length;
185
- console.log(` ${category} (${count})`);
186
- }
187
- });
188
-
189
- // Info command
190
- program
191
- .command("info")
192
- .argument("<hook>", "Hook name")
193
- .description("Show detailed info about a hook")
194
- .action((hook: string) => {
195
- const meta = getHook(hook);
196
- if (!meta) {
197
- console.log(chalk.red(`Hook '${hook}' not found`));
198
- return;
199
- }
200
- console.log(chalk.bold(`\n${meta.displayName}\n`));
201
- console.log(` ${meta.description}`);
202
- console.log();
203
- console.log(` ${chalk.dim("Category:")} ${meta.category}`);
204
- console.log(` ${chalk.dim("Event:")} ${meta.event}`);
205
- console.log(` ${chalk.dim("Matcher:")} ${meta.matcher || "(none)"}`);
206
- console.log(` ${chalk.dim("Tags:")} ${meta.tags.join(", ")}`);
207
- console.log(` ${chalk.dim("Package:")} hook-${meta.name}`);
208
- console.log();
209
-
210
- const registered = getRegisteredHooks();
211
- if (registered.includes(meta.name)) {
212
- console.log(chalk.green(" ● Registered in Claude settings"));
213
- } else {
214
- console.log(chalk.dim(" ○ Not registered"));
215
- }
216
- });
217
-
218
- program.parse();
package/src/index.ts DELETED
@@ -1,31 +0,0 @@
1
- /**
2
- * @hasna/hooks - Open source Claude Code hooks library
3
- *
4
- * Install hooks with a single command:
5
- * npx @hasna/hooks install gitguard branchprotect
6
- *
7
- * Or use the interactive CLI:
8
- * npx @hasna/hooks
9
- */
10
-
11
- export {
12
- HOOKS,
13
- CATEGORIES,
14
- getHook,
15
- getHooksByCategory,
16
- searchHooks,
17
- type HookMeta,
18
- type Category,
19
- } from "./lib/registry.js";
20
-
21
- export {
22
- installHook,
23
- installHooks,
24
- getInstalledHooks,
25
- getRegisteredHooks,
26
- removeHook,
27
- hookExists,
28
- getHookPath,
29
- type InstallResult,
30
- type InstallOptions,
31
- } from "./lib/installer.js";
@@ -1,288 +0,0 @@
1
- /**
2
- * Hook installer - handles copying hooks to user projects and configuring settings
3
- */
4
-
5
- import { existsSync, cpSync, mkdirSync, readFileSync, writeFileSync } from "fs";
6
- import { join, dirname } from "path";
7
- import { homedir } from "os";
8
- import { fileURLToPath } from "url";
9
- import { getHook } from "./registry.js";
10
-
11
- const __dirname = dirname(fileURLToPath(import.meta.url));
12
- const HOOKS_DIR = join(__dirname, "..", "..", "hooks");
13
- const SETTINGS_PATH = join(homedir(), ".claude", "settings.json");
14
-
15
- export interface InstallResult {
16
- hook: string;
17
- success: boolean;
18
- error?: string;
19
- path?: string;
20
- }
21
-
22
- export interface InstallOptions {
23
- targetDir?: string;
24
- overwrite?: boolean;
25
- }
26
-
27
- /**
28
- * Get the path to a hook in the package
29
- */
30
- export function getHookPath(name: string): string {
31
- const hookName = name.startsWith("hook-") ? name : `hook-${name}`;
32
- return join(HOOKS_DIR, hookName);
33
- }
34
-
35
- /**
36
- * Check if a hook exists in the package
37
- */
38
- export function hookExists(name: string): boolean {
39
- return existsSync(getHookPath(name));
40
- }
41
-
42
- /**
43
- * Read Claude settings.json
44
- */
45
- function readSettings(): Record<string, any> {
46
- try {
47
- if (existsSync(SETTINGS_PATH)) {
48
- return JSON.parse(readFileSync(SETTINGS_PATH, "utf-8"));
49
- }
50
- } catch {}
51
- return {};
52
- }
53
-
54
- /**
55
- * Write Claude settings.json
56
- */
57
- function writeSettings(settings: Record<string, any>): void {
58
- const dir = dirname(SETTINGS_PATH);
59
- if (!existsSync(dir)) {
60
- mkdirSync(dir, { recursive: true });
61
- }
62
- writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2) + "\n");
63
- }
64
-
65
- /**
66
- * Install a single hook to the target directory and register in settings
67
- */
68
- export function installHook(
69
- name: string,
70
- options: InstallOptions = {}
71
- ): InstallResult {
72
- const { targetDir = process.cwd(), overwrite = false } = options;
73
-
74
- const hookName = name.startsWith("hook-") ? name : `hook-${name}`;
75
- const shortName = hookName.replace("hook-", "");
76
- const sourcePath = getHookPath(name);
77
- const destDir = join(targetDir, ".hooks");
78
- const destPath = join(destDir, hookName);
79
-
80
- // Check if hook exists in package
81
- if (!existsSync(sourcePath)) {
82
- return {
83
- hook: shortName,
84
- success: false,
85
- error: `Hook '${shortName}' not found`,
86
- };
87
- }
88
-
89
- // Check if already installed
90
- if (existsSync(destPath) && !overwrite) {
91
- return {
92
- hook: shortName,
93
- success: false,
94
- error: `Already installed. Use --overwrite to replace.`,
95
- path: destPath,
96
- };
97
- }
98
-
99
- try {
100
- // Ensure .hooks directory exists
101
- if (!existsSync(destDir)) {
102
- mkdirSync(destDir, { recursive: true });
103
- }
104
-
105
- // Copy hook
106
- cpSync(sourcePath, destPath, { recursive: true });
107
-
108
- // Register hook in Claude settings
109
- registerHookInSettings(shortName);
110
-
111
- // Update .hooks/index.ts
112
- updateHooksIndex(destDir);
113
-
114
- return {
115
- hook: shortName,
116
- success: true,
117
- path: destPath,
118
- };
119
- } catch (error) {
120
- return {
121
- hook: shortName,
122
- success: false,
123
- error: error instanceof Error ? error.message : "Unknown error",
124
- };
125
- }
126
- }
127
-
128
- /**
129
- * Register a hook in ~/.claude/settings.json
130
- */
131
- function registerHookInSettings(name: string): void {
132
- const meta = getHook(name);
133
- if (!meta) return;
134
-
135
- const settings = readSettings();
136
- if (!settings.hooks) settings.hooks = {};
137
-
138
- const eventKey = meta.event;
139
- if (!settings.hooks[eventKey]) settings.hooks[eventKey] = [];
140
-
141
- // Check if already registered
142
- const hookCommand = `hook-${name}`;
143
- const existing = settings.hooks[eventKey].find((entry: any) =>
144
- entry.hooks?.some((h: any) => h.command?.includes(hookCommand))
145
- );
146
- if (existing) return;
147
-
148
- const entry: Record<string, any> = {
149
- hooks: [{ type: "command", command: hookCommand }],
150
- };
151
- if (meta.matcher) {
152
- entry.matcher = meta.matcher;
153
- }
154
-
155
- settings.hooks[eventKey].push(entry);
156
- writeSettings(settings);
157
- }
158
-
159
- /**
160
- * Unregister a hook from ~/.claude/settings.json
161
- */
162
- function unregisterHookFromSettings(name: string): void {
163
- const meta = getHook(name);
164
- if (!meta) return;
165
-
166
- const settings = readSettings();
167
- if (!settings.hooks) return;
168
-
169
- const eventKey = meta.event;
170
- if (!settings.hooks[eventKey]) return;
171
-
172
- const hookCommand = `hook-${name}`;
173
- settings.hooks[eventKey] = settings.hooks[eventKey].filter(
174
- (entry: any) =>
175
- !entry.hooks?.some((h: any) => h.command?.includes(hookCommand))
176
- );
177
-
178
- // Clean up empty arrays
179
- if (settings.hooks[eventKey].length === 0) {
180
- delete settings.hooks[eventKey];
181
- }
182
- if (Object.keys(settings.hooks).length === 0) {
183
- delete settings.hooks;
184
- }
185
-
186
- writeSettings(settings);
187
- }
188
-
189
- /**
190
- * Install multiple hooks
191
- */
192
- export function installHooks(
193
- names: string[],
194
- options: InstallOptions = {}
195
- ): InstallResult[] {
196
- return names.map((name) => installHook(name, options));
197
- }
198
-
199
- /**
200
- * Update the .hooks/index.ts file to export all installed hooks
201
- */
202
- function updateHooksIndex(hooksDir: string): void {
203
- const indexPath = join(hooksDir, "index.ts");
204
- const { readdirSync } = require("fs");
205
- const hooks = readdirSync(hooksDir).filter(
206
- (f: string) => f.startsWith("hook-") && !f.includes(".")
207
- );
208
-
209
- const exports = hooks
210
- .map((h: string) => {
211
- const name = h.replace("hook-", "");
212
- return `export * as ${name} from './${h}/src/index.js';`;
213
- })
214
- .join("\n");
215
-
216
- const content = `/**
217
- * Auto-generated index of installed hooks
218
- * Do not edit manually - run 'hooks install' to update
219
- */
220
-
221
- ${exports}
222
- `;
223
-
224
- writeFileSync(indexPath, content);
225
- }
226
-
227
- /**
228
- * Get list of installed hooks in a directory
229
- */
230
- export function getInstalledHooks(targetDir: string = process.cwd()): string[] {
231
- const hooksDir = join(targetDir, ".hooks");
232
-
233
- if (!existsSync(hooksDir)) {
234
- return [];
235
- }
236
-
237
- const { readdirSync, statSync } = require("fs");
238
- return readdirSync(hooksDir)
239
- .filter((f: string) => {
240
- const fullPath = join(hooksDir, f);
241
- return f.startsWith("hook-") && statSync(fullPath).isDirectory();
242
- })
243
- .map((f: string) => f.replace("hook-", ""));
244
- }
245
-
246
- /**
247
- * Check which hooks are registered in Claude settings
248
- */
249
- export function getRegisteredHooks(): string[] {
250
- const settings = readSettings();
251
- if (!settings.hooks) return [];
252
-
253
- const registered: string[] = [];
254
- for (const eventKey of Object.keys(settings.hooks)) {
255
- for (const entry of settings.hooks[eventKey]) {
256
- for (const hook of entry.hooks || []) {
257
- const match = hook.command?.match(/hook-(\w+)/);
258
- if (match) {
259
- registered.push(match[1]);
260
- }
261
- }
262
- }
263
- }
264
- return [...new Set(registered)];
265
- }
266
-
267
- /**
268
- * Remove an installed hook
269
- */
270
- export function removeHook(
271
- name: string,
272
- targetDir: string = process.cwd()
273
- ): boolean {
274
- const { rmSync } = require("fs");
275
- const hookName = name.startsWith("hook-") ? name : `hook-${name}`;
276
- const shortName = hookName.replace("hook-", "");
277
- const hooksDir = join(targetDir, ".hooks");
278
- const hookPath = join(hooksDir, hookName);
279
-
280
- if (!existsSync(hookPath)) {
281
- return false;
282
- }
283
-
284
- rmSync(hookPath, { recursive: true });
285
- unregisterHookFromSettings(shortName);
286
- updateHooksIndex(hooksDir);
287
- return true;
288
- }