@hasna/hooks 0.0.5 → 0.0.6
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 +160 -196
- package/dist/index.js +46 -94
- package/package.json +1 -1
- package/.hooks/index.ts +0 -6
package/bin/index.js
CHANGED
|
@@ -3521,7 +3521,6 @@ var {
|
|
|
3521
3521
|
import chalk2 from "chalk";
|
|
3522
3522
|
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
3523
3523
|
import { join as join2 } from "path";
|
|
3524
|
-
import { homedir as homedir2 } from "os";
|
|
3525
3524
|
|
|
3526
3525
|
// src/cli/components/App.tsx
|
|
3527
3526
|
import { useState as useState7 } from "react";
|
|
@@ -4522,71 +4521,57 @@ function Spinner({ type = "dots" }) {
|
|
|
4522
4521
|
var build_default2 = Spinner;
|
|
4523
4522
|
|
|
4524
4523
|
// src/lib/installer.ts
|
|
4525
|
-
import { existsSync,
|
|
4524
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
4526
4525
|
import { join, dirname } from "path";
|
|
4527
4526
|
import { homedir } from "os";
|
|
4528
4527
|
import { fileURLToPath } from "url";
|
|
4529
4528
|
var __dirname2 = dirname(fileURLToPath(import.meta.url));
|
|
4530
4529
|
var HOOKS_DIR = existsSync(join(__dirname2, "..", "..", "hooks", "hook-gitguard")) ? join(__dirname2, "..", "..", "hooks") : join(__dirname2, "..", "hooks");
|
|
4531
|
-
|
|
4530
|
+
function getSettingsPath(scope = "global") {
|
|
4531
|
+
if (scope === "project") {
|
|
4532
|
+
return join(process.cwd(), ".claude", "settings.json");
|
|
4533
|
+
}
|
|
4534
|
+
return join(homedir(), ".claude", "settings.json");
|
|
4535
|
+
}
|
|
4532
4536
|
function getHookPath(name) {
|
|
4533
4537
|
const hookName = name.startsWith("hook-") ? name : `hook-${name}`;
|
|
4534
4538
|
return join(HOOKS_DIR, hookName);
|
|
4535
4539
|
}
|
|
4536
|
-
function
|
|
4540
|
+
function hookExists(name) {
|
|
4541
|
+
return existsSync(getHookPath(name));
|
|
4542
|
+
}
|
|
4543
|
+
function readSettings(scope = "global") {
|
|
4544
|
+
const path = getSettingsPath(scope);
|
|
4537
4545
|
try {
|
|
4538
|
-
if (existsSync(
|
|
4539
|
-
return JSON.parse(readFileSync(
|
|
4546
|
+
if (existsSync(path)) {
|
|
4547
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
4540
4548
|
}
|
|
4541
4549
|
} catch {}
|
|
4542
4550
|
return {};
|
|
4543
4551
|
}
|
|
4544
|
-
function writeSettings(settings) {
|
|
4545
|
-
const
|
|
4552
|
+
function writeSettings(settings, scope = "global") {
|
|
4553
|
+
const path = getSettingsPath(scope);
|
|
4554
|
+
const dir = dirname(path);
|
|
4546
4555
|
if (!existsSync(dir)) {
|
|
4547
4556
|
mkdirSync(dir, { recursive: true });
|
|
4548
4557
|
}
|
|
4549
|
-
writeFileSync(
|
|
4558
|
+
writeFileSync(path, JSON.stringify(settings, null, 2) + `
|
|
4550
4559
|
`);
|
|
4551
4560
|
}
|
|
4552
4561
|
function installHook(name, options = {}) {
|
|
4553
|
-
const {
|
|
4562
|
+
const { scope = "global", overwrite = false } = options;
|
|
4554
4563
|
const hookName = name.startsWith("hook-") ? name : `hook-${name}`;
|
|
4555
4564
|
const shortName = hookName.replace("hook-", "");
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
const destPath = join(destDir, hookName);
|
|
4559
|
-
if (!existsSync(sourcePath)) {
|
|
4560
|
-
return {
|
|
4561
|
-
hook: shortName,
|
|
4562
|
-
success: false,
|
|
4563
|
-
error: `Hook '${shortName}' not found`
|
|
4564
|
-
};
|
|
4565
|
+
if (!hookExists(shortName)) {
|
|
4566
|
+
return { hook: shortName, success: false, error: `Hook '${shortName}' not found` };
|
|
4565
4567
|
}
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
success: false,
|
|
4570
|
-
error: `Already installed. Use --overwrite to replace.`,
|
|
4571
|
-
path: destPath
|
|
4572
|
-
};
|
|
4568
|
+
const registered = getRegisteredHooks(scope);
|
|
4569
|
+
if (registered.includes(shortName) && !overwrite) {
|
|
4570
|
+
return { hook: shortName, success: false, error: "Already installed. Use --overwrite to replace.", scope };
|
|
4573
4571
|
}
|
|
4574
4572
|
try {
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
}
|
|
4578
|
-
if (overwrite && existsSync(destPath)) {
|
|
4579
|
-
const { rmSync } = __require("fs");
|
|
4580
|
-
rmSync(destPath, { recursive: true, force: true });
|
|
4581
|
-
}
|
|
4582
|
-
cpSync(sourcePath, destPath, { recursive: true });
|
|
4583
|
-
registerHookInSettings(shortName);
|
|
4584
|
-
updateHooksIndex(destDir);
|
|
4585
|
-
return {
|
|
4586
|
-
hook: shortName,
|
|
4587
|
-
success: true,
|
|
4588
|
-
path: destPath
|
|
4589
|
-
};
|
|
4573
|
+
registerHook(shortName, scope);
|
|
4574
|
+
return { hook: shortName, success: true, scope };
|
|
4590
4575
|
} catch (error) {
|
|
4591
4576
|
return {
|
|
4592
4577
|
hook: shortName,
|
|
@@ -4595,20 +4580,18 @@ function installHook(name, options = {}) {
|
|
|
4595
4580
|
};
|
|
4596
4581
|
}
|
|
4597
4582
|
}
|
|
4598
|
-
function
|
|
4583
|
+
function registerHook(name, scope = "global") {
|
|
4599
4584
|
const meta = getHook(name);
|
|
4600
4585
|
if (!meta)
|
|
4601
4586
|
return;
|
|
4602
|
-
const settings = readSettings();
|
|
4587
|
+
const settings = readSettings(scope);
|
|
4603
4588
|
if (!settings.hooks)
|
|
4604
4589
|
settings.hooks = {};
|
|
4605
4590
|
const eventKey = meta.event;
|
|
4606
4591
|
if (!settings.hooks[eventKey])
|
|
4607
4592
|
settings.hooks[eventKey] = [];
|
|
4608
|
-
const hookCommand = `
|
|
4609
|
-
|
|
4610
|
-
if (existing)
|
|
4611
|
-
return;
|
|
4593
|
+
const hookCommand = `hooks run ${name}`;
|
|
4594
|
+
settings.hooks[eventKey] = settings.hooks[eventKey].filter((entry2) => !entry2.hooks?.some((h) => h.command === hookCommand));
|
|
4612
4595
|
const entry = {
|
|
4613
4596
|
hooks: [{ type: "command", command: hookCommand }]
|
|
4614
4597
|
};
|
|
@@ -4616,66 +4599,39 @@ function registerHookInSettings(name) {
|
|
|
4616
4599
|
entry.matcher = meta.matcher;
|
|
4617
4600
|
}
|
|
4618
4601
|
settings.hooks[eventKey].push(entry);
|
|
4619
|
-
writeSettings(settings);
|
|
4602
|
+
writeSettings(settings, scope);
|
|
4620
4603
|
}
|
|
4621
|
-
function
|
|
4604
|
+
function unregisterHook(name, scope = "global") {
|
|
4622
4605
|
const meta = getHook(name);
|
|
4623
4606
|
if (!meta)
|
|
4624
4607
|
return;
|
|
4625
|
-
const settings = readSettings();
|
|
4608
|
+
const settings = readSettings(scope);
|
|
4626
4609
|
if (!settings.hooks)
|
|
4627
4610
|
return;
|
|
4628
4611
|
const eventKey = meta.event;
|
|
4629
4612
|
if (!settings.hooks[eventKey])
|
|
4630
4613
|
return;
|
|
4631
|
-
const hookCommand = `
|
|
4632
|
-
settings.hooks[eventKey] = settings.hooks[eventKey].filter((entry) => !entry.hooks?.some((h) => h.command
|
|
4614
|
+
const hookCommand = `hooks run ${name}`;
|
|
4615
|
+
settings.hooks[eventKey] = settings.hooks[eventKey].filter((entry) => !entry.hooks?.some((h) => h.command === hookCommand));
|
|
4633
4616
|
if (settings.hooks[eventKey].length === 0) {
|
|
4634
4617
|
delete settings.hooks[eventKey];
|
|
4635
4618
|
}
|
|
4636
4619
|
if (Object.keys(settings.hooks).length === 0) {
|
|
4637
4620
|
delete settings.hooks;
|
|
4638
4621
|
}
|
|
4639
|
-
writeSettings(settings);
|
|
4640
|
-
}
|
|
4641
|
-
function updateHooksIndex(hooksDir) {
|
|
4642
|
-
const indexPath = join(hooksDir, "index.ts");
|
|
4643
|
-
const { readdirSync } = __require("fs");
|
|
4644
|
-
const hooks = readdirSync(hooksDir).filter((f) => f.startsWith("hook-") && !f.includes("."));
|
|
4645
|
-
const exports = hooks.map((h) => {
|
|
4646
|
-
const name = h.replace("hook-", "");
|
|
4647
|
-
return `export * as ${name} from './${h}/src/index.js';`;
|
|
4648
|
-
}).join(`
|
|
4649
|
-
`);
|
|
4650
|
-
const content = `/**
|
|
4651
|
-
* Auto-generated index of installed hooks
|
|
4652
|
-
* Do not edit manually - run 'hooks install' to update
|
|
4653
|
-
*/
|
|
4654
|
-
|
|
4655
|
-
${exports}
|
|
4656
|
-
`;
|
|
4657
|
-
writeFileSync(indexPath, content);
|
|
4658
|
-
}
|
|
4659
|
-
function getInstalledHooks(targetDir = process.cwd()) {
|
|
4660
|
-
const hooksDir = join(targetDir, ".hooks");
|
|
4661
|
-
if (!existsSync(hooksDir)) {
|
|
4662
|
-
return [];
|
|
4663
|
-
}
|
|
4664
|
-
const { readdirSync, statSync } = __require("fs");
|
|
4665
|
-
return readdirSync(hooksDir).filter((f) => {
|
|
4666
|
-
const fullPath = join(hooksDir, f);
|
|
4667
|
-
return f.startsWith("hook-") && statSync(fullPath).isDirectory();
|
|
4668
|
-
}).map((f) => f.replace("hook-", ""));
|
|
4622
|
+
writeSettings(settings, scope);
|
|
4669
4623
|
}
|
|
4670
|
-
function getRegisteredHooks() {
|
|
4671
|
-
const settings = readSettings();
|
|
4624
|
+
function getRegisteredHooks(scope = "global") {
|
|
4625
|
+
const settings = readSettings(scope);
|
|
4672
4626
|
if (!settings.hooks)
|
|
4673
4627
|
return [];
|
|
4674
4628
|
const registered = [];
|
|
4675
4629
|
for (const eventKey of Object.keys(settings.hooks)) {
|
|
4676
4630
|
for (const entry of settings.hooks[eventKey]) {
|
|
4677
4631
|
for (const hook of entry.hooks || []) {
|
|
4678
|
-
const
|
|
4632
|
+
const newMatch = hook.command?.match(/^hooks run (\w+)$/);
|
|
4633
|
+
const oldMatch = hook.command?.match(/^hook-(\w+)$/);
|
|
4634
|
+
const match = newMatch || oldMatch;
|
|
4679
4635
|
if (match) {
|
|
4680
4636
|
registered.push(match[1]);
|
|
4681
4637
|
}
|
|
@@ -4684,18 +4640,17 @@ function getRegisteredHooks() {
|
|
|
4684
4640
|
}
|
|
4685
4641
|
return [...new Set(registered)];
|
|
4686
4642
|
}
|
|
4687
|
-
function
|
|
4688
|
-
|
|
4643
|
+
function getInstalledHooks(scope = "global") {
|
|
4644
|
+
return getRegisteredHooks(scope);
|
|
4645
|
+
}
|
|
4646
|
+
function removeHook(name, scope = "global") {
|
|
4689
4647
|
const hookName = name.startsWith("hook-") ? name : `hook-${name}`;
|
|
4690
4648
|
const shortName = hookName.replace("hook-", "");
|
|
4691
|
-
const
|
|
4692
|
-
|
|
4693
|
-
if (!existsSync(hookPath)) {
|
|
4649
|
+
const registered = getRegisteredHooks(scope);
|
|
4650
|
+
if (!registered.includes(shortName)) {
|
|
4694
4651
|
return false;
|
|
4695
4652
|
}
|
|
4696
|
-
|
|
4697
|
-
unregisterHookFromSettings(shortName);
|
|
4698
|
-
updateHooksIndex(hooksDir);
|
|
4653
|
+
unregisterHook(shortName, scope);
|
|
4699
4654
|
return true;
|
|
4700
4655
|
}
|
|
4701
4656
|
|
|
@@ -5035,11 +4990,45 @@ function App({ initialHooks, overwrite = false }) {
|
|
|
5035
4990
|
// src/cli/index.tsx
|
|
5036
4991
|
import { jsxDEV as jsxDEV8 } from "react/jsx-dev-runtime";
|
|
5037
4992
|
var program2 = new Command;
|
|
5038
|
-
|
|
4993
|
+
function resolveScope(options) {
|
|
4994
|
+
if (options.project)
|
|
4995
|
+
return "project";
|
|
4996
|
+
return "global";
|
|
4997
|
+
}
|
|
4998
|
+
program2.name("hooks").description("Install Claude Code hooks for your project").version("0.0.6");
|
|
5039
4999
|
program2.command("interactive", { isDefault: true }).alias("i").description("Interactive hook browser").action(() => {
|
|
5040
5000
|
render(/* @__PURE__ */ jsxDEV8(App, {}, undefined, false, undefined, this));
|
|
5041
5001
|
});
|
|
5042
|
-
program2.command("
|
|
5002
|
+
program2.command("run").argument("<hook>", "Hook to run").description("Execute a hook (called by Claude Code)").action(async (hook) => {
|
|
5003
|
+
const meta = getHook(hook);
|
|
5004
|
+
if (!meta) {
|
|
5005
|
+
console.error(JSON.stringify({ error: `Hook '${hook}' not found` }));
|
|
5006
|
+
process.exit(1);
|
|
5007
|
+
}
|
|
5008
|
+
const hookDir = getHookPath(hook);
|
|
5009
|
+
const hookScript = join2(hookDir, "src", "hook.ts");
|
|
5010
|
+
if (!existsSync2(hookScript)) {
|
|
5011
|
+
console.error(JSON.stringify({ error: `Hook script not found: ${hookScript}` }));
|
|
5012
|
+
process.exit(1);
|
|
5013
|
+
}
|
|
5014
|
+
const stdin = await new Response(Bun.stdin.stream()).text();
|
|
5015
|
+
const proc = Bun.spawn(["bun", "run", hookScript], {
|
|
5016
|
+
stdin: new Response(stdin),
|
|
5017
|
+
stdout: "pipe",
|
|
5018
|
+
stderr: "pipe",
|
|
5019
|
+
env: process.env
|
|
5020
|
+
});
|
|
5021
|
+
const stdout = await new Response(proc.stdout).text();
|
|
5022
|
+
const stderr = await new Response(proc.stderr).text();
|
|
5023
|
+
const exitCode = await proc.exited;
|
|
5024
|
+
if (stdout)
|
|
5025
|
+
process.stdout.write(stdout);
|
|
5026
|
+
if (stderr)
|
|
5027
|
+
process.stderr.write(stderr);
|
|
5028
|
+
process.exit(exitCode);
|
|
5029
|
+
});
|
|
5030
|
+
program2.command("install").alias("add").argument("[hooks...]", "Hooks to install").option("-o, --overwrite", "Overwrite existing hooks", false).option("-a, --all", "Install all available hooks", false).option("-c, --category <category>", "Install all hooks in a category").option("-g, --global", "Install globally (~/.claude/settings.json)", false).option("-p, --project", "Install for current project (.claude/settings.json)", false).option("-j, --json", "Output as JSON", false).description("Install one or more hooks").action((hooks, options) => {
|
|
5031
|
+
const scope = resolveScope(options);
|
|
5043
5032
|
let toInstall = hooks;
|
|
5044
5033
|
if (options.all) {
|
|
5045
5034
|
toInstall = HOOKS.map((h) => h.name);
|
|
@@ -5062,7 +5051,7 @@ program2.command("install").alias("add").argument("[hooks...]", "Hooks to instal
|
|
|
5062
5051
|
}
|
|
5063
5052
|
const results = [];
|
|
5064
5053
|
for (const name of toInstall) {
|
|
5065
|
-
const result = installHook(name, { overwrite: options.overwrite });
|
|
5054
|
+
const result = installHook(name, { scope, overwrite: options.overwrite });
|
|
5066
5055
|
results.push(result);
|
|
5067
5056
|
}
|
|
5068
5057
|
if (options.json) {
|
|
@@ -5070,43 +5059,46 @@ program2.command("install").alias("add").argument("[hooks...]", "Hooks to instal
|
|
|
5070
5059
|
installed: results.filter((r) => r.success).map((r) => r.hook),
|
|
5071
5060
|
failed: results.filter((r) => !r.success).map((r) => ({ hook: r.hook, error: r.error })),
|
|
5072
5061
|
total: results.length,
|
|
5073
|
-
success: results.filter((r) => r.success).length
|
|
5062
|
+
success: results.filter((r) => r.success).length,
|
|
5063
|
+
scope
|
|
5074
5064
|
}));
|
|
5075
5065
|
return;
|
|
5076
5066
|
}
|
|
5067
|
+
const settingsFile = scope === "project" ? ".claude/settings.json" : "~/.claude/settings.json";
|
|
5077
5068
|
console.log(chalk2.bold(`
|
|
5078
|
-
Installing hooks...
|
|
5069
|
+
Installing hooks (${scope})...
|
|
5079
5070
|
`));
|
|
5080
5071
|
for (const result of results) {
|
|
5081
5072
|
if (result.success) {
|
|
5082
5073
|
const meta = getHook(result.hook);
|
|
5083
5074
|
console.log(chalk2.green(`\u2713 ${result.hook}`));
|
|
5084
5075
|
if (meta) {
|
|
5085
|
-
console.log(chalk2.dim(` ${meta.event}${meta.matcher ? ` [${meta.matcher}]` : ""} \u2192
|
|
5076
|
+
console.log(chalk2.dim(` ${meta.event}${meta.matcher ? ` [${meta.matcher}]` : ""} \u2192 hooks run ${result.hook}`));
|
|
5086
5077
|
}
|
|
5087
5078
|
} else {
|
|
5088
5079
|
console.log(chalk2.red(`\u2717 ${result.hook}: ${result.error}`));
|
|
5089
5080
|
}
|
|
5090
5081
|
}
|
|
5091
5082
|
console.log(chalk2.dim(`
|
|
5092
|
-
|
|
5083
|
+
Registered in ${settingsFile}`));
|
|
5093
5084
|
});
|
|
5094
|
-
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 hooks registered in Claude settings", false).option("-j, --json", "Output as JSON", false).description("List available or installed hooks").action((options) => {
|
|
5095
|
-
|
|
5096
|
-
|
|
5085
|
+
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 hooks registered in Claude settings", 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) => {
|
|
5086
|
+
const scope = resolveScope(options);
|
|
5087
|
+
if (options.registered || options.installed) {
|
|
5088
|
+
const registered = getRegisteredHooks(scope);
|
|
5097
5089
|
if (options.json) {
|
|
5098
5090
|
console.log(JSON.stringify(registered.map((name) => {
|
|
5099
5091
|
const meta = getHook(name);
|
|
5100
|
-
return { name, event: meta?.event, description: meta?.description };
|
|
5092
|
+
return { name, event: meta?.event, version: meta?.version, description: meta?.description, scope };
|
|
5101
5093
|
})));
|
|
5102
5094
|
return;
|
|
5103
5095
|
}
|
|
5104
5096
|
if (registered.length === 0) {
|
|
5105
|
-
console.log(chalk2.dim(
|
|
5097
|
+
console.log(chalk2.dim(`No hooks registered (${scope})`));
|
|
5106
5098
|
return;
|
|
5107
5099
|
}
|
|
5108
5100
|
console.log(chalk2.bold(`
|
|
5109
|
-
Registered hooks (${registered.length}):
|
|
5101
|
+
Registered hooks \u2014 ${scope} (${registered.length}):
|
|
5110
5102
|
`));
|
|
5111
5103
|
for (const name of registered) {
|
|
5112
5104
|
const meta = getHook(name);
|
|
@@ -5114,27 +5106,6 @@ Registered hooks (${registered.length}):
|
|
|
5114
5106
|
}
|
|
5115
5107
|
return;
|
|
5116
5108
|
}
|
|
5117
|
-
if (options.installed) {
|
|
5118
|
-
const installed = getInstalledHooks();
|
|
5119
|
-
if (options.json) {
|
|
5120
|
-
console.log(JSON.stringify(installed.map((name) => {
|
|
5121
|
-
const meta = getHook(name);
|
|
5122
|
-
return { name, event: meta?.event, version: meta?.version, description: meta?.description };
|
|
5123
|
-
})));
|
|
5124
|
-
return;
|
|
5125
|
-
}
|
|
5126
|
-
if (installed.length === 0) {
|
|
5127
|
-
console.log(chalk2.dim("No hooks installed"));
|
|
5128
|
-
return;
|
|
5129
|
-
}
|
|
5130
|
-
console.log(chalk2.bold(`
|
|
5131
|
-
Installed hooks (${installed.length}):
|
|
5132
|
-
`));
|
|
5133
|
-
for (const name of installed) {
|
|
5134
|
-
console.log(` ${name}`);
|
|
5135
|
-
}
|
|
5136
|
-
return;
|
|
5137
|
-
}
|
|
5138
5109
|
if (options.category) {
|
|
5139
5110
|
const category = CATEGORIES.find((c) => c.toLowerCase() === options.category.toLowerCase());
|
|
5140
5111
|
if (!category) {
|
|
@@ -5197,16 +5168,17 @@ Found ${results.length} hook(s):
|
|
|
5197
5168
|
console.log(` ${h.description}`);
|
|
5198
5169
|
}
|
|
5199
5170
|
});
|
|
5200
|
-
program2.command("remove").alias("rm").argument("<hook>", "Hook to remove").option("-j, --json", "Output as JSON", false).description("Remove an installed hook").action((hook, options) => {
|
|
5201
|
-
const
|
|
5171
|
+
program2.command("remove").alias("rm").argument("<hook>", "Hook to remove").option("-g, --global", "Remove from global settings", false).option("-p, --project", "Remove from project settings", false).option("-j, --json", "Output as JSON", false).description("Remove an installed hook").action((hook, options) => {
|
|
5172
|
+
const scope = resolveScope(options);
|
|
5173
|
+
const removed = removeHook(hook, scope);
|
|
5202
5174
|
if (options.json) {
|
|
5203
|
-
console.log(JSON.stringify({ hook, removed }));
|
|
5175
|
+
console.log(JSON.stringify({ hook, removed, scope }));
|
|
5204
5176
|
return;
|
|
5205
5177
|
}
|
|
5206
5178
|
if (removed) {
|
|
5207
|
-
console.log(chalk2.green(`\u2713 Removed ${hook} (
|
|
5179
|
+
console.log(chalk2.green(`\u2713 Removed ${hook} (${scope})`));
|
|
5208
5180
|
} else {
|
|
5209
|
-
console.log(chalk2.red(`\u2717 ${hook} is not installed`));
|
|
5181
|
+
console.log(chalk2.red(`\u2717 ${hook} is not installed (${scope})`));
|
|
5210
5182
|
}
|
|
5211
5183
|
});
|
|
5212
5184
|
program2.command("categories").option("-j, --json", "Output as JSON", false).description("List all categories").action((options) => {
|
|
@@ -5236,12 +5208,10 @@ program2.command("info").argument("<hook>", "Hook name").option("-j, --json", "O
|
|
|
5236
5208
|
}
|
|
5237
5209
|
return;
|
|
5238
5210
|
}
|
|
5239
|
-
const
|
|
5240
|
-
const
|
|
5241
|
-
const installed = getInstalledHooks();
|
|
5242
|
-
const isInstalled = installed.includes(meta.name);
|
|
5211
|
+
const globalInstalled = getRegisteredHooks("global").includes(meta.name);
|
|
5212
|
+
const projectInstalled = getRegisteredHooks("project").includes(meta.name);
|
|
5243
5213
|
if (options.json) {
|
|
5244
|
-
console.log(JSON.stringify({ ...meta,
|
|
5214
|
+
console.log(JSON.stringify({ ...meta, global: globalInstalled, project: projectInstalled }));
|
|
5245
5215
|
return;
|
|
5246
5216
|
}
|
|
5247
5217
|
console.log(chalk2.bold(`
|
|
@@ -5254,53 +5224,50 @@ ${meta.displayName}
|
|
|
5254
5224
|
console.log(` ${chalk2.dim("Event:")} ${meta.event}`);
|
|
5255
5225
|
console.log(` ${chalk2.dim("Matcher:")} ${meta.matcher || "(none)"}`);
|
|
5256
5226
|
console.log(` ${chalk2.dim("Tags:")} ${meta.tags.join(", ")}`);
|
|
5257
|
-
console.log(` ${chalk2.dim("
|
|
5227
|
+
console.log(` ${chalk2.dim("Command:")} hooks run ${meta.name}`);
|
|
5258
5228
|
console.log();
|
|
5259
|
-
if (
|
|
5260
|
-
console.log(chalk2.green(" \u25CF
|
|
5229
|
+
if (globalInstalled) {
|
|
5230
|
+
console.log(chalk2.green(" \u25CF Installed globally"));
|
|
5261
5231
|
} else {
|
|
5262
|
-
console.log(chalk2.dim(" \u25CB Not
|
|
5232
|
+
console.log(chalk2.dim(" \u25CB Not installed globally"));
|
|
5263
5233
|
}
|
|
5264
|
-
if (
|
|
5265
|
-
console.log(chalk2.green(" \u25CF Installed in
|
|
5234
|
+
if (projectInstalled) {
|
|
5235
|
+
console.log(chalk2.green(" \u25CF Installed in project"));
|
|
5266
5236
|
} else {
|
|
5267
|
-
console.log(chalk2.dim(" \u25CB Not installed"));
|
|
5237
|
+
console.log(chalk2.dim(" \u25CB Not installed in project"));
|
|
5268
5238
|
}
|
|
5269
5239
|
});
|
|
5270
|
-
program2.command("doctor").option("-j, --json", "Output as JSON", false).description("Check health of installed hooks").action((options) => {
|
|
5271
|
-
const
|
|
5240
|
+
program2.command("doctor").option("-g, --global", "Check global settings", false).option("-p, --project", "Check project settings", false).option("-j, --json", "Output as JSON", false).description("Check health of installed hooks").action((options) => {
|
|
5241
|
+
const scope = resolveScope(options);
|
|
5242
|
+
const settingsPath = getSettingsPath(scope);
|
|
5272
5243
|
const issues = [];
|
|
5273
5244
|
const healthy = [];
|
|
5274
5245
|
const settingsExist = existsSync2(settingsPath);
|
|
5275
5246
|
if (!settingsExist) {
|
|
5276
|
-
issues.push({ hook: "(settings)", issue:
|
|
5247
|
+
issues.push({ hook: "(settings)", issue: `${settingsPath} not found`, severity: "warning" });
|
|
5277
5248
|
}
|
|
5278
|
-
const
|
|
5279
|
-
const
|
|
5280
|
-
for (const name of installed) {
|
|
5249
|
+
const registered = getRegisteredHooks(scope);
|
|
5250
|
+
for (const name of registered) {
|
|
5281
5251
|
const meta = getHook(name);
|
|
5282
|
-
const hookDir = join2(process.cwd(), ".hooks", `hook-${name}`);
|
|
5283
5252
|
let hookHealthy = true;
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
issues.push({ hook: name, issue: "Missing src/ directory", severity: "error" });
|
|
5287
|
-
hookHealthy = false;
|
|
5288
|
-
}
|
|
5289
|
-
if (!existsSync2(join2(hookDir, "package.json"))) {
|
|
5290
|
-
issues.push({ hook: name, issue: "Missing package.json", severity: "error" });
|
|
5253
|
+
if (!hookExists(name)) {
|
|
5254
|
+
issues.push({ hook: name, issue: "Hook not found in @hasna/hooks package", severity: "error" });
|
|
5291
5255
|
hookHealthy = false;
|
|
5256
|
+
continue;
|
|
5292
5257
|
}
|
|
5293
|
-
|
|
5294
|
-
|
|
5258
|
+
const hookDir = getHookPath(name);
|
|
5259
|
+
const hookScript = join2(hookDir, "src", "hook.ts");
|
|
5260
|
+
if (!existsSync2(hookScript)) {
|
|
5261
|
+
issues.push({ hook: name, issue: "Missing src/hook.ts in package", severity: "error" });
|
|
5295
5262
|
hookHealthy = false;
|
|
5296
5263
|
}
|
|
5297
5264
|
if (meta && settingsExist) {
|
|
5298
5265
|
try {
|
|
5299
5266
|
const settings = JSON.parse(readFileSync2(settingsPath, "utf-8"));
|
|
5300
5267
|
const eventHooks = settings.hooks?.[meta.event] || [];
|
|
5301
|
-
const found = eventHooks.some((entry) => entry.hooks?.some((h) => h.command
|
|
5302
|
-
if (
|
|
5303
|
-
issues.push({ hook: name, issue: `
|
|
5268
|
+
const found = eventHooks.some((entry) => entry.hooks?.some((h) => h.command === `hooks run ${name}`));
|
|
5269
|
+
if (!found) {
|
|
5270
|
+
issues.push({ hook: name, issue: `Not registered under correct event (${meta.event})`, severity: "error" });
|
|
5304
5271
|
hookHealthy = false;
|
|
5305
5272
|
}
|
|
5306
5273
|
} catch {}
|
|
@@ -5309,20 +5276,15 @@ program2.command("doctor").option("-j, --json", "Output as JSON", false).descrip
|
|
|
5309
5276
|
healthy.push(name);
|
|
5310
5277
|
}
|
|
5311
5278
|
}
|
|
5312
|
-
for (const name of registered) {
|
|
5313
|
-
if (!installed.includes(name)) {
|
|
5314
|
-
issues.push({ hook: name, issue: "Registered in settings but not installed in .hooks/", severity: "warning" });
|
|
5315
|
-
}
|
|
5316
|
-
}
|
|
5317
5279
|
if (options.json) {
|
|
5318
|
-
console.log(JSON.stringify({ healthy, issues,
|
|
5280
|
+
console.log(JSON.stringify({ healthy, issues, registered, scope }));
|
|
5319
5281
|
return;
|
|
5320
5282
|
}
|
|
5321
5283
|
console.log(chalk2.bold(`
|
|
5322
|
-
Hook Health Check
|
|
5284
|
+
Hook Health Check (${scope})
|
|
5323
5285
|
`));
|
|
5324
|
-
if (
|
|
5325
|
-
console.log(chalk2.dim(" No hooks
|
|
5286
|
+
if (registered.length === 0) {
|
|
5287
|
+
console.log(chalk2.dim(" No hooks registered."));
|
|
5326
5288
|
console.log(chalk2.dim(" Run: hooks install gitguard"));
|
|
5327
5289
|
return;
|
|
5328
5290
|
}
|
|
@@ -5345,8 +5307,9 @@ Hook Health Check
|
|
|
5345
5307
|
}
|
|
5346
5308
|
console.log();
|
|
5347
5309
|
});
|
|
5348
|
-
program2.command("update").argument("[hooks...]", "Hooks to update (defaults to all installed)").option("-j, --json", "Output as JSON", false).description("
|
|
5349
|
-
const
|
|
5310
|
+
program2.command("update").argument("[hooks...]", "Hooks to update (defaults to all installed)").option("-g, --global", "Update global hooks", false).option("-p, --project", "Update project hooks", false).option("-j, --json", "Output as JSON", false).description("Re-register hooks (picks up new package version)").action((hooks, options) => {
|
|
5311
|
+
const scope = resolveScope(options);
|
|
5312
|
+
const installed = getInstalledHooks(scope);
|
|
5350
5313
|
const toUpdate = hooks.length > 0 ? hooks : installed;
|
|
5351
5314
|
if (toUpdate.length === 0) {
|
|
5352
5315
|
if (options.json) {
|
|
@@ -5362,7 +5325,7 @@ program2.command("update").argument("[hooks...]", "Hooks to update (defaults to
|
|
|
5362
5325
|
results.push({ hook: name, success: false, error: "Not installed" });
|
|
5363
5326
|
continue;
|
|
5364
5327
|
}
|
|
5365
|
-
const result = installHook(name, { overwrite: true });
|
|
5328
|
+
const result = installHook(name, { scope, overwrite: true });
|
|
5366
5329
|
results.push(result);
|
|
5367
5330
|
}
|
|
5368
5331
|
if (options.json) {
|
|
@@ -5412,24 +5375,24 @@ ${meta.displayName} v${meta.version}
|
|
|
5412
5375
|
console.log(chalk2.bold(" Configuration:"));
|
|
5413
5376
|
console.log(` Event: ${meta.event}`);
|
|
5414
5377
|
console.log(` Matcher: ${meta.matcher || "(all tools)"}`);
|
|
5415
|
-
console.log(`
|
|
5378
|
+
console.log(` Command: hooks run ${meta.name}`);
|
|
5416
5379
|
console.log();
|
|
5417
5380
|
console.log(chalk2.bold(" Install:"));
|
|
5418
|
-
console.log(` hooks install ${meta.name}`);
|
|
5381
|
+
console.log(` hooks install ${meta.name} # global`);
|
|
5382
|
+
console.log(` hooks install ${meta.name} --project # project only`);
|
|
5419
5383
|
console.log();
|
|
5420
5384
|
if (readme) {
|
|
5421
5385
|
console.log(chalk2.bold(` README:
|
|
5422
5386
|
`));
|
|
5423
|
-
const
|
|
5424
|
-
`)
|
|
5425
|
-
for (const line of lines) {
|
|
5387
|
+
for (const line of readme.split(`
|
|
5388
|
+
`)) {
|
|
5426
5389
|
console.log(` ${line}`);
|
|
5427
5390
|
}
|
|
5428
5391
|
}
|
|
5429
5392
|
return;
|
|
5430
5393
|
}
|
|
5431
5394
|
const generalDocs = {
|
|
5432
|
-
overview: "Claude Code hooks are scripts that run at specific points in a Claude Code session.",
|
|
5395
|
+
overview: "Claude Code hooks are scripts that run at specific points in a Claude Code session. Install @hasna/hooks globally, then register hooks \u2014 no files are copied to your project.",
|
|
5433
5396
|
events: {
|
|
5434
5397
|
PreToolUse: 'Fires before a tool executes. Can block the operation by returning { "decision": "block" }.',
|
|
5435
5398
|
PostToolUse: "Fires after a tool executes. Runs asynchronously, cannot block.",
|
|
@@ -5437,25 +5400,26 @@ ${meta.displayName} v${meta.version}
|
|
|
5437
5400
|
Notification: "Fires on notification events like context compaction."
|
|
5438
5401
|
},
|
|
5439
5402
|
installation: {
|
|
5440
|
-
|
|
5441
|
-
|
|
5403
|
+
global: "hooks install gitguard",
|
|
5404
|
+
project: "hooks install gitguard --project",
|
|
5442
5405
|
category: 'hooks install --category "Git Safety"',
|
|
5443
5406
|
all: "hooks install --all"
|
|
5444
5407
|
},
|
|
5445
5408
|
management: {
|
|
5446
5409
|
list: "hooks list",
|
|
5447
5410
|
listInstalled: "hooks list --installed",
|
|
5448
|
-
listRegistered: "hooks list --registered",
|
|
5449
5411
|
search: "hooks search <query>",
|
|
5450
5412
|
info: "hooks info <name>",
|
|
5451
5413
|
remove: "hooks remove <name>",
|
|
5452
5414
|
update: "hooks update",
|
|
5453
|
-
doctor: "hooks doctor"
|
|
5415
|
+
doctor: "hooks doctor",
|
|
5416
|
+
docs: "hooks docs <name>"
|
|
5454
5417
|
},
|
|
5455
|
-
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
|
|
5418
|
+
howItWorks: {
|
|
5419
|
+
install: "bun install -g @hasna/hooks",
|
|
5420
|
+
register: "hooks install gitguard \u2192 writes to ~/.claude/settings.json",
|
|
5421
|
+
execution: "Claude Code runs 'hooks run gitguard' \u2192 executes hook from global package",
|
|
5422
|
+
noFileCopy: "No files are copied to your project. Hooks run from the global @hasna/hooks package."
|
|
5459
5423
|
}
|
|
5460
5424
|
};
|
|
5461
5425
|
if (options.json) {
|
|
@@ -5469,7 +5433,13 @@ ${meta.displayName} v${meta.version}
|
|
|
5469
5433
|
`));
|
|
5470
5434
|
console.log(` ${generalDocs.overview}
|
|
5471
5435
|
`);
|
|
5472
|
-
console.log(chalk2.bold(`
|
|
5436
|
+
console.log(chalk2.bold(` How It Works
|
|
5437
|
+
`));
|
|
5438
|
+
for (const [label, desc] of Object.entries(generalDocs.howItWorks)) {
|
|
5439
|
+
console.log(` ${chalk2.dim(label + ":")} ${desc}`);
|
|
5440
|
+
}
|
|
5441
|
+
console.log(chalk2.bold(`
|
|
5442
|
+
Hook Events
|
|
5473
5443
|
`));
|
|
5474
5444
|
for (const [event, desc] of Object.entries(generalDocs.events)) {
|
|
5475
5445
|
console.log(` ${chalk2.cyan(event)}`);
|
|
@@ -5488,12 +5458,6 @@ ${meta.displayName} v${meta.version}
|
|
|
5488
5458
|
console.log(` ${chalk2.dim(label + ":")} ${cmd}`);
|
|
5489
5459
|
}
|
|
5490
5460
|
console.log(chalk2.bold(`
|
|
5491
|
-
File Structure
|
|
5492
|
-
`));
|
|
5493
|
-
for (const [label, path] of Object.entries(generalDocs.structure)) {
|
|
5494
|
-
console.log(` ${chalk2.dim(label + ":")} ${path}`);
|
|
5495
|
-
}
|
|
5496
|
-
console.log(chalk2.bold(`
|
|
5497
5461
|
Hook-Specific Docs
|
|
5498
5462
|
`));
|
|
5499
5463
|
console.log(` hooks docs <name> View README for a specific hook`);
|
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