@hasna/hooks 0.2.4 → 0.2.5
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 +216 -21
- 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");
|
|
@@ -4303,8 +4346,9 @@ function createHooksServer() {
|
|
|
4303
4346
|
server.tool("hooks_run", "Execute a hook programmatically with the given input and return its output", {
|
|
4304
4347
|
name: z.string().describe("Hook name (e.g. 'gitguard', 'checkpoint')"),
|
|
4305
4348
|
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
|
-
|
|
4349
|
+
profile: z.string().optional().describe("Agent profile ID to inject into hook input"),
|
|
4350
|
+
timeout_ms: z.number().default(1e4).describe("Timeout in milliseconds (default: 10000)")
|
|
4351
|
+
}, async ({ name, input, profile, timeout_ms }) => {
|
|
4308
4352
|
const meta = getHook(name);
|
|
4309
4353
|
if (!meta) {
|
|
4310
4354
|
return { content: [{ type: "text", text: JSON.stringify({ error: `Hook '${name}' not found` }) }] };
|
|
@@ -4332,24 +4376,56 @@ function createHooksServer() {
|
|
|
4332
4376
|
stderr: "pipe",
|
|
4333
4377
|
env: process.env
|
|
4334
4378
|
});
|
|
4335
|
-
const
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4379
|
+
const timeoutPromise = new Promise((resolve) => setTimeout(() => resolve(null), timeout_ms));
|
|
4380
|
+
const result = await Promise.race([
|
|
4381
|
+
Promise.all([
|
|
4382
|
+
new Response(proc.stdout).text(),
|
|
4383
|
+
new Response(proc.stderr).text(),
|
|
4384
|
+
proc.exited
|
|
4385
|
+
]).then(([stdout, stderr, exitCode]) => ({ stdout, stderr, exitCode, timedOut: false })),
|
|
4386
|
+
timeoutPromise.then(() => {
|
|
4387
|
+
proc.kill();
|
|
4388
|
+
return { stdout: "", stderr: "", exitCode: -1, timedOut: true };
|
|
4389
|
+
})
|
|
4339
4390
|
]);
|
|
4340
4391
|
let output = {};
|
|
4341
4392
|
try {
|
|
4342
|
-
output = JSON.parse(
|
|
4393
|
+
output = JSON.parse(result.stdout);
|
|
4343
4394
|
} catch {
|
|
4344
|
-
output = { raw:
|
|
4395
|
+
output = result.stdout ? { raw: result.stdout } : {};
|
|
4345
4396
|
}
|
|
4346
4397
|
return {
|
|
4347
4398
|
content: [{
|
|
4348
4399
|
type: "text",
|
|
4349
|
-
text: JSON.stringify({
|
|
4400
|
+
text: JSON.stringify({
|
|
4401
|
+
hook: name,
|
|
4402
|
+
output,
|
|
4403
|
+
stderr: result.stderr || undefined,
|
|
4404
|
+
exitCode: result.exitCode,
|
|
4405
|
+
...result.timedOut ? { timedOut: true, timeout_ms } : {}
|
|
4406
|
+
})
|
|
4350
4407
|
}]
|
|
4351
4408
|
};
|
|
4352
4409
|
});
|
|
4410
|
+
server.tool("hooks_update", "Re-register installed hooks to pick up new package version (reinstalls with overwrite)", {
|
|
4411
|
+
hooks: z.array(z.string()).optional().describe("Hook names to update (omit to update all installed hooks)"),
|
|
4412
|
+
scope: z.enum(["global", "project"]).default("global").describe("Scope to update")
|
|
4413
|
+
}, async ({ hooks, scope }) => {
|
|
4414
|
+
const installed = getRegisteredHooks(scope);
|
|
4415
|
+
const toUpdate = hooks && hooks.length > 0 ? hooks : installed;
|
|
4416
|
+
if (toUpdate.length === 0) {
|
|
4417
|
+
return { content: [{ type: "text", text: JSON.stringify({ updated: [], error: "No hooks installed" }) }] };
|
|
4418
|
+
}
|
|
4419
|
+
const results = toUpdate.map((name) => {
|
|
4420
|
+
if (!installed.includes(name)) {
|
|
4421
|
+
return { hook: name, success: false, error: "Not installed" };
|
|
4422
|
+
}
|
|
4423
|
+
return installHook(name, { scope, overwrite: true });
|
|
4424
|
+
});
|
|
4425
|
+
const updated = results.filter((r) => r.success).map((r) => r.hook);
|
|
4426
|
+
const failed = results.filter((r) => !r.success).map((r) => ({ hook: r.hook, error: r.error }));
|
|
4427
|
+
return { content: [{ type: "text", text: JSON.stringify({ updated, failed, total: results.length }) }] };
|
|
4428
|
+
});
|
|
4353
4429
|
server.tool("hooks_init", "Register a new agent profile \u2014 returns a unique agent_id for use with hook installation and execution", {
|
|
4354
4430
|
agent_type: z.enum(["claude", "gemini", "custom"]).default("claude").describe("Type of AI agent"),
|
|
4355
4431
|
name: z.string().optional().describe("Optional display name for the agent")
|
|
@@ -5617,6 +5693,28 @@ function resolveScope(options) {
|
|
|
5617
5693
|
return "project";
|
|
5618
5694
|
return "global";
|
|
5619
5695
|
}
|
|
5696
|
+
function resolveTarget(options) {
|
|
5697
|
+
if (options.target === "gemini")
|
|
5698
|
+
return "gemini";
|
|
5699
|
+
if (options.target === "all")
|
|
5700
|
+
return "all";
|
|
5701
|
+
return "claude";
|
|
5702
|
+
}
|
|
5703
|
+
function editDistance(a, b) {
|
|
5704
|
+
const m = a.length, n = b.length;
|
|
5705
|
+
const dp = Array.from({ length: m + 1 }, (_, i) => [i, ...Array(n).fill(0)]);
|
|
5706
|
+
for (let j = 0;j <= n; j++)
|
|
5707
|
+
dp[0][j] = j;
|
|
5708
|
+
for (let i = 1;i <= m; i++) {
|
|
5709
|
+
for (let j = 1;j <= n; j++) {
|
|
5710
|
+
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]);
|
|
5711
|
+
}
|
|
5712
|
+
}
|
|
5713
|
+
return dp[m][n];
|
|
5714
|
+
}
|
|
5715
|
+
function suggestHooks(name, max = 3) {
|
|
5716
|
+
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);
|
|
5717
|
+
}
|
|
5620
5718
|
program2.name("hooks").description("Install hooks for AI coding agents").version(pkg2.version);
|
|
5621
5719
|
program2.command("interactive", { isDefault: true }).alias("i").description("Interactive hook browser").action(() => {
|
|
5622
5720
|
render(/* @__PURE__ */ jsxDEV8(App, {}, undefined, false, undefined, this));
|
|
@@ -5696,8 +5794,9 @@ program2.command("run").argument("<hook>", "Hook to run").option("--profile <id>
|
|
|
5696
5794
|
process.stderr.write(stderr);
|
|
5697
5795
|
process.exit(exitCode);
|
|
5698
5796
|
});
|
|
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) => {
|
|
5797
|
+
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
5798
|
const scope = resolveScope(options);
|
|
5799
|
+
const target = resolveTarget(options);
|
|
5701
5800
|
let toInstall = hooks;
|
|
5702
5801
|
if (options.all) {
|
|
5703
5802
|
toInstall = HOOKS.map((h) => h.name);
|
|
@@ -5718,9 +5817,38 @@ program2.command("install").alias("add").argument("[hooks...]", "Hooks to instal
|
|
|
5718
5817
|
render(/* @__PURE__ */ jsxDEV8(App, {}, undefined, false, undefined, this));
|
|
5719
5818
|
return;
|
|
5720
5819
|
}
|
|
5820
|
+
if (options.dryRun) {
|
|
5821
|
+
const known = toInstall.filter((n) => getHook(n));
|
|
5822
|
+
const unknown = toInstall.filter((n) => !getHook(n));
|
|
5823
|
+
if (options.json) {
|
|
5824
|
+
console.log(JSON.stringify({ dryRun: true, would_install: known, unknown, scope, target }));
|
|
5825
|
+
return;
|
|
5826
|
+
}
|
|
5827
|
+
console.log(chalk2.bold(`
|
|
5828
|
+
Dry run \u2014 would install (${scope}, ${target}):
|
|
5829
|
+
`));
|
|
5830
|
+
for (const name of known) {
|
|
5831
|
+
const meta = getHook(name);
|
|
5832
|
+
console.log(chalk2.cyan(` ${name}`) + chalk2.dim(` [${meta.event}${meta.matcher ? ` ${meta.matcher}` : ""}]`));
|
|
5833
|
+
}
|
|
5834
|
+
if (unknown.length > 0) {
|
|
5835
|
+
console.log();
|
|
5836
|
+
for (const name of unknown) {
|
|
5837
|
+
const suggestions = suggestHooks(name);
|
|
5838
|
+
console.log(chalk2.red(` \u2717 unknown: ${name}`) + (suggestions.length ? chalk2.dim(` \u2014 did you mean: ${suggestions.join(", ")}?`) : ""));
|
|
5839
|
+
}
|
|
5840
|
+
}
|
|
5841
|
+
return;
|
|
5842
|
+
}
|
|
5721
5843
|
const results = [];
|
|
5722
5844
|
for (const name of toInstall) {
|
|
5723
|
-
|
|
5845
|
+
if (!getHook(name)) {
|
|
5846
|
+
const suggestions = suggestHooks(name);
|
|
5847
|
+
const hint = suggestions.length ? ` \u2014 did you mean: ${suggestions.join(", ")}?` : "";
|
|
5848
|
+
results.push({ hook: name, success: false, error: `Hook '${name}' not found${hint}` });
|
|
5849
|
+
continue;
|
|
5850
|
+
}
|
|
5851
|
+
const result = installHook(name, { scope, overwrite: options.overwrite, target, profile: options.profile });
|
|
5724
5852
|
results.push(result);
|
|
5725
5853
|
}
|
|
5726
5854
|
if (options.json) {
|
|
@@ -5729,13 +5857,14 @@ program2.command("install").alias("add").argument("[hooks...]", "Hooks to instal
|
|
|
5729
5857
|
failed: results.filter((r) => !r.success).map((r) => ({ hook: r.hook, error: r.error })),
|
|
5730
5858
|
total: results.length,
|
|
5731
5859
|
success: results.filter((r) => r.success).length,
|
|
5732
|
-
scope
|
|
5860
|
+
scope,
|
|
5861
|
+
target
|
|
5733
5862
|
}));
|
|
5734
5863
|
return;
|
|
5735
5864
|
}
|
|
5736
5865
|
const settingsFile = scope === "project" ? ".claude/settings.json" : "~/.claude/settings.json";
|
|
5737
5866
|
console.log(chalk2.bold(`
|
|
5738
|
-
Installing hooks (${scope})...
|
|
5867
|
+
Installing hooks (${scope}, ${target})...
|
|
5739
5868
|
`));
|
|
5740
5869
|
for (const result of results) {
|
|
5741
5870
|
if (result.success) {
|
|
@@ -5744,6 +5873,9 @@ Installing hooks (${scope})...
|
|
|
5744
5873
|
if (meta) {
|
|
5745
5874
|
console.log(chalk2.dim(` ${meta.event}${meta.matcher ? ` [${meta.matcher}]` : ""} \u2192 hooks run ${result.hook}`));
|
|
5746
5875
|
}
|
|
5876
|
+
if (result.conflict) {
|
|
5877
|
+
console.log(chalk2.yellow(` \u26A0 Warning: ${result.conflict}`));
|
|
5878
|
+
}
|
|
5747
5879
|
} else {
|
|
5748
5880
|
console.log(chalk2.red(`\u2717 ${result.hook}: ${result.error}`));
|
|
5749
5881
|
}
|
|
@@ -5838,17 +5970,28 @@ Found ${results.length} hook(s):
|
|
|
5838
5970
|
console.log(` ${h.description}`);
|
|
5839
5971
|
}
|
|
5840
5972
|
});
|
|
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) => {
|
|
5973
|
+
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
5974
|
const scope = resolveScope(options);
|
|
5843
|
-
const
|
|
5975
|
+
const target = resolveTarget(options);
|
|
5976
|
+
if (!getHook(hook)) {
|
|
5977
|
+
const suggestions = suggestHooks(hook);
|
|
5978
|
+
const hint = suggestions.length ? ` \u2014 did you mean: ${suggestions.join(", ")}?` : "";
|
|
5979
|
+
if (options.json) {
|
|
5980
|
+
console.log(JSON.stringify({ hook, removed: false, scope, target, error: `Hook '${hook}' not found${hint}`, suggestions }));
|
|
5981
|
+
} else {
|
|
5982
|
+
console.log(chalk2.red(`\u2717 Hook '${hook}' not found${hint}`));
|
|
5983
|
+
}
|
|
5984
|
+
return;
|
|
5985
|
+
}
|
|
5986
|
+
const removed = removeHook(hook, scope, target);
|
|
5844
5987
|
if (options.json) {
|
|
5845
|
-
console.log(JSON.stringify({ hook, removed, scope }));
|
|
5988
|
+
console.log(JSON.stringify({ hook, removed, scope, target }));
|
|
5846
5989
|
return;
|
|
5847
5990
|
}
|
|
5848
5991
|
if (removed) {
|
|
5849
|
-
console.log(chalk2.green(`\u2713 Removed ${hook} (${scope})`));
|
|
5992
|
+
console.log(chalk2.green(`\u2713 Removed ${hook} (${scope}, ${target})`));
|
|
5850
5993
|
} else {
|
|
5851
|
-
console.log(chalk2.red(`\u2717 ${hook} is not installed (${scope})`));
|
|
5994
|
+
console.log(chalk2.red(`\u2717 ${hook} is not installed (${scope}, ${target})`));
|
|
5852
5995
|
}
|
|
5853
5996
|
});
|
|
5854
5997
|
program2.command("categories").option("-j, --json", "Output as JSON", false).description("List all categories").action((options) => {
|
|
@@ -5871,10 +6014,12 @@ Categories:
|
|
|
5871
6014
|
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
6015
|
const meta = getHook(hook);
|
|
5873
6016
|
if (!meta) {
|
|
6017
|
+
const suggestions = suggestHooks(hook);
|
|
6018
|
+
const hint = suggestions.length ? ` \u2014 did you mean: ${suggestions.join(", ")}?` : "";
|
|
5874
6019
|
if (options.json) {
|
|
5875
|
-
console.log(JSON.stringify({ error: `Hook '${hook}' not found
|
|
6020
|
+
console.log(JSON.stringify({ error: `Hook '${hook}' not found${hint}`, suggestions }));
|
|
5876
6021
|
} else {
|
|
5877
|
-
console.log(chalk2.red(`Hook '${hook}' not found`));
|
|
6022
|
+
console.log(chalk2.red(`Hook '${hook}' not found${hint}`));
|
|
5878
6023
|
}
|
|
5879
6024
|
return;
|
|
5880
6025
|
}
|
|
@@ -6199,6 +6344,56 @@ Upgrading @hasna/hooks (${pm})...
|
|
|
6199
6344
|
\u2713 Upgraded: ${current} \u2192 ${latest}`));
|
|
6200
6345
|
}
|
|
6201
6346
|
});
|
|
6347
|
+
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) => {
|
|
6348
|
+
const profiles = exportProfiles();
|
|
6349
|
+
const json = JSON.stringify(profiles, null, 2);
|
|
6350
|
+
if (options.output) {
|
|
6351
|
+
const { writeFileSync: writeFileSync3 } = await import("fs");
|
|
6352
|
+
writeFileSync3(options.output, json + `
|
|
6353
|
+
`);
|
|
6354
|
+
console.log(chalk2.green(`\u2713 Exported ${profiles.length} profile(s) to ${options.output}`));
|
|
6355
|
+
} else {
|
|
6356
|
+
console.log(json);
|
|
6357
|
+
}
|
|
6358
|
+
});
|
|
6359
|
+
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) => {
|
|
6360
|
+
let raw;
|
|
6361
|
+
if (file === "-") {
|
|
6362
|
+
raw = await new Response(Bun.stdin.stream()).text();
|
|
6363
|
+
} else {
|
|
6364
|
+
const { readFileSync: readFileSync5 } = await import("fs");
|
|
6365
|
+
try {
|
|
6366
|
+
raw = readFileSync5(file, "utf-8");
|
|
6367
|
+
} catch {
|
|
6368
|
+
if (options.json) {
|
|
6369
|
+
console.log(JSON.stringify({ error: `Cannot read file: ${file}` }));
|
|
6370
|
+
} else {
|
|
6371
|
+
console.log(chalk2.red(`\u2717 Cannot read file: ${file}`));
|
|
6372
|
+
}
|
|
6373
|
+
return;
|
|
6374
|
+
}
|
|
6375
|
+
}
|
|
6376
|
+
let profiles;
|
|
6377
|
+
try {
|
|
6378
|
+
const parsed = JSON.parse(raw);
|
|
6379
|
+
profiles = Array.isArray(parsed) ? parsed : [parsed];
|
|
6380
|
+
} catch {
|
|
6381
|
+
if (options.json) {
|
|
6382
|
+
console.log(JSON.stringify({ error: "Invalid JSON" }));
|
|
6383
|
+
} else {
|
|
6384
|
+
console.log(chalk2.red("\u2717 Invalid JSON"));
|
|
6385
|
+
}
|
|
6386
|
+
return;
|
|
6387
|
+
}
|
|
6388
|
+
const result = importProfiles(profiles);
|
|
6389
|
+
if (options.json) {
|
|
6390
|
+
console.log(JSON.stringify(result));
|
|
6391
|
+
} else {
|
|
6392
|
+
console.log(chalk2.green(`\u2713 Imported ${result.imported} profile(s)`));
|
|
6393
|
+
if (result.skipped > 0)
|
|
6394
|
+
console.log(chalk2.dim(` Skipped ${result.skipped} (already exist or invalid)`));
|
|
6395
|
+
}
|
|
6396
|
+
});
|
|
6202
6397
|
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
6398
|
if (options.stdio) {
|
|
6204
6399
|
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