@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/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, cpSync, mkdirSync, readFileSync, writeFileSync } from "fs";
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
- var SETTINGS_PATH = join(homedir(), ".claude", "settings.json");
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(SETTINGS_PATH)) {
192
- return JSON.parse(readFileSync(SETTINGS_PATH, "utf-8"));
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 dir = dirname(SETTINGS_PATH);
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(SETTINGS_PATH, JSON.stringify(settings, null, 2) + `
207
+ writeFileSync(path, JSON.stringify(settings, null, 2) + `
203
208
  `);
204
209
  }
205
210
  function installHook(name, options = {}) {
206
- const { targetDir = process.cwd(), overwrite = false } = options;
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
- const sourcePath = getHookPath(name);
210
- const destDir = join(targetDir, ".hooks");
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
- if (existsSync(destPath) && !overwrite) {
220
- return {
221
- hook: shortName,
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
- if (!existsSync(destDir)) {
229
- mkdirSync(destDir, { recursive: true });
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 registerHookInSettings(name) {
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 = `hook-${name}`;
262
- const existing = settings.hooks[eventKey].find((entry2) => entry2.hooks?.some((h) => h.command?.includes(hookCommand)));
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 unregisterHookFromSettings(name) {
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 = `hook-${name}`;
285
- settings.hooks[eventKey] = settings.hooks[eventKey].filter((entry) => !entry.hooks?.some((h) => h.command?.includes(hookCommand)));
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 updateHooksIndex(hooksDir) {
298
- const indexPath = join(hooksDir, "index.ts");
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 match = hook.command?.match(/hook-(\w+)/);
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 removeHook(name, targetDir = process.cwd()) {
344
- const { rmSync } = __require("fs");
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 hooksDir = join(targetDir, ".hooks");
348
- const hookPath = join(hooksDir, hookName);
349
- if (!existsSync(hookPath)) {
301
+ const registered = getRegisteredHooks(scope);
302
+ if (!registered.includes(shortName)) {
350
303
  return false;
351
304
  }
352
- rmSync(hookPath, { recursive: true });
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.5",
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",
package/.hooks/index.ts DELETED
@@ -1,6 +0,0 @@
1
- /**
2
- * Auto-generated index of installed hooks
3
- * Do not edit manually - run 'hooks install' to update
4
- */
5
-
6
-