@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.
Files changed (3) hide show
  1. package/bin/index.js +216 -21
  2. package/dist/index.js +48 -1
  3. 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
- }, async ({ name, input, profile }) => {
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 [stdoutText, stderrText, exitCode] = await Promise.all([
4336
- new Response(proc.stdout).text(),
4337
- new Response(proc.stderr).text(),
4338
- proc.exited
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(stdoutText);
4393
+ output = JSON.parse(result.stdout);
4343
4394
  } catch {
4344
- output = { raw: stdoutText };
4395
+ output = result.stdout ? { raw: result.stdout } : {};
4345
4396
  }
4346
4397
  return {
4347
4398
  content: [{
4348
4399
  type: "text",
4349
- text: JSON.stringify({ hook: name, output, stderr: stderrText || undefined, exitCode })
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
- const result = installHook(name, { scope, overwrite: options.overwrite, profile: options.profile });
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 removed = removeHook(hook, scope);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/hooks",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "Open source hooks library for AI coding agents - Install safety, quality, and automation hooks with a single command",
5
5
  "type": "module",
6
6
  "bin": {