@hasna/hooks 0.2.4 → 0.2.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 +431 -28
- package/dist/index.js +48 -1
- package/package.json +1 -1
package/bin/index.js
CHANGED
|
@@ -3905,6 +3905,25 @@ function writeSettings(settings, scope = "global", target = "claude") {
|
|
|
3905
3905
|
function getTargetEventName(internalEvent, target) {
|
|
3906
3906
|
return EVENT_MAP[target]?.[internalEvent] || internalEvent;
|
|
3907
3907
|
}
|
|
3908
|
+
function detectConflict(name, scope, target) {
|
|
3909
|
+
const meta = getHook(name);
|
|
3910
|
+
if (!meta || !meta.matcher)
|
|
3911
|
+
return;
|
|
3912
|
+
const registered = getRegisteredHooksForTarget(scope, target);
|
|
3913
|
+
for (const existingName of registered) {
|
|
3914
|
+
if (existingName === name)
|
|
3915
|
+
continue;
|
|
3916
|
+
const existing = getHook(existingName);
|
|
3917
|
+
if (!existing || existing.event !== meta.event || !existing.matcher)
|
|
3918
|
+
continue;
|
|
3919
|
+
const a = meta.matcher.toLowerCase();
|
|
3920
|
+
const b = existing.matcher.toLowerCase();
|
|
3921
|
+
if (a === b || a.includes(b) || b.includes(a)) {
|
|
3922
|
+
return `conflicts with '${existingName}' (same event ${meta.event}, overlapping matcher '${existing.matcher}')`;
|
|
3923
|
+
}
|
|
3924
|
+
}
|
|
3925
|
+
return;
|
|
3926
|
+
}
|
|
3908
3927
|
function installForTarget(name, scope, overwrite, target, profile) {
|
|
3909
3928
|
const shortName = shortHookName(name);
|
|
3910
3929
|
if (!hookExists(shortName)) {
|
|
@@ -3914,9 +3933,10 @@ function installForTarget(name, scope, overwrite, target, profile) {
|
|
|
3914
3933
|
if (registered.includes(shortName) && !overwrite) {
|
|
3915
3934
|
return { hook: shortName, success: false, error: "Already installed. Use --overwrite to replace.", scope, target };
|
|
3916
3935
|
}
|
|
3936
|
+
const conflict = detectConflict(shortName, scope, target);
|
|
3917
3937
|
try {
|
|
3918
3938
|
registerHook(shortName, scope, target, profile);
|
|
3919
|
-
return { hook: shortName, success: true, scope, target };
|
|
3939
|
+
return { hook: shortName, success: true, scope, target, ...conflict ? { conflict } : {} };
|
|
3920
3940
|
} catch (error) {
|
|
3921
3941
|
return {
|
|
3922
3942
|
hook: shortName,
|
|
@@ -4104,6 +4124,29 @@ function touchProfile(id) {
|
|
|
4104
4124
|
writeFileSync2(profilePath(id), JSON.stringify(profile, null, 2) + `
|
|
4105
4125
|
`);
|
|
4106
4126
|
}
|
|
4127
|
+
function exportProfiles() {
|
|
4128
|
+
return listProfiles();
|
|
4129
|
+
}
|
|
4130
|
+
function importProfiles(profiles) {
|
|
4131
|
+
ensureProfilesDir();
|
|
4132
|
+
let imported = 0;
|
|
4133
|
+
let skipped = 0;
|
|
4134
|
+
for (const profile of profiles) {
|
|
4135
|
+
if (!profile.agent_id || !profile.agent_type) {
|
|
4136
|
+
skipped++;
|
|
4137
|
+
continue;
|
|
4138
|
+
}
|
|
4139
|
+
const path = profilePath(profile.agent_id);
|
|
4140
|
+
if (existsSync2(path)) {
|
|
4141
|
+
skipped++;
|
|
4142
|
+
continue;
|
|
4143
|
+
}
|
|
4144
|
+
writeFileSync2(path, JSON.stringify(profile, null, 2) + `
|
|
4145
|
+
`);
|
|
4146
|
+
imported++;
|
|
4147
|
+
}
|
|
4148
|
+
return { imported, skipped };
|
|
4149
|
+
}
|
|
4107
4150
|
var PROFILES_DIR;
|
|
4108
4151
|
var init_profiles = __esm(() => {
|
|
4109
4152
|
PROFILES_DIR = join2(homedir2(), ".hooks", "profiles");
|
|
@@ -4140,13 +4183,20 @@ function createHooksServer() {
|
|
|
4140
4183
|
name: "@hasna/hooks",
|
|
4141
4184
|
version: pkg.version
|
|
4142
4185
|
});
|
|
4143
|
-
server.tool("hooks_list", "List all available hooks, optionally filtered by category
|
|
4186
|
+
server.tool("hooks_list", "List all available hooks, optionally filtered by category. Use compact:true to get minimal output (name+event+matcher only) \u2014 saves tokens.", {
|
|
4187
|
+
category: z.string().optional().describe("Filter by category name (e.g. 'Git Safety', 'Code Quality', 'Security')"),
|
|
4188
|
+
compact: z.boolean().default(false).describe("Return minimal fields only: name, event, matcher. Reduces token usage.")
|
|
4189
|
+
}, async ({ category, compact }) => {
|
|
4190
|
+
const slim = (hooks) => compact ? hooks.map((h) => ({ name: h.name, event: h.event, matcher: h.matcher })) : hooks;
|
|
4144
4191
|
if (category) {
|
|
4145
4192
|
const cat = CATEGORIES.find((c) => c.toLowerCase() === category.toLowerCase());
|
|
4146
4193
|
if (!cat) {
|
|
4147
4194
|
return { content: [{ type: "text", text: JSON.stringify({ error: `Unknown category: ${category}`, available: [...CATEGORIES] }) }] };
|
|
4148
4195
|
}
|
|
4149
|
-
return { content: [{ type: "text", text: JSON.stringify(getHooksByCategory(cat)) }] };
|
|
4196
|
+
return { content: [{ type: "text", text: JSON.stringify(slim(getHooksByCategory(cat))) }] };
|
|
4197
|
+
}
|
|
4198
|
+
if (compact) {
|
|
4199
|
+
return { content: [{ type: "text", text: JSON.stringify(slim(HOOKS)) }] };
|
|
4150
4200
|
}
|
|
4151
4201
|
const result = {};
|
|
4152
4202
|
for (const cat of CATEGORIES) {
|
|
@@ -4154,9 +4204,13 @@ function createHooksServer() {
|
|
|
4154
4204
|
}
|
|
4155
4205
|
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
4156
4206
|
});
|
|
4157
|
-
server.tool("hooks_search", "Search for hooks by name, description, or tags
|
|
4207
|
+
server.tool("hooks_search", "Search for hooks by name, description, or tags. Use compact:true for minimal output to save tokens.", {
|
|
4208
|
+
query: z.string().describe("Search query"),
|
|
4209
|
+
compact: z.boolean().default(false).describe("Return minimal fields only: name, event, matcher.")
|
|
4210
|
+
}, async ({ query, compact }) => {
|
|
4158
4211
|
const results = searchHooks(query);
|
|
4159
|
-
|
|
4212
|
+
const out = compact ? results.map((h) => ({ name: h.name, event: h.event, matcher: h.matcher })) : results;
|
|
4213
|
+
return { content: [{ type: "text", text: JSON.stringify(out) }] };
|
|
4160
4214
|
});
|
|
4161
4215
|
server.tool("hooks_info", "Get detailed information about a specific hook including install status", { name: z.string().describe("Hook name (e.g. 'gitguard', 'checkpoint')") }, async ({ name }) => {
|
|
4162
4216
|
const meta = getHook(name);
|
|
@@ -4243,7 +4297,7 @@ function createHooksServer() {
|
|
|
4243
4297
|
if (hookHealthy)
|
|
4244
4298
|
healthy.push(name);
|
|
4245
4299
|
}
|
|
4246
|
-
return { content: [{ type: "text", text: JSON.stringify({ healthy, issues, registered, scope }) }] };
|
|
4300
|
+
return { content: [{ type: "text", text: JSON.stringify({ healthy: issues.length === 0, healthy_hooks: healthy, issues, registered, scope }) }] };
|
|
4247
4301
|
});
|
|
4248
4302
|
server.tool("hooks_categories", "List all hook categories with counts", {}, async () => {
|
|
4249
4303
|
const result = CATEGORIES.map((cat) => ({
|
|
@@ -4296,15 +4350,16 @@ function createHooksServer() {
|
|
|
4296
4350
|
const registered = getRegisteredHooks(scope);
|
|
4297
4351
|
const result = registered.map((name) => {
|
|
4298
4352
|
const meta = getHook(name);
|
|
4299
|
-
return { name, event: meta?.event, version: meta?.version, description: meta?.description };
|
|
4353
|
+
return { name, event: meta?.event, matcher: meta?.matcher ?? "", version: meta?.version, description: meta?.description };
|
|
4300
4354
|
});
|
|
4301
4355
|
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
4302
4356
|
});
|
|
4303
4357
|
server.tool("hooks_run", "Execute a hook programmatically with the given input and return its output", {
|
|
4304
4358
|
name: z.string().describe("Hook name (e.g. 'gitguard', 'checkpoint')"),
|
|
4305
4359
|
input: z.record(z.string(), z.unknown()).default(() => ({})).describe("Hook input as JSON object (HookInput)"),
|
|
4306
|
-
profile: z.string().optional().describe("Agent profile ID to inject into hook input")
|
|
4307
|
-
|
|
4360
|
+
profile: z.string().optional().describe("Agent profile ID to inject into hook input"),
|
|
4361
|
+
timeout_ms: z.number().default(1e4).describe("Timeout in milliseconds (default: 10000)")
|
|
4362
|
+
}, async ({ name, input, profile, timeout_ms }) => {
|
|
4308
4363
|
const meta = getHook(name);
|
|
4309
4364
|
if (!meta) {
|
|
4310
4365
|
return { content: [{ type: "text", text: JSON.stringify({ error: `Hook '${name}' not found` }) }] };
|
|
@@ -4332,24 +4387,253 @@ function createHooksServer() {
|
|
|
4332
4387
|
stderr: "pipe",
|
|
4333
4388
|
env: process.env
|
|
4334
4389
|
});
|
|
4335
|
-
const
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4390
|
+
const timeoutPromise = new Promise((resolve) => setTimeout(() => resolve(null), timeout_ms));
|
|
4391
|
+
const result = await Promise.race([
|
|
4392
|
+
Promise.all([
|
|
4393
|
+
new Response(proc.stdout).text(),
|
|
4394
|
+
new Response(proc.stderr).text(),
|
|
4395
|
+
proc.exited
|
|
4396
|
+
]).then(([stdout, stderr, exitCode]) => ({ stdout, stderr, exitCode, timedOut: false })),
|
|
4397
|
+
timeoutPromise.then(() => {
|
|
4398
|
+
proc.kill();
|
|
4399
|
+
return { stdout: "", stderr: "", exitCode: -1, timedOut: true };
|
|
4400
|
+
})
|
|
4339
4401
|
]);
|
|
4340
4402
|
let output = {};
|
|
4341
4403
|
try {
|
|
4342
|
-
output = JSON.parse(
|
|
4404
|
+
output = JSON.parse(result.stdout);
|
|
4343
4405
|
} catch {
|
|
4344
|
-
output = { raw:
|
|
4406
|
+
output = result.stdout ? { raw: result.stdout } : {};
|
|
4407
|
+
}
|
|
4408
|
+
return {
|
|
4409
|
+
content: [{
|
|
4410
|
+
type: "text",
|
|
4411
|
+
text: JSON.stringify({
|
|
4412
|
+
hook: name,
|
|
4413
|
+
output,
|
|
4414
|
+
stderr: result.stderr || undefined,
|
|
4415
|
+
exitCode: result.exitCode,
|
|
4416
|
+
...result.timedOut ? { timedOut: true, timeout_ms } : {}
|
|
4417
|
+
})
|
|
4418
|
+
}]
|
|
4419
|
+
};
|
|
4420
|
+
});
|
|
4421
|
+
server.tool("hooks_update", "Re-register installed hooks to pick up new package version (reinstalls with overwrite)", {
|
|
4422
|
+
hooks: z.array(z.string()).optional().describe("Hook names to update (omit to update all installed hooks)"),
|
|
4423
|
+
scope: z.enum(["global", "project"]).default("global").describe("Scope to update")
|
|
4424
|
+
}, async ({ hooks, scope }) => {
|
|
4425
|
+
const installed = getRegisteredHooks(scope);
|
|
4426
|
+
const toUpdate = hooks && hooks.length > 0 ? hooks : installed;
|
|
4427
|
+
if (toUpdate.length === 0) {
|
|
4428
|
+
return { content: [{ type: "text", text: JSON.stringify({ updated: [], error: "No hooks installed" }) }] };
|
|
4429
|
+
}
|
|
4430
|
+
const results = toUpdate.map((name) => {
|
|
4431
|
+
if (!installed.includes(name)) {
|
|
4432
|
+
return { hook: name, success: false, error: "Not installed" };
|
|
4433
|
+
}
|
|
4434
|
+
return installHook(name, { scope, overwrite: true });
|
|
4435
|
+
});
|
|
4436
|
+
const updated = results.filter((r) => r.success).map((r) => r.hook);
|
|
4437
|
+
const failed = results.filter((r) => !r.success).map((r) => ({ hook: r.hook, error: r.error }));
|
|
4438
|
+
return { content: [{ type: "text", text: JSON.stringify({ updated, failed, total: results.length }) }] };
|
|
4439
|
+
});
|
|
4440
|
+
server.tool("hooks_context", "Get full agent context in one call: installed hooks (with event+matcher), active profile, settings path, and doctor status. Call this once at session start instead of making 4 separate calls.", {
|
|
4441
|
+
scope: z.enum(["global", "project"]).default("global").describe("Scope to inspect"),
|
|
4442
|
+
profile: z.string().optional().describe("Agent profile ID to include in context")
|
|
4443
|
+
}, async ({ scope, profile }) => {
|
|
4444
|
+
const settingsPath = getSettingsPath(scope);
|
|
4445
|
+
const registered = getRegisteredHooks(scope);
|
|
4446
|
+
const hooks = registered.map((name) => {
|
|
4447
|
+
const meta = getHook(name);
|
|
4448
|
+
return { name, event: meta?.event, matcher: meta?.matcher ?? "", version: meta?.version };
|
|
4449
|
+
});
|
|
4450
|
+
const issues = [];
|
|
4451
|
+
for (const name of registered) {
|
|
4452
|
+
if (!hookExists(name)) {
|
|
4453
|
+
issues.push({ hook: name, issue: "Hook not found in package", severity: "error" });
|
|
4454
|
+
}
|
|
4455
|
+
}
|
|
4456
|
+
const healthy = issues.length === 0;
|
|
4457
|
+
const ctx = {
|
|
4458
|
+
scope,
|
|
4459
|
+
settings_path: settingsPath,
|
|
4460
|
+
settings_exists: existsSync3(settingsPath),
|
|
4461
|
+
registered_hooks: hooks,
|
|
4462
|
+
hook_count: hooks.length,
|
|
4463
|
+
healthy,
|
|
4464
|
+
issues,
|
|
4465
|
+
version: pkg.version
|
|
4466
|
+
};
|
|
4467
|
+
if (profile) {
|
|
4468
|
+
const p = getProfile(profile);
|
|
4469
|
+
ctx.profile = p ?? null;
|
|
4345
4470
|
}
|
|
4471
|
+
return { content: [{ type: "text", text: JSON.stringify(ctx) }] };
|
|
4472
|
+
});
|
|
4473
|
+
server.tool("hooks_preview", "Simulate which installed PreToolUse hooks would fire for a given tool call and what decision each returns. Use this to understand your hook environment before taking an action.", {
|
|
4474
|
+
tool_name: z.string().describe("Tool name to simulate (e.g. 'Bash', 'Write', 'Edit')"),
|
|
4475
|
+
tool_input: z.record(z.string(), z.unknown()).default(() => ({})).describe("Tool input to pass to matching hooks"),
|
|
4476
|
+
scope: z.enum(["global", "project"]).default("global").describe("Scope to check"),
|
|
4477
|
+
timeout_ms: z.number().default(5000).describe("Per-hook timeout in milliseconds")
|
|
4478
|
+
}, async ({ tool_name, tool_input, scope, timeout_ms }) => {
|
|
4479
|
+
const registered = getRegisteredHooks(scope);
|
|
4480
|
+
const matchingHooks = registered.filter((name) => {
|
|
4481
|
+
const meta = getHook(name);
|
|
4482
|
+
if (!meta || meta.event !== "PreToolUse")
|
|
4483
|
+
return false;
|
|
4484
|
+
if (!meta.matcher)
|
|
4485
|
+
return true;
|
|
4486
|
+
try {
|
|
4487
|
+
return new RegExp(meta.matcher).test(tool_name);
|
|
4488
|
+
} catch {
|
|
4489
|
+
return false;
|
|
4490
|
+
}
|
|
4491
|
+
});
|
|
4492
|
+
if (matchingHooks.length === 0) {
|
|
4493
|
+
return { content: [{ type: "text", text: JSON.stringify({ tool_name, matching_hooks: [], result: "no_hooks_match", decision: "approve" }) }] };
|
|
4494
|
+
}
|
|
4495
|
+
const input = { tool_name, tool_input };
|
|
4496
|
+
const results = await Promise.all(matchingHooks.map(async (name) => {
|
|
4497
|
+
const hookDir = getHookPath(name);
|
|
4498
|
+
const hookScript = join3(hookDir, "src", "hook.ts");
|
|
4499
|
+
if (!existsSync3(hookScript))
|
|
4500
|
+
return { name, decision: "approve", error: "script not found" };
|
|
4501
|
+
const proc = Bun.spawn(["bun", "run", hookScript], {
|
|
4502
|
+
stdin: new Response(JSON.stringify(input)),
|
|
4503
|
+
stdout: "pipe",
|
|
4504
|
+
stderr: "pipe",
|
|
4505
|
+
env: process.env
|
|
4506
|
+
});
|
|
4507
|
+
const timeout = new Promise((r) => setTimeout(() => r(null), timeout_ms));
|
|
4508
|
+
const res = await Promise.race([
|
|
4509
|
+
Promise.all([new Response(proc.stdout).text(), proc.exited]).then(([stdout]) => ({ stdout, timedOut: false })),
|
|
4510
|
+
timeout.then(() => {
|
|
4511
|
+
proc.kill();
|
|
4512
|
+
return { stdout: "", timedOut: true };
|
|
4513
|
+
})
|
|
4514
|
+
]);
|
|
4515
|
+
if (res.timedOut)
|
|
4516
|
+
return { name, decision: "approve", timedOut: true };
|
|
4517
|
+
let output = {};
|
|
4518
|
+
try {
|
|
4519
|
+
output = JSON.parse(res.stdout);
|
|
4520
|
+
} catch {}
|
|
4521
|
+
return { name, decision: output.decision ?? "approve", reason: output.reason, raw: output };
|
|
4522
|
+
}));
|
|
4523
|
+
const blocked = results.find((r) => r.decision === "block");
|
|
4524
|
+
return {
|
|
4525
|
+
content: [{
|
|
4526
|
+
type: "text",
|
|
4527
|
+
text: JSON.stringify({
|
|
4528
|
+
tool_name,
|
|
4529
|
+
matching_hooks: matchingHooks,
|
|
4530
|
+
results,
|
|
4531
|
+
decision: blocked ? "block" : "approve",
|
|
4532
|
+
blocked_by: blocked?.name ?? null,
|
|
4533
|
+
blocked_reason: blocked?.reason ?? null
|
|
4534
|
+
})
|
|
4535
|
+
}]
|
|
4536
|
+
};
|
|
4537
|
+
});
|
|
4538
|
+
server.tool("hooks_setup", "Single-shot agent onboarding: create an agent profile + install recommended hooks in one call. Ideal for agents setting up hooks at session start.", {
|
|
4539
|
+
agent_type: z.enum(["claude", "gemini", "custom"]).default("claude").describe("Type of AI agent"),
|
|
4540
|
+
name: z.string().optional().describe("Optional display name for the agent"),
|
|
4541
|
+
hooks: z.array(z.string()).optional().describe("Hook names to install (omit for sensible defaults: gitguard, checkpoint, checktests, protectfiles)"),
|
|
4542
|
+
scope: z.enum(["global", "project"]).default("global").describe("Install scope")
|
|
4543
|
+
}, async ({ agent_type, name, hooks, scope }) => {
|
|
4544
|
+
const profile = createProfile({ agent_type, name });
|
|
4545
|
+
const toInstall = hooks && hooks.length > 0 ? hooks : ["gitguard", "checkpoint", "checktests", "protectfiles"];
|
|
4546
|
+
const results = toInstall.map((h) => installHook(h, { scope, overwrite: false, profile: profile.agent_id }));
|
|
4547
|
+
const installed = results.filter((r) => r.success).map((r) => r.hook);
|
|
4548
|
+
const failed = results.filter((r) => !r.success).map((r) => ({ hook: r.hook, error: r.error }));
|
|
4346
4549
|
return {
|
|
4347
4550
|
content: [{
|
|
4348
4551
|
type: "text",
|
|
4349
|
-
text: JSON.stringify({
|
|
4552
|
+
text: JSON.stringify({ profile, installed, failed, scope, run_with: `hooks run <name> --profile ${profile.agent_id}` })
|
|
4350
4553
|
}]
|
|
4351
4554
|
};
|
|
4352
4555
|
});
|
|
4556
|
+
server.tool("hooks_batch_run", "Run multiple hooks in parallel in a single call. Returns all results at once \u2014 more efficient than N separate hooks_run calls.", {
|
|
4557
|
+
hooks: z.array(z.object({
|
|
4558
|
+
name: z.string().describe("Hook name"),
|
|
4559
|
+
input: z.record(z.string(), z.unknown()).default(() => ({})).describe("Hook input JSON")
|
|
4560
|
+
})).describe("List of hooks to run with their inputs"),
|
|
4561
|
+
timeout_ms: z.number().default(1e4).describe("Per-hook timeout in milliseconds")
|
|
4562
|
+
}, async ({ hooks, timeout_ms }) => {
|
|
4563
|
+
const results = await Promise.all(hooks.map(async ({ name, input }) => {
|
|
4564
|
+
const meta = getHook(name);
|
|
4565
|
+
if (!meta)
|
|
4566
|
+
return { name, error: `Hook '${name}' not found` };
|
|
4567
|
+
const hookScript = join3(getHookPath(name), "src", "hook.ts");
|
|
4568
|
+
if (!existsSync3(hookScript))
|
|
4569
|
+
return { name, error: "script not found" };
|
|
4570
|
+
const proc = Bun.spawn(["bun", "run", hookScript], {
|
|
4571
|
+
stdin: new Response(JSON.stringify(input)),
|
|
4572
|
+
stdout: "pipe",
|
|
4573
|
+
stderr: "pipe",
|
|
4574
|
+
env: process.env
|
|
4575
|
+
});
|
|
4576
|
+
const timeout = new Promise((r) => setTimeout(() => r(null), timeout_ms));
|
|
4577
|
+
const res = await Promise.race([
|
|
4578
|
+
Promise.all([new Response(proc.stdout).text(), new Response(proc.stderr).text(), proc.exited]).then(([stdout, stderr, exitCode]) => ({ stdout, stderr, exitCode, timedOut: false })),
|
|
4579
|
+
timeout.then(() => {
|
|
4580
|
+
proc.kill();
|
|
4581
|
+
return { stdout: "", stderr: "", exitCode: -1, timedOut: true };
|
|
4582
|
+
})
|
|
4583
|
+
]);
|
|
4584
|
+
let output = {};
|
|
4585
|
+
try {
|
|
4586
|
+
output = JSON.parse(res.stdout);
|
|
4587
|
+
} catch {
|
|
4588
|
+
output = res.stdout ? { raw: res.stdout } : {};
|
|
4589
|
+
}
|
|
4590
|
+
return { name, output, exitCode: res.exitCode, ...res.timedOut ? { timedOut: true } : {} };
|
|
4591
|
+
}));
|
|
4592
|
+
return { content: [{ type: "text", text: JSON.stringify({ results, count: results.length }) }] };
|
|
4593
|
+
});
|
|
4594
|
+
server.tool("hooks_disable", "Temporarily disable a registered hook without removing it. Stores disabled list in settings under hooks.__disabled.", {
|
|
4595
|
+
name: z.string().describe("Hook name to disable"),
|
|
4596
|
+
scope: z.enum(["global", "project"]).default("global").describe("Scope")
|
|
4597
|
+
}, async ({ name, scope }) => {
|
|
4598
|
+
const settingsPath = getSettingsPath(scope);
|
|
4599
|
+
let settings = {};
|
|
4600
|
+
try {
|
|
4601
|
+
if (existsSync3(settingsPath))
|
|
4602
|
+
settings = JSON.parse(readFileSync3(settingsPath, "utf-8"));
|
|
4603
|
+
} catch {}
|
|
4604
|
+
if (!settings.hooks)
|
|
4605
|
+
settings.hooks = {};
|
|
4606
|
+
const disabled = settings.hooks.__disabled ?? [];
|
|
4607
|
+
if (!disabled.includes(name))
|
|
4608
|
+
disabled.push(name);
|
|
4609
|
+
settings.hooks.__disabled = disabled;
|
|
4610
|
+
const { writeFileSync: writeFileSync3, mkdirSync: mkdirSync3 } = await import("fs");
|
|
4611
|
+
const { dirname: dirname3 } = await import("path");
|
|
4612
|
+
mkdirSync3(dirname3(settingsPath), { recursive: true });
|
|
4613
|
+
writeFileSync3(settingsPath, JSON.stringify(settings, null, 2) + `
|
|
4614
|
+
`);
|
|
4615
|
+
return { content: [{ type: "text", text: JSON.stringify({ hook: name, disabled: true, scope }) }] };
|
|
4616
|
+
});
|
|
4617
|
+
server.tool("hooks_enable", "Re-enable a previously disabled hook.", {
|
|
4618
|
+
name: z.string().describe("Hook name to enable"),
|
|
4619
|
+
scope: z.enum(["global", "project"]).default("global").describe("Scope")
|
|
4620
|
+
}, async ({ name, scope }) => {
|
|
4621
|
+
const settingsPath = getSettingsPath(scope);
|
|
4622
|
+
let settings = {};
|
|
4623
|
+
try {
|
|
4624
|
+
if (existsSync3(settingsPath))
|
|
4625
|
+
settings = JSON.parse(readFileSync3(settingsPath, "utf-8"));
|
|
4626
|
+
} catch {}
|
|
4627
|
+
if (settings.hooks?.__disabled) {
|
|
4628
|
+
settings.hooks.__disabled = settings.hooks.__disabled.filter((n) => n !== name);
|
|
4629
|
+
if (settings.hooks.__disabled.length === 0)
|
|
4630
|
+
delete settings.hooks.__disabled;
|
|
4631
|
+
const { writeFileSync: writeFileSync3 } = await import("fs");
|
|
4632
|
+
writeFileSync3(settingsPath, JSON.stringify(settings, null, 2) + `
|
|
4633
|
+
`);
|
|
4634
|
+
}
|
|
4635
|
+
return { content: [{ type: "text", text: JSON.stringify({ hook: name, disabled: false, scope }) }] };
|
|
4636
|
+
});
|
|
4353
4637
|
server.tool("hooks_init", "Register a new agent profile \u2014 returns a unique agent_id for use with hook installation and execution", {
|
|
4354
4638
|
agent_type: z.enum(["claude", "gemini", "custom"]).default("claude").describe("Type of AI agent"),
|
|
4355
4639
|
name: z.string().optional().describe("Optional display name for the agent")
|
|
@@ -5617,6 +5901,28 @@ function resolveScope(options) {
|
|
|
5617
5901
|
return "project";
|
|
5618
5902
|
return "global";
|
|
5619
5903
|
}
|
|
5904
|
+
function resolveTarget(options) {
|
|
5905
|
+
if (options.target === "gemini")
|
|
5906
|
+
return "gemini";
|
|
5907
|
+
if (options.target === "all")
|
|
5908
|
+
return "all";
|
|
5909
|
+
return "claude";
|
|
5910
|
+
}
|
|
5911
|
+
function editDistance(a, b) {
|
|
5912
|
+
const m = a.length, n = b.length;
|
|
5913
|
+
const dp = Array.from({ length: m + 1 }, (_, i) => [i, ...Array(n).fill(0)]);
|
|
5914
|
+
for (let j = 0;j <= n; j++)
|
|
5915
|
+
dp[0][j] = j;
|
|
5916
|
+
for (let i = 1;i <= m; i++) {
|
|
5917
|
+
for (let j = 1;j <= n; j++) {
|
|
5918
|
+
dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
|
|
5919
|
+
}
|
|
5920
|
+
}
|
|
5921
|
+
return dp[m][n];
|
|
5922
|
+
}
|
|
5923
|
+
function suggestHooks(name, max = 3) {
|
|
5924
|
+
return HOOKS.map((h) => ({ name: h.name, dist: editDistance(name.toLowerCase(), h.name.toLowerCase()) })).filter(({ dist }) => dist <= 4).sort((a, b) => a.dist - b.dist).slice(0, max).map(({ name: n }) => n);
|
|
5925
|
+
}
|
|
5620
5926
|
program2.name("hooks").description("Install hooks for AI coding agents").version(pkg2.version);
|
|
5621
5927
|
program2.command("interactive", { isDefault: true }).alias("i").description("Interactive hook browser").action(() => {
|
|
5622
5928
|
render(/* @__PURE__ */ jsxDEV8(App, {}, undefined, false, undefined, this));
|
|
@@ -5696,8 +6002,9 @@ program2.command("run").argument("<hook>", "Hook to run").option("--profile <id>
|
|
|
5696
6002
|
process.stderr.write(stderr);
|
|
5697
6003
|
process.exit(exitCode);
|
|
5698
6004
|
});
|
|
5699
|
-
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("--profile <id>", "Agent profile ID to scope hooks to").option("-j, --json", "Output as JSON", false).description("Install one or more hooks").action((hooks, options) => {
|
|
6005
|
+
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("-t, --target <target>", "Agent target: claude, gemini, all (default: claude)", "claude").option("--profile <id>", "Agent profile ID to scope hooks to").option("--dry-run", "Preview what would be installed without writing to settings", false).option("-j, --json", "Output as JSON", false).description("Install one or more hooks").action((hooks, options) => {
|
|
5700
6006
|
const scope = resolveScope(options);
|
|
6007
|
+
const target = resolveTarget(options);
|
|
5701
6008
|
let toInstall = hooks;
|
|
5702
6009
|
if (options.all) {
|
|
5703
6010
|
toInstall = HOOKS.map((h) => h.name);
|
|
@@ -5718,9 +6025,38 @@ program2.command("install").alias("add").argument("[hooks...]", "Hooks to instal
|
|
|
5718
6025
|
render(/* @__PURE__ */ jsxDEV8(App, {}, undefined, false, undefined, this));
|
|
5719
6026
|
return;
|
|
5720
6027
|
}
|
|
6028
|
+
if (options.dryRun) {
|
|
6029
|
+
const known = toInstall.filter((n) => getHook(n));
|
|
6030
|
+
const unknown = toInstall.filter((n) => !getHook(n));
|
|
6031
|
+
if (options.json) {
|
|
6032
|
+
console.log(JSON.stringify({ dryRun: true, would_install: known, unknown, scope, target }));
|
|
6033
|
+
return;
|
|
6034
|
+
}
|
|
6035
|
+
console.log(chalk2.bold(`
|
|
6036
|
+
Dry run \u2014 would install (${scope}, ${target}):
|
|
6037
|
+
`));
|
|
6038
|
+
for (const name of known) {
|
|
6039
|
+
const meta = getHook(name);
|
|
6040
|
+
console.log(chalk2.cyan(` ${name}`) + chalk2.dim(` [${meta.event}${meta.matcher ? ` ${meta.matcher}` : ""}]`));
|
|
6041
|
+
}
|
|
6042
|
+
if (unknown.length > 0) {
|
|
6043
|
+
console.log();
|
|
6044
|
+
for (const name of unknown) {
|
|
6045
|
+
const suggestions = suggestHooks(name);
|
|
6046
|
+
console.log(chalk2.red(` \u2717 unknown: ${name}`) + (suggestions.length ? chalk2.dim(` \u2014 did you mean: ${suggestions.join(", ")}?`) : ""));
|
|
6047
|
+
}
|
|
6048
|
+
}
|
|
6049
|
+
return;
|
|
6050
|
+
}
|
|
5721
6051
|
const results = [];
|
|
5722
6052
|
for (const name of toInstall) {
|
|
5723
|
-
|
|
6053
|
+
if (!getHook(name)) {
|
|
6054
|
+
const suggestions = suggestHooks(name);
|
|
6055
|
+
const hint = suggestions.length ? ` \u2014 did you mean: ${suggestions.join(", ")}?` : "";
|
|
6056
|
+
results.push({ hook: name, success: false, error: `Hook '${name}' not found${hint}` });
|
|
6057
|
+
continue;
|
|
6058
|
+
}
|
|
6059
|
+
const result = installHook(name, { scope, overwrite: options.overwrite, target, profile: options.profile });
|
|
5724
6060
|
results.push(result);
|
|
5725
6061
|
}
|
|
5726
6062
|
if (options.json) {
|
|
@@ -5729,13 +6065,14 @@ program2.command("install").alias("add").argument("[hooks...]", "Hooks to instal
|
|
|
5729
6065
|
failed: results.filter((r) => !r.success).map((r) => ({ hook: r.hook, error: r.error })),
|
|
5730
6066
|
total: results.length,
|
|
5731
6067
|
success: results.filter((r) => r.success).length,
|
|
5732
|
-
scope
|
|
6068
|
+
scope,
|
|
6069
|
+
target
|
|
5733
6070
|
}));
|
|
5734
6071
|
return;
|
|
5735
6072
|
}
|
|
5736
6073
|
const settingsFile = scope === "project" ? ".claude/settings.json" : "~/.claude/settings.json";
|
|
5737
6074
|
console.log(chalk2.bold(`
|
|
5738
|
-
Installing hooks (${scope})...
|
|
6075
|
+
Installing hooks (${scope}, ${target})...
|
|
5739
6076
|
`));
|
|
5740
6077
|
for (const result of results) {
|
|
5741
6078
|
if (result.success) {
|
|
@@ -5744,6 +6081,9 @@ Installing hooks (${scope})...
|
|
|
5744
6081
|
if (meta) {
|
|
5745
6082
|
console.log(chalk2.dim(` ${meta.event}${meta.matcher ? ` [${meta.matcher}]` : ""} \u2192 hooks run ${result.hook}`));
|
|
5746
6083
|
}
|
|
6084
|
+
if (result.conflict) {
|
|
6085
|
+
console.log(chalk2.yellow(` \u26A0 Warning: ${result.conflict}`));
|
|
6086
|
+
}
|
|
5747
6087
|
} else {
|
|
5748
6088
|
console.log(chalk2.red(`\u2717 ${result.hook}: ${result.error}`));
|
|
5749
6089
|
}
|
|
@@ -5838,17 +6178,28 @@ Found ${results.length} hook(s):
|
|
|
5838
6178
|
console.log(` ${h.description}`);
|
|
5839
6179
|
}
|
|
5840
6180
|
});
|
|
5841
|
-
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) => {
|
|
6181
|
+
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("-t, --target <target>", "Agent target: claude, gemini, all (default: claude)", "claude").option("-j, --json", "Output as JSON", false).description("Remove an installed hook").action((hook, options) => {
|
|
5842
6182
|
const scope = resolveScope(options);
|
|
5843
|
-
const
|
|
6183
|
+
const target = resolveTarget(options);
|
|
6184
|
+
if (!getHook(hook)) {
|
|
6185
|
+
const suggestions = suggestHooks(hook);
|
|
6186
|
+
const hint = suggestions.length ? ` \u2014 did you mean: ${suggestions.join(", ")}?` : "";
|
|
6187
|
+
if (options.json) {
|
|
6188
|
+
console.log(JSON.stringify({ hook, removed: false, scope, target, error: `Hook '${hook}' not found${hint}`, suggestions }));
|
|
6189
|
+
} else {
|
|
6190
|
+
console.log(chalk2.red(`\u2717 Hook '${hook}' not found${hint}`));
|
|
6191
|
+
}
|
|
6192
|
+
return;
|
|
6193
|
+
}
|
|
6194
|
+
const removed = removeHook(hook, scope, target);
|
|
5844
6195
|
if (options.json) {
|
|
5845
|
-
console.log(JSON.stringify({ hook, removed, scope }));
|
|
6196
|
+
console.log(JSON.stringify({ hook, removed, scope, target }));
|
|
5846
6197
|
return;
|
|
5847
6198
|
}
|
|
5848
6199
|
if (removed) {
|
|
5849
|
-
console.log(chalk2.green(`\u2713 Removed ${hook} (${scope})`));
|
|
6200
|
+
console.log(chalk2.green(`\u2713 Removed ${hook} (${scope}, ${target})`));
|
|
5850
6201
|
} else {
|
|
5851
|
-
console.log(chalk2.red(`\u2717 ${hook} is not installed (${scope})`));
|
|
6202
|
+
console.log(chalk2.red(`\u2717 ${hook} is not installed (${scope}, ${target})`));
|
|
5852
6203
|
}
|
|
5853
6204
|
});
|
|
5854
6205
|
program2.command("categories").option("-j, --json", "Output as JSON", false).description("List all categories").action((options) => {
|
|
@@ -5871,10 +6222,12 @@ Categories:
|
|
|
5871
6222
|
program2.command("info").argument("<hook>", "Hook name").option("-j, --json", "Output as JSON", false).description("Show detailed info about a hook").action((hook, options) => {
|
|
5872
6223
|
const meta = getHook(hook);
|
|
5873
6224
|
if (!meta) {
|
|
6225
|
+
const suggestions = suggestHooks(hook);
|
|
6226
|
+
const hint = suggestions.length ? ` \u2014 did you mean: ${suggestions.join(", ")}?` : "";
|
|
5874
6227
|
if (options.json) {
|
|
5875
|
-
console.log(JSON.stringify({ error: `Hook '${hook}' not found
|
|
6228
|
+
console.log(JSON.stringify({ error: `Hook '${hook}' not found${hint}`, suggestions }));
|
|
5876
6229
|
} else {
|
|
5877
|
-
console.log(chalk2.red(`Hook '${hook}' not found`));
|
|
6230
|
+
console.log(chalk2.red(`Hook '${hook}' not found${hint}`));
|
|
5878
6231
|
}
|
|
5879
6232
|
return;
|
|
5880
6233
|
}
|
|
@@ -5950,7 +6303,7 @@ program2.command("doctor").option("-g, --global", "Check global settings", false
|
|
|
5950
6303
|
}
|
|
5951
6304
|
}
|
|
5952
6305
|
if (options.json) {
|
|
5953
|
-
console.log(JSON.stringify({ healthy, issues, registered, scope }));
|
|
6306
|
+
console.log(JSON.stringify({ healthy: issues.length === 0, healthy_hooks: healthy, issues, registered, scope }));
|
|
5954
6307
|
return;
|
|
5955
6308
|
}
|
|
5956
6309
|
console.log(chalk2.bold(`
|
|
@@ -6199,6 +6552,56 @@ Upgrading @hasna/hooks (${pm})...
|
|
|
6199
6552
|
\u2713 Upgraded: ${current} \u2192 ${latest}`));
|
|
6200
6553
|
}
|
|
6201
6554
|
});
|
|
6555
|
+
program2.command("profile-export").description("Export all agent profiles as JSON (for backup/cross-machine setup)").option("-o, --output <file>", "Write to file instead of stdout").option("-j, --json", "Output as JSON (default: true)", false).action(async (options) => {
|
|
6556
|
+
const profiles = exportProfiles();
|
|
6557
|
+
const json = JSON.stringify(profiles, null, 2);
|
|
6558
|
+
if (options.output) {
|
|
6559
|
+
const { writeFileSync: writeFileSync3 } = await import("fs");
|
|
6560
|
+
writeFileSync3(options.output, json + `
|
|
6561
|
+
`);
|
|
6562
|
+
console.log(chalk2.green(`\u2713 Exported ${profiles.length} profile(s) to ${options.output}`));
|
|
6563
|
+
} else {
|
|
6564
|
+
console.log(json);
|
|
6565
|
+
}
|
|
6566
|
+
});
|
|
6567
|
+
program2.command("profile-import").argument("<file>", "JSON file to import profiles from (use - for stdin)").description("Import agent profiles from a JSON export file").option("-j, --json", "Output result as JSON", false).action(async (file, options) => {
|
|
6568
|
+
let raw;
|
|
6569
|
+
if (file === "-") {
|
|
6570
|
+
raw = await new Response(Bun.stdin.stream()).text();
|
|
6571
|
+
} else {
|
|
6572
|
+
const { readFileSync: readFileSync5 } = await import("fs");
|
|
6573
|
+
try {
|
|
6574
|
+
raw = readFileSync5(file, "utf-8");
|
|
6575
|
+
} catch {
|
|
6576
|
+
if (options.json) {
|
|
6577
|
+
console.log(JSON.stringify({ error: `Cannot read file: ${file}` }));
|
|
6578
|
+
} else {
|
|
6579
|
+
console.log(chalk2.red(`\u2717 Cannot read file: ${file}`));
|
|
6580
|
+
}
|
|
6581
|
+
return;
|
|
6582
|
+
}
|
|
6583
|
+
}
|
|
6584
|
+
let profiles;
|
|
6585
|
+
try {
|
|
6586
|
+
const parsed = JSON.parse(raw);
|
|
6587
|
+
profiles = Array.isArray(parsed) ? parsed : [parsed];
|
|
6588
|
+
} catch {
|
|
6589
|
+
if (options.json) {
|
|
6590
|
+
console.log(JSON.stringify({ error: "Invalid JSON" }));
|
|
6591
|
+
} else {
|
|
6592
|
+
console.log(chalk2.red("\u2717 Invalid JSON"));
|
|
6593
|
+
}
|
|
6594
|
+
return;
|
|
6595
|
+
}
|
|
6596
|
+
const result = importProfiles(profiles);
|
|
6597
|
+
if (options.json) {
|
|
6598
|
+
console.log(JSON.stringify(result));
|
|
6599
|
+
} else {
|
|
6600
|
+
console.log(chalk2.green(`\u2713 Imported ${result.imported} profile(s)`));
|
|
6601
|
+
if (result.skipped > 0)
|
|
6602
|
+
console.log(chalk2.dim(` Skipped ${result.skipped} (already exist or invalid)`));
|
|
6603
|
+
}
|
|
6604
|
+
});
|
|
6202
6605
|
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) => {
|
|
6203
6606
|
if (options.stdio) {
|
|
6204
6607
|
const { startStdioServer: startStdioServer2 } = await Promise.resolve().then(() => (init_server(), exports_server));
|
package/dist/index.js
CHANGED
|
@@ -17,8 +17,10 @@ __export(exports_profiles, {
|
|
|
17
17
|
updateProfile: () => updateProfile,
|
|
18
18
|
touchProfile: () => touchProfile,
|
|
19
19
|
listProfiles: () => listProfiles,
|
|
20
|
+
importProfiles: () => importProfiles,
|
|
20
21
|
getProfilesDir: () => getProfilesDir,
|
|
21
22
|
getProfile: () => getProfile,
|
|
23
|
+
exportProfiles: () => exportProfiles,
|
|
22
24
|
deleteProfile: () => deleteProfile,
|
|
23
25
|
createProfile: () => createProfile
|
|
24
26
|
});
|
|
@@ -115,6 +117,29 @@ function touchProfile(id) {
|
|
|
115
117
|
writeFileSync2(profilePath(id), JSON.stringify(profile, null, 2) + `
|
|
116
118
|
`);
|
|
117
119
|
}
|
|
120
|
+
function exportProfiles() {
|
|
121
|
+
return listProfiles();
|
|
122
|
+
}
|
|
123
|
+
function importProfiles(profiles) {
|
|
124
|
+
ensureProfilesDir();
|
|
125
|
+
let imported = 0;
|
|
126
|
+
let skipped = 0;
|
|
127
|
+
for (const profile of profiles) {
|
|
128
|
+
if (!profile.agent_id || !profile.agent_type) {
|
|
129
|
+
skipped++;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
const path = profilePath(profile.agent_id);
|
|
133
|
+
if (existsSync2(path)) {
|
|
134
|
+
skipped++;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
writeFileSync2(path, JSON.stringify(profile, null, 2) + `
|
|
138
|
+
`);
|
|
139
|
+
imported++;
|
|
140
|
+
}
|
|
141
|
+
return { imported, skipped };
|
|
142
|
+
}
|
|
118
143
|
var PROFILES_DIR;
|
|
119
144
|
var init_profiles = __esm(() => {
|
|
120
145
|
PROFILES_DIR = join2(homedir2(), ".hooks", "profiles");
|
|
@@ -529,6 +554,25 @@ function writeSettings(settings, scope = "global", target = "claude") {
|
|
|
529
554
|
function getTargetEventName(internalEvent, target) {
|
|
530
555
|
return EVENT_MAP[target]?.[internalEvent] || internalEvent;
|
|
531
556
|
}
|
|
557
|
+
function detectConflict(name, scope, target) {
|
|
558
|
+
const meta = getHook(name);
|
|
559
|
+
if (!meta || !meta.matcher)
|
|
560
|
+
return;
|
|
561
|
+
const registered = getRegisteredHooksForTarget(scope, target);
|
|
562
|
+
for (const existingName of registered) {
|
|
563
|
+
if (existingName === name)
|
|
564
|
+
continue;
|
|
565
|
+
const existing = getHook(existingName);
|
|
566
|
+
if (!existing || existing.event !== meta.event || !existing.matcher)
|
|
567
|
+
continue;
|
|
568
|
+
const a = meta.matcher.toLowerCase();
|
|
569
|
+
const b = existing.matcher.toLowerCase();
|
|
570
|
+
if (a === b || a.includes(b) || b.includes(a)) {
|
|
571
|
+
return `conflicts with '${existingName}' (same event ${meta.event}, overlapping matcher '${existing.matcher}')`;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
532
576
|
function installForTarget(name, scope, overwrite, target, profile) {
|
|
533
577
|
const shortName = shortHookName(name);
|
|
534
578
|
if (!hookExists(shortName)) {
|
|
@@ -538,9 +582,10 @@ function installForTarget(name, scope, overwrite, target, profile) {
|
|
|
538
582
|
if (registered.includes(shortName) && !overwrite) {
|
|
539
583
|
return { hook: shortName, success: false, error: "Already installed. Use --overwrite to replace.", scope, target };
|
|
540
584
|
}
|
|
585
|
+
const conflict = detectConflict(shortName, scope, target);
|
|
541
586
|
try {
|
|
542
587
|
registerHook(shortName, scope, target, profile);
|
|
543
|
-
return { hook: shortName, success: true, scope, target };
|
|
588
|
+
return { hook: shortName, success: true, scope, target, ...conflict ? { conflict } : {} };
|
|
544
589
|
} catch (error) {
|
|
545
590
|
return {
|
|
546
591
|
hook: shortName,
|
|
@@ -711,6 +756,7 @@ export {
|
|
|
711
756
|
installHooks,
|
|
712
757
|
installHookForProject,
|
|
713
758
|
installHook,
|
|
759
|
+
importProfiles,
|
|
714
760
|
hookExists,
|
|
715
761
|
getSettingsPath,
|
|
716
762
|
getRegisteredHooksForTarget,
|
|
@@ -721,6 +767,7 @@ export {
|
|
|
721
767
|
getHooksByCategory,
|
|
722
768
|
getHookPath,
|
|
723
769
|
getHook,
|
|
770
|
+
exportProfiles,
|
|
724
771
|
deleteProfile,
|
|
725
772
|
createProfile,
|
|
726
773
|
HOOKS,
|
package/package.json
CHANGED