agentpacks 0.5.0 → 0.6.0

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 (41) hide show
  1. package/README.md +8 -1
  2. package/dist/api.js +98 -19
  3. package/dist/cli/export-cmd.js +76 -19
  4. package/dist/cli/generate.js +29 -0
  5. package/dist/cli/install.js +7 -0
  6. package/dist/cli/models-explain.js +7 -0
  7. package/dist/cli/pack/list.js +7 -0
  8. package/dist/cli/pack/validate.js +7 -0
  9. package/dist/cli/publish.js +7 -0
  10. package/dist/core/config.d.ts +7 -0
  11. package/dist/core/config.js +7 -0
  12. package/dist/core/index.js +7 -0
  13. package/dist/core/metarepo.js +7 -0
  14. package/dist/core/pack-loader.js +7 -0
  15. package/dist/exporters/cursor-plugin.d.ts +18 -18
  16. package/dist/exporters/cursor-plugin.js +117 -29
  17. package/dist/exporters/index.js +117 -29
  18. package/dist/index.js +98 -19
  19. package/dist/node/api.js +98 -19
  20. package/dist/node/cli/export-cmd.js +76 -19
  21. package/dist/node/cli/generate.js +29 -0
  22. package/dist/node/cli/install.js +7 -0
  23. package/dist/node/cli/models-explain.js +7 -0
  24. package/dist/node/cli/pack/list.js +7 -0
  25. package/dist/node/cli/pack/validate.js +7 -0
  26. package/dist/node/cli/publish.js +7 -0
  27. package/dist/node/core/config.js +7 -0
  28. package/dist/node/core/index.js +7 -0
  29. package/dist/node/core/metarepo.js +7 -0
  30. package/dist/node/core/pack-loader.js +7 -0
  31. package/dist/node/exporters/cursor-plugin.js +117 -29
  32. package/dist/node/exporters/index.js +117 -29
  33. package/dist/node/index.js +98 -19
  34. package/dist/node/targets/cursor.js +67 -7
  35. package/dist/node/targets/index.js +22 -0
  36. package/dist/node/targets/registry.js +22 -0
  37. package/dist/targets/cursor.d.ts +1 -1
  38. package/dist/targets/cursor.js +67 -7
  39. package/dist/targets/index.js +22 -0
  40. package/dist/targets/registry.js +22 -0
  41. package/package.json +2 -2
@@ -251,6 +251,44 @@ function skillMatchesTarget(skill, targetId) {
251
251
  return Array.isArray(targets) && targets.includes(targetId);
252
252
  }
253
253
 
254
+ // src/features/hooks.ts
255
+ import { join as join3 } from "path";
256
+ var TARGET_OVERRIDE_KEYS = ["cursor", "claudecode", "opencode"];
257
+ function parseHooks(packDir, packName) {
258
+ const hooksPath = join3(packDir, "hooks", "hooks.json");
259
+ const raw = readJsonOrNull(hooksPath);
260
+ if (!raw)
261
+ return null;
262
+ const shared = raw.hooks ?? {};
263
+ const targetOverrides = {};
264
+ for (const key of TARGET_OVERRIDE_KEYS) {
265
+ const override = raw[key];
266
+ if (override && typeof override === "object" && "hooks" in override && override.hooks) {
267
+ targetOverrides[key] = override.hooks;
268
+ }
269
+ }
270
+ return {
271
+ packName,
272
+ sourcePath: hooksPath,
273
+ version: raw.version,
274
+ shared,
275
+ targetOverrides
276
+ };
277
+ }
278
+ function resolveHooksForTarget(hooks, targetId) {
279
+ const merged = {};
280
+ for (const [event, entries] of Object.entries(hooks.shared)) {
281
+ merged[event] = [...entries];
282
+ }
283
+ const overrides = hooks.targetOverrides[targetId];
284
+ if (overrides) {
285
+ for (const [event, entries] of Object.entries(overrides)) {
286
+ merged[event] = [...merged[event] ?? [], ...entries];
287
+ }
288
+ }
289
+ return merged;
290
+ }
291
+
254
292
  // src/core/profile-resolver.ts
255
293
  function resolveModels(merged, modelProfile, targetId) {
256
294
  let defaultModel = merged.default;
@@ -374,7 +412,7 @@ class BaseTarget {
374
412
  }
375
413
 
376
414
  // src/targets/cursor.ts
377
- import { resolve, join as join3 } from "path";
415
+ import { resolve, join as join4 } from "path";
378
416
  var TARGET_ID = "cursor";
379
417
 
380
418
  class CursorTarget extends BaseTarget {
@@ -416,7 +454,7 @@ class CursorTarget extends BaseTarget {
416
454
  if (globs) {
417
455
  frontmatter.globs = globs;
418
456
  }
419
- const filepath = join3(rulesDir, `${rule.name}.mdc`);
457
+ const filepath = join4(rulesDir, `${rule.name}.mdc`);
420
458
  const content = serializeFrontmatter(frontmatter, rule.content);
421
459
  writeGeneratedFile(filepath, content);
422
460
  filesWritten.push(filepath);
@@ -442,7 +480,7 @@ class CursorTarget extends BaseTarget {
442
480
  if (model) {
443
481
  frontmatter.model = model;
444
482
  }
445
- const filepath = join3(agentsDir, `${agent.name}.md`);
483
+ const filepath = join4(agentsDir, `${agent.name}.md`);
446
484
  const content = serializeFrontmatter(frontmatter, agent.content);
447
485
  writeGeneratedFile(filepath, content);
448
486
  filesWritten.push(filepath);
@@ -457,13 +495,13 @@ class CursorTarget extends BaseTarget {
457
495
  ensureDir(skillsDir);
458
496
  const skills = features.skills.filter((s) => skillMatchesTarget(s, TARGET_ID));
459
497
  for (const skill of skills) {
460
- const skillSubDir = join3(skillsDir, skill.name);
498
+ const skillSubDir = join4(skillsDir, skill.name);
461
499
  ensureDir(skillSubDir);
462
500
  const frontmatter = {
463
501
  name: skill.name,
464
502
  description: skill.meta.description ?? ""
465
503
  };
466
- const filepath = join3(skillSubDir, "SKILL.md");
504
+ const filepath = join4(skillSubDir, "SKILL.md");
467
505
  const content = serializeFrontmatter(frontmatter, skill.content);
468
506
  writeGeneratedFile(filepath, content);
469
507
  filesWritten.push(filepath);
@@ -478,11 +516,33 @@ class CursorTarget extends BaseTarget {
478
516
  ensureDir(commandsDir);
479
517
  const commands = features.commands.filter((c) => commandMatchesTarget(c, TARGET_ID));
480
518
  for (const cmd of commands) {
481
- const filepath = join3(commandsDir, `${cmd.name}.md`);
519
+ const filepath = join4(commandsDir, `${cmd.name}.md`);
482
520
  writeGeneratedFile(filepath, cmd.content);
483
521
  filesWritten.push(filepath);
484
522
  }
485
523
  }
524
+ if (effective.includes("hooks")) {
525
+ const hooksFilepath = resolve(cursorDir, "hooks.json");
526
+ if (deleteExisting) {
527
+ removeIfExists(hooksFilepath);
528
+ filesDeleted.push(hooksFilepath);
529
+ }
530
+ const mergedHooks = {};
531
+ for (const hookSet of features.hooks) {
532
+ const events = resolveHooksForTarget(hookSet, TARGET_ID);
533
+ for (const [event, entries] of Object.entries(events)) {
534
+ if (!mergedHooks[event]) {
535
+ mergedHooks[event] = [];
536
+ }
537
+ mergedHooks[event].push(...entries);
538
+ }
539
+ }
540
+ if (Object.keys(mergedHooks).length > 0) {
541
+ const hooksVersion = features.hooks.find((h) => h.version !== undefined)?.version ?? 1;
542
+ writeGeneratedJson(hooksFilepath, { version: hooksVersion, hooks: mergedHooks }, { header: false });
543
+ filesWritten.push(hooksFilepath);
544
+ }
545
+ }
486
546
  if (effective.includes("mcp")) {
487
547
  const mcpEntries = Object.entries(features.mcpServers);
488
548
  if (mcpEntries.length > 0) {
@@ -508,7 +568,7 @@ class CursorTarget extends BaseTarget {
508
568
  if (guidanceContent) {
509
569
  const rulesDir = resolve(cursorDir, "rules");
510
570
  ensureDir(rulesDir);
511
- const filepath = join3(rulesDir, "model-config.mdc");
571
+ const filepath = join4(rulesDir, "model-config.mdc");
512
572
  writeGeneratedFile(filepath, guidanceContent, { header: false });
513
573
  filesWritten.push(filepath);
514
574
  }
@@ -782,6 +782,28 @@ class CursorTarget extends BaseTarget {
782
782
  filesWritten.push(filepath);
783
783
  }
784
784
  }
785
+ if (effective.includes("hooks")) {
786
+ const hooksFilepath = resolve2(cursorDir, "hooks.json");
787
+ if (deleteExisting) {
788
+ removeIfExists(hooksFilepath);
789
+ filesDeleted.push(hooksFilepath);
790
+ }
791
+ const mergedHooks = {};
792
+ for (const hookSet of features.hooks) {
793
+ const events = resolveHooksForTarget(hookSet, TARGET_ID2);
794
+ for (const [event, entries] of Object.entries(events)) {
795
+ if (!mergedHooks[event]) {
796
+ mergedHooks[event] = [];
797
+ }
798
+ mergedHooks[event].push(...entries);
799
+ }
800
+ }
801
+ if (Object.keys(mergedHooks).length > 0) {
802
+ const hooksVersion = features.hooks.find((h) => h.version !== undefined)?.version ?? 1;
803
+ writeGeneratedJson(hooksFilepath, { version: hooksVersion, hooks: mergedHooks }, { header: false });
804
+ filesWritten.push(hooksFilepath);
805
+ }
806
+ }
785
807
  if (effective.includes("mcp")) {
786
808
  const mcpEntries = Object.entries(features.mcpServers);
787
809
  if (mcpEntries.length > 0) {
@@ -782,6 +782,28 @@ class CursorTarget extends BaseTarget {
782
782
  filesWritten.push(filepath);
783
783
  }
784
784
  }
785
+ if (effective.includes("hooks")) {
786
+ const hooksFilepath = resolve2(cursorDir, "hooks.json");
787
+ if (deleteExisting) {
788
+ removeIfExists(hooksFilepath);
789
+ filesDeleted.push(hooksFilepath);
790
+ }
791
+ const mergedHooks = {};
792
+ for (const hookSet of features.hooks) {
793
+ const events = resolveHooksForTarget(hookSet, TARGET_ID2);
794
+ for (const [event, entries] of Object.entries(events)) {
795
+ if (!mergedHooks[event]) {
796
+ mergedHooks[event] = [];
797
+ }
798
+ mergedHooks[event].push(...entries);
799
+ }
800
+ }
801
+ if (Object.keys(mergedHooks).length > 0) {
802
+ const hooksVersion = features.hooks.find((h) => h.version !== undefined)?.version ?? 1;
803
+ writeGeneratedJson(hooksFilepath, { version: hooksVersion, hooks: mergedHooks }, { header: false });
804
+ filesWritten.push(hooksFilepath);
805
+ }
806
+ }
785
807
  if (effective.includes("mcp")) {
786
808
  const mcpEntries = Object.entries(features.mcpServers);
787
809
  if (mcpEntries.length > 0) {
@@ -3,7 +3,7 @@ import { BaseTarget, type GenerateOptions, type GenerateResult } from './base-ta
3
3
  /**
4
4
  * Cursor target generator.
5
5
  * Generates: .cursor/rules/, .cursor/skills/, .cursor/agents/, .cursor/commands/,
6
- * .cursor/mcp.json, .cursorignore
6
+ * .cursor/hooks.json, .cursor/mcp.json, .cursorignore
7
7
  */
8
8
  export declare class CursorTarget extends BaseTarget {
9
9
  readonly id = "cursor";
@@ -251,6 +251,44 @@ function skillMatchesTarget(skill, targetId) {
251
251
  return Array.isArray(targets) && targets.includes(targetId);
252
252
  }
253
253
 
254
+ // src/features/hooks.ts
255
+ import { join as join3 } from "path";
256
+ var TARGET_OVERRIDE_KEYS = ["cursor", "claudecode", "opencode"];
257
+ function parseHooks(packDir, packName) {
258
+ const hooksPath = join3(packDir, "hooks", "hooks.json");
259
+ const raw = readJsonOrNull(hooksPath);
260
+ if (!raw)
261
+ return null;
262
+ const shared = raw.hooks ?? {};
263
+ const targetOverrides = {};
264
+ for (const key of TARGET_OVERRIDE_KEYS) {
265
+ const override = raw[key];
266
+ if (override && typeof override === "object" && "hooks" in override && override.hooks) {
267
+ targetOverrides[key] = override.hooks;
268
+ }
269
+ }
270
+ return {
271
+ packName,
272
+ sourcePath: hooksPath,
273
+ version: raw.version,
274
+ shared,
275
+ targetOverrides
276
+ };
277
+ }
278
+ function resolveHooksForTarget(hooks, targetId) {
279
+ const merged = {};
280
+ for (const [event, entries] of Object.entries(hooks.shared)) {
281
+ merged[event] = [...entries];
282
+ }
283
+ const overrides = hooks.targetOverrides[targetId];
284
+ if (overrides) {
285
+ for (const [event, entries] of Object.entries(overrides)) {
286
+ merged[event] = [...merged[event] ?? [], ...entries];
287
+ }
288
+ }
289
+ return merged;
290
+ }
291
+
254
292
  // src/core/profile-resolver.ts
255
293
  function resolveModels(merged, modelProfile, targetId) {
256
294
  let defaultModel = merged.default;
@@ -374,7 +412,7 @@ class BaseTarget {
374
412
  }
375
413
 
376
414
  // src/targets/cursor.ts
377
- import { resolve, join as join3 } from "path";
415
+ import { resolve, join as join4 } from "path";
378
416
  var TARGET_ID = "cursor";
379
417
 
380
418
  class CursorTarget extends BaseTarget {
@@ -416,7 +454,7 @@ class CursorTarget extends BaseTarget {
416
454
  if (globs) {
417
455
  frontmatter.globs = globs;
418
456
  }
419
- const filepath = join3(rulesDir, `${rule.name}.mdc`);
457
+ const filepath = join4(rulesDir, `${rule.name}.mdc`);
420
458
  const content = serializeFrontmatter(frontmatter, rule.content);
421
459
  writeGeneratedFile(filepath, content);
422
460
  filesWritten.push(filepath);
@@ -442,7 +480,7 @@ class CursorTarget extends BaseTarget {
442
480
  if (model) {
443
481
  frontmatter.model = model;
444
482
  }
445
- const filepath = join3(agentsDir, `${agent.name}.md`);
483
+ const filepath = join4(agentsDir, `${agent.name}.md`);
446
484
  const content = serializeFrontmatter(frontmatter, agent.content);
447
485
  writeGeneratedFile(filepath, content);
448
486
  filesWritten.push(filepath);
@@ -457,13 +495,13 @@ class CursorTarget extends BaseTarget {
457
495
  ensureDir(skillsDir);
458
496
  const skills = features.skills.filter((s) => skillMatchesTarget(s, TARGET_ID));
459
497
  for (const skill of skills) {
460
- const skillSubDir = join3(skillsDir, skill.name);
498
+ const skillSubDir = join4(skillsDir, skill.name);
461
499
  ensureDir(skillSubDir);
462
500
  const frontmatter = {
463
501
  name: skill.name,
464
502
  description: skill.meta.description ?? ""
465
503
  };
466
- const filepath = join3(skillSubDir, "SKILL.md");
504
+ const filepath = join4(skillSubDir, "SKILL.md");
467
505
  const content = serializeFrontmatter(frontmatter, skill.content);
468
506
  writeGeneratedFile(filepath, content);
469
507
  filesWritten.push(filepath);
@@ -478,11 +516,33 @@ class CursorTarget extends BaseTarget {
478
516
  ensureDir(commandsDir);
479
517
  const commands = features.commands.filter((c) => commandMatchesTarget(c, TARGET_ID));
480
518
  for (const cmd of commands) {
481
- const filepath = join3(commandsDir, `${cmd.name}.md`);
519
+ const filepath = join4(commandsDir, `${cmd.name}.md`);
482
520
  writeGeneratedFile(filepath, cmd.content);
483
521
  filesWritten.push(filepath);
484
522
  }
485
523
  }
524
+ if (effective.includes("hooks")) {
525
+ const hooksFilepath = resolve(cursorDir, "hooks.json");
526
+ if (deleteExisting) {
527
+ removeIfExists(hooksFilepath);
528
+ filesDeleted.push(hooksFilepath);
529
+ }
530
+ const mergedHooks = {};
531
+ for (const hookSet of features.hooks) {
532
+ const events = resolveHooksForTarget(hookSet, TARGET_ID);
533
+ for (const [event, entries] of Object.entries(events)) {
534
+ if (!mergedHooks[event]) {
535
+ mergedHooks[event] = [];
536
+ }
537
+ mergedHooks[event].push(...entries);
538
+ }
539
+ }
540
+ if (Object.keys(mergedHooks).length > 0) {
541
+ const hooksVersion = features.hooks.find((h) => h.version !== undefined)?.version ?? 1;
542
+ writeGeneratedJson(hooksFilepath, { version: hooksVersion, hooks: mergedHooks }, { header: false });
543
+ filesWritten.push(hooksFilepath);
544
+ }
545
+ }
486
546
  if (effective.includes("mcp")) {
487
547
  const mcpEntries = Object.entries(features.mcpServers);
488
548
  if (mcpEntries.length > 0) {
@@ -508,7 +568,7 @@ class CursorTarget extends BaseTarget {
508
568
  if (guidanceContent) {
509
569
  const rulesDir = resolve(cursorDir, "rules");
510
570
  ensureDir(rulesDir);
511
- const filepath = join3(rulesDir, "model-config.mdc");
571
+ const filepath = join4(rulesDir, "model-config.mdc");
512
572
  writeGeneratedFile(filepath, guidanceContent, { header: false });
513
573
  filesWritten.push(filepath);
514
574
  }
@@ -782,6 +782,28 @@ class CursorTarget extends BaseTarget {
782
782
  filesWritten.push(filepath);
783
783
  }
784
784
  }
785
+ if (effective.includes("hooks")) {
786
+ const hooksFilepath = resolve2(cursorDir, "hooks.json");
787
+ if (deleteExisting) {
788
+ removeIfExists(hooksFilepath);
789
+ filesDeleted.push(hooksFilepath);
790
+ }
791
+ const mergedHooks = {};
792
+ for (const hookSet of features.hooks) {
793
+ const events = resolveHooksForTarget(hookSet, TARGET_ID2);
794
+ for (const [event, entries] of Object.entries(events)) {
795
+ if (!mergedHooks[event]) {
796
+ mergedHooks[event] = [];
797
+ }
798
+ mergedHooks[event].push(...entries);
799
+ }
800
+ }
801
+ if (Object.keys(mergedHooks).length > 0) {
802
+ const hooksVersion = features.hooks.find((h) => h.version !== undefined)?.version ?? 1;
803
+ writeGeneratedJson(hooksFilepath, { version: hooksVersion, hooks: mergedHooks }, { header: false });
804
+ filesWritten.push(hooksFilepath);
805
+ }
806
+ }
785
807
  if (effective.includes("mcp")) {
786
808
  const mcpEntries = Object.entries(features.mcpServers);
787
809
  if (mcpEntries.length > 0) {
@@ -782,6 +782,28 @@ class CursorTarget extends BaseTarget {
782
782
  filesWritten.push(filepath);
783
783
  }
784
784
  }
785
+ if (effective.includes("hooks")) {
786
+ const hooksFilepath = resolve2(cursorDir, "hooks.json");
787
+ if (deleteExisting) {
788
+ removeIfExists(hooksFilepath);
789
+ filesDeleted.push(hooksFilepath);
790
+ }
791
+ const mergedHooks = {};
792
+ for (const hookSet of features.hooks) {
793
+ const events = resolveHooksForTarget(hookSet, TARGET_ID2);
794
+ for (const [event, entries] of Object.entries(events)) {
795
+ if (!mergedHooks[event]) {
796
+ mergedHooks[event] = [];
797
+ }
798
+ mergedHooks[event].push(...entries);
799
+ }
800
+ }
801
+ if (Object.keys(mergedHooks).length > 0) {
802
+ const hooksVersion = features.hooks.find((h) => h.version !== undefined)?.version ?? 1;
803
+ writeGeneratedJson(hooksFilepath, { version: hooksVersion, hooks: mergedHooks }, { header: false });
804
+ filesWritten.push(hooksFilepath);
805
+ }
806
+ }
785
807
  if (effective.includes("mcp")) {
786
808
  const mcpEntries = Object.entries(features.mcpServers);
787
809
  if (mcpEntries.length > 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentpacks",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "type": "module",
5
5
  "description": "Composable AI agent configuration manager. Pack-based rules, commands, skills, hooks, and MCP sync across OpenCode, Cursor, Claude Code, Codex, Gemini, Copilot, and more.",
6
6
  "keywords": [
@@ -500,7 +500,7 @@
500
500
  "gray-matter": "^4.0.3",
501
501
  "zod": "^4.3.5",
502
502
  "jsonc-parser": "^3.3.1",
503
- "glob": "^13.0.3"
503
+ "glob": "^13.0.6"
504
504
  },
505
505
  "devDependencies": {
506
506
  "@contractspec/tool.typescript": "2.6.0",