@hasna/hooks 0.0.5 → 0.0.7
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/.claude/settings.json +24 -0
- package/bin/index.js +710 -462
- package/dist/index.js +46 -94
- package/package.json +3 -2
- package/.hooks/index.ts +0 -6
package/dist/index.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var __require = import.meta.require;
|
|
3
|
-
|
|
4
2
|
// src/lib/registry.ts
|
|
5
3
|
var CATEGORIES = [
|
|
6
4
|
"Git Safety",
|
|
@@ -172,13 +170,18 @@ function getHook(name) {
|
|
|
172
170
|
return HOOKS.find((h) => h.name === name);
|
|
173
171
|
}
|
|
174
172
|
// src/lib/installer.ts
|
|
175
|
-
import { existsSync,
|
|
173
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
176
174
|
import { join, dirname } from "path";
|
|
177
175
|
import { homedir } from "os";
|
|
178
176
|
import { fileURLToPath } from "url";
|
|
179
177
|
var __dirname2 = dirname(fileURLToPath(import.meta.url));
|
|
180
178
|
var HOOKS_DIR = existsSync(join(__dirname2, "..", "..", "hooks", "hook-gitguard")) ? join(__dirname2, "..", "..", "hooks") : join(__dirname2, "..", "hooks");
|
|
181
|
-
|
|
179
|
+
function getSettingsPath(scope = "global") {
|
|
180
|
+
if (scope === "project") {
|
|
181
|
+
return join(process.cwd(), ".claude", "settings.json");
|
|
182
|
+
}
|
|
183
|
+
return join(homedir(), ".claude", "settings.json");
|
|
184
|
+
}
|
|
182
185
|
function getHookPath(name) {
|
|
183
186
|
const hookName = name.startsWith("hook-") ? name : `hook-${name}`;
|
|
184
187
|
return join(HOOKS_DIR, hookName);
|
|
@@ -186,60 +189,38 @@ function getHookPath(name) {
|
|
|
186
189
|
function hookExists(name) {
|
|
187
190
|
return existsSync(getHookPath(name));
|
|
188
191
|
}
|
|
189
|
-
function readSettings() {
|
|
192
|
+
function readSettings(scope = "global") {
|
|
193
|
+
const path = getSettingsPath(scope);
|
|
190
194
|
try {
|
|
191
|
-
if (existsSync(
|
|
192
|
-
return JSON.parse(readFileSync(
|
|
195
|
+
if (existsSync(path)) {
|
|
196
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
193
197
|
}
|
|
194
198
|
} catch {}
|
|
195
199
|
return {};
|
|
196
200
|
}
|
|
197
|
-
function writeSettings(settings) {
|
|
198
|
-
const
|
|
201
|
+
function writeSettings(settings, scope = "global") {
|
|
202
|
+
const path = getSettingsPath(scope);
|
|
203
|
+
const dir = dirname(path);
|
|
199
204
|
if (!existsSync(dir)) {
|
|
200
205
|
mkdirSync(dir, { recursive: true });
|
|
201
206
|
}
|
|
202
|
-
writeFileSync(
|
|
207
|
+
writeFileSync(path, JSON.stringify(settings, null, 2) + `
|
|
203
208
|
`);
|
|
204
209
|
}
|
|
205
210
|
function installHook(name, options = {}) {
|
|
206
|
-
const {
|
|
211
|
+
const { scope = "global", overwrite = false } = options;
|
|
207
212
|
const hookName = name.startsWith("hook-") ? name : `hook-${name}`;
|
|
208
213
|
const shortName = hookName.replace("hook-", "");
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
const destPath = join(destDir, hookName);
|
|
212
|
-
if (!existsSync(sourcePath)) {
|
|
213
|
-
return {
|
|
214
|
-
hook: shortName,
|
|
215
|
-
success: false,
|
|
216
|
-
error: `Hook '${shortName}' not found`
|
|
217
|
-
};
|
|
214
|
+
if (!hookExists(shortName)) {
|
|
215
|
+
return { hook: shortName, success: false, error: `Hook '${shortName}' not found` };
|
|
218
216
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
success: false,
|
|
223
|
-
error: `Already installed. Use --overwrite to replace.`,
|
|
224
|
-
path: destPath
|
|
225
|
-
};
|
|
217
|
+
const registered = getRegisteredHooks(scope);
|
|
218
|
+
if (registered.includes(shortName) && !overwrite) {
|
|
219
|
+
return { hook: shortName, success: false, error: "Already installed. Use --overwrite to replace.", scope };
|
|
226
220
|
}
|
|
227
221
|
try {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
}
|
|
231
|
-
if (overwrite && existsSync(destPath)) {
|
|
232
|
-
const { rmSync } = __require("fs");
|
|
233
|
-
rmSync(destPath, { recursive: true, force: true });
|
|
234
|
-
}
|
|
235
|
-
cpSync(sourcePath, destPath, { recursive: true });
|
|
236
|
-
registerHookInSettings(shortName);
|
|
237
|
-
updateHooksIndex(destDir);
|
|
238
|
-
return {
|
|
239
|
-
hook: shortName,
|
|
240
|
-
success: true,
|
|
241
|
-
path: destPath
|
|
242
|
-
};
|
|
222
|
+
registerHook(shortName, scope);
|
|
223
|
+
return { hook: shortName, success: true, scope };
|
|
243
224
|
} catch (error) {
|
|
244
225
|
return {
|
|
245
226
|
hook: shortName,
|
|
@@ -248,20 +229,18 @@ function installHook(name, options = {}) {
|
|
|
248
229
|
};
|
|
249
230
|
}
|
|
250
231
|
}
|
|
251
|
-
function
|
|
232
|
+
function registerHook(name, scope = "global") {
|
|
252
233
|
const meta = getHook(name);
|
|
253
234
|
if (!meta)
|
|
254
235
|
return;
|
|
255
|
-
const settings = readSettings();
|
|
236
|
+
const settings = readSettings(scope);
|
|
256
237
|
if (!settings.hooks)
|
|
257
238
|
settings.hooks = {};
|
|
258
239
|
const eventKey = meta.event;
|
|
259
240
|
if (!settings.hooks[eventKey])
|
|
260
241
|
settings.hooks[eventKey] = [];
|
|
261
|
-
const hookCommand = `
|
|
262
|
-
|
|
263
|
-
if (existing)
|
|
264
|
-
return;
|
|
242
|
+
const hookCommand = `hooks run ${name}`;
|
|
243
|
+
settings.hooks[eventKey] = settings.hooks[eventKey].filter((entry2) => !entry2.hooks?.some((h) => h.command === hookCommand));
|
|
265
244
|
const entry = {
|
|
266
245
|
hooks: [{ type: "command", command: hookCommand }]
|
|
267
246
|
};
|
|
@@ -269,69 +248,42 @@ function registerHookInSettings(name) {
|
|
|
269
248
|
entry.matcher = meta.matcher;
|
|
270
249
|
}
|
|
271
250
|
settings.hooks[eventKey].push(entry);
|
|
272
|
-
writeSettings(settings);
|
|
251
|
+
writeSettings(settings, scope);
|
|
273
252
|
}
|
|
274
|
-
function
|
|
253
|
+
function unregisterHook(name, scope = "global") {
|
|
275
254
|
const meta = getHook(name);
|
|
276
255
|
if (!meta)
|
|
277
256
|
return;
|
|
278
|
-
const settings = readSettings();
|
|
257
|
+
const settings = readSettings(scope);
|
|
279
258
|
if (!settings.hooks)
|
|
280
259
|
return;
|
|
281
260
|
const eventKey = meta.event;
|
|
282
261
|
if (!settings.hooks[eventKey])
|
|
283
262
|
return;
|
|
284
|
-
const hookCommand = `
|
|
285
|
-
settings.hooks[eventKey] = settings.hooks[eventKey].filter((entry) => !entry.hooks?.some((h) => h.command
|
|
263
|
+
const hookCommand = `hooks run ${name}`;
|
|
264
|
+
settings.hooks[eventKey] = settings.hooks[eventKey].filter((entry) => !entry.hooks?.some((h) => h.command === hookCommand));
|
|
286
265
|
if (settings.hooks[eventKey].length === 0) {
|
|
287
266
|
delete settings.hooks[eventKey];
|
|
288
267
|
}
|
|
289
268
|
if (Object.keys(settings.hooks).length === 0) {
|
|
290
269
|
delete settings.hooks;
|
|
291
270
|
}
|
|
292
|
-
writeSettings(settings);
|
|
271
|
+
writeSettings(settings, scope);
|
|
293
272
|
}
|
|
294
273
|
function installHooks(names, options = {}) {
|
|
295
274
|
return names.map((name) => installHook(name, options));
|
|
296
275
|
}
|
|
297
|
-
function
|
|
298
|
-
const
|
|
299
|
-
const { readdirSync } = __require("fs");
|
|
300
|
-
const hooks = readdirSync(hooksDir).filter((f) => f.startsWith("hook-") && !f.includes("."));
|
|
301
|
-
const exports = hooks.map((h) => {
|
|
302
|
-
const name = h.replace("hook-", "");
|
|
303
|
-
return `export * as ${name} from './${h}/src/index.js';`;
|
|
304
|
-
}).join(`
|
|
305
|
-
`);
|
|
306
|
-
const content = `/**
|
|
307
|
-
* Auto-generated index of installed hooks
|
|
308
|
-
* Do not edit manually - run 'hooks install' to update
|
|
309
|
-
*/
|
|
310
|
-
|
|
311
|
-
${exports}
|
|
312
|
-
`;
|
|
313
|
-
writeFileSync(indexPath, content);
|
|
314
|
-
}
|
|
315
|
-
function getInstalledHooks(targetDir = process.cwd()) {
|
|
316
|
-
const hooksDir = join(targetDir, ".hooks");
|
|
317
|
-
if (!existsSync(hooksDir)) {
|
|
318
|
-
return [];
|
|
319
|
-
}
|
|
320
|
-
const { readdirSync, statSync } = __require("fs");
|
|
321
|
-
return readdirSync(hooksDir).filter((f) => {
|
|
322
|
-
const fullPath = join(hooksDir, f);
|
|
323
|
-
return f.startsWith("hook-") && statSync(fullPath).isDirectory();
|
|
324
|
-
}).map((f) => f.replace("hook-", ""));
|
|
325
|
-
}
|
|
326
|
-
function getRegisteredHooks() {
|
|
327
|
-
const settings = readSettings();
|
|
276
|
+
function getRegisteredHooks(scope = "global") {
|
|
277
|
+
const settings = readSettings(scope);
|
|
328
278
|
if (!settings.hooks)
|
|
329
279
|
return [];
|
|
330
280
|
const registered = [];
|
|
331
281
|
for (const eventKey of Object.keys(settings.hooks)) {
|
|
332
282
|
for (const entry of settings.hooks[eventKey]) {
|
|
333
283
|
for (const hook of entry.hooks || []) {
|
|
334
|
-
const
|
|
284
|
+
const newMatch = hook.command?.match(/^hooks run (\w+)$/);
|
|
285
|
+
const oldMatch = hook.command?.match(/^hook-(\w+)$/);
|
|
286
|
+
const match = newMatch || oldMatch;
|
|
335
287
|
if (match) {
|
|
336
288
|
registered.push(match[1]);
|
|
337
289
|
}
|
|
@@ -340,18 +292,17 @@ function getRegisteredHooks() {
|
|
|
340
292
|
}
|
|
341
293
|
return [...new Set(registered)];
|
|
342
294
|
}
|
|
343
|
-
function
|
|
344
|
-
|
|
295
|
+
function getInstalledHooks(scope = "global") {
|
|
296
|
+
return getRegisteredHooks(scope);
|
|
297
|
+
}
|
|
298
|
+
function removeHook(name, scope = "global") {
|
|
345
299
|
const hookName = name.startsWith("hook-") ? name : `hook-${name}`;
|
|
346
300
|
const shortName = hookName.replace("hook-", "");
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
if (!existsSync(hookPath)) {
|
|
301
|
+
const registered = getRegisteredHooks(scope);
|
|
302
|
+
if (!registered.includes(shortName)) {
|
|
350
303
|
return false;
|
|
351
304
|
}
|
|
352
|
-
|
|
353
|
-
unregisterHookFromSettings(shortName);
|
|
354
|
-
updateHooksIndex(hooksDir);
|
|
305
|
+
unregisterHook(shortName, scope);
|
|
355
306
|
return true;
|
|
356
307
|
}
|
|
357
308
|
export {
|
|
@@ -360,6 +311,7 @@ export {
|
|
|
360
311
|
installHooks,
|
|
361
312
|
installHook,
|
|
362
313
|
hookExists,
|
|
314
|
+
getSettingsPath,
|
|
363
315
|
getRegisteredHooks,
|
|
364
316
|
getInstalledHooks,
|
|
365
317
|
getHooksByCategory,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hasna/hooks",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "Open source Claude Code hooks library - Install hooks with a single command",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"main": "./dist/index.js",
|
|
16
16
|
"types": "./dist/index.d.ts",
|
|
17
17
|
"scripts": {
|
|
18
|
-
"build": "bun build ./src/cli/index.tsx --outdir ./bin --target bun --external ink --external react --external chalk --external conf && bun build ./src/index.ts --outdir ./dist --target bun",
|
|
18
|
+
"build": "bun build ./src/cli/index.tsx --outdir ./bin --target bun --external ink --external react --external chalk --external conf --external @modelcontextprotocol/sdk --external zod && bun build ./src/index.ts --outdir ./dist --target bun",
|
|
19
19
|
"dev": "bun run ./src/cli/index.tsx",
|
|
20
20
|
"test": "bun test",
|
|
21
21
|
"typecheck": "tsc --noEmit",
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"typescript": "^5"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
42
43
|
"chalk": "^5.3.0",
|
|
43
44
|
"commander": "^12.1.0",
|
|
44
45
|
"conf": "^13.0.1",
|