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
@@ -129,28 +129,84 @@ function serializeFrontmatter(data, content) {
129
129
  return matter.stringify(content, filtered);
130
130
  }
131
131
 
132
+ // src/features/hooks.ts
133
+ import { join as join2 } from "path";
134
+ var TARGET_OVERRIDE_KEYS = ["cursor", "claudecode", "opencode"];
135
+ function parseHooks(packDir, packName) {
136
+ const hooksPath = join2(packDir, "hooks", "hooks.json");
137
+ const raw = readJsonOrNull(hooksPath);
138
+ if (!raw)
139
+ return null;
140
+ const shared = raw.hooks ?? {};
141
+ const targetOverrides = {};
142
+ for (const key of TARGET_OVERRIDE_KEYS) {
143
+ const override = raw[key];
144
+ if (override && typeof override === "object" && "hooks" in override && override.hooks) {
145
+ targetOverrides[key] = override.hooks;
146
+ }
147
+ }
148
+ return {
149
+ packName,
150
+ sourcePath: hooksPath,
151
+ version: raw.version,
152
+ shared,
153
+ targetOverrides
154
+ };
155
+ }
156
+ function resolveHooksForTarget(hooks, targetId) {
157
+ const merged = {};
158
+ for (const [event, entries] of Object.entries(hooks.shared)) {
159
+ merged[event] = [...entries];
160
+ }
161
+ const overrides = hooks.targetOverrides[targetId];
162
+ if (overrides) {
163
+ for (const [event, entries] of Object.entries(overrides)) {
164
+ merged[event] = [...merged[event] ?? [], ...entries];
165
+ }
166
+ }
167
+ return merged;
168
+ }
169
+
132
170
  // src/exporters/cursor-plugin.ts
133
- import { resolve, join as join2 } from "path";
171
+ import { resolve, join as join3 } from "path";
134
172
  import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
135
173
  function exportCursorPlugin(pack, outputDir) {
136
174
  const filesWritten = [];
137
- const pluginDir = resolve(outputDir, pack.manifest.name);
175
+ const pluginName = normalizeCursorPluginName(pack.manifest.name);
176
+ const pluginDir = resolve(outputDir, pluginName);
138
177
  mkdirSync2(pluginDir, { recursive: true });
139
178
  const manifest = {
140
- name: pack.manifest.name,
141
- version: pack.manifest.version,
142
- description: pack.manifest.description
179
+ name: pluginName
143
180
  };
144
- if (pack.manifest.author) {
145
- manifest.author = typeof pack.manifest.author === "string" ? pack.manifest.author : pack.manifest.author.name;
181
+ if (pack.manifest.version) {
182
+ manifest.version = pack.manifest.version;
183
+ }
184
+ if (pack.manifest.description) {
185
+ manifest.description = pack.manifest.description;
186
+ }
187
+ const author = toCursorPluginAuthor(pack.manifest.author);
188
+ if (author) {
189
+ manifest.author = author;
190
+ }
191
+ if (pack.manifest.homepage) {
192
+ manifest.homepage = pack.manifest.homepage;
193
+ }
194
+ if (pack.manifest.repository) {
195
+ manifest.repository = typeof pack.manifest.repository === "string" ? pack.manifest.repository : pack.manifest.repository.url;
196
+ }
197
+ if (pack.manifest.license) {
198
+ manifest.license = pack.manifest.license;
199
+ }
200
+ if (pack.manifest.logo) {
201
+ manifest.logo = pack.manifest.logo;
146
202
  }
147
203
  if (pack.manifest.tags.length > 0) {
148
- manifest.tags = pack.manifest.tags;
204
+ manifest.keywords = pack.manifest.tags;
149
205
  }
150
206
  if (pack.rules.length > 0) {
151
- const rulesDir = join2(pluginDir, "rules");
207
+ const rulesDir = join3(pluginDir, "rules");
152
208
  ensureDir(rulesDir);
153
- manifest.rules = [];
209
+ manifest.rules = "rules";
154
210
  for (const rule of pack.rules) {
155
211
  const cursorMeta = rule.meta.cursor ?? {};
156
212
  const fm = {
@@ -161,70 +217,102 @@ function exportCursorPlugin(pack, outputDir) {
161
217
  if (globs)
162
218
  fm.globs = globs;
163
219
  const filename = `${rule.name}.mdc`;
164
- const filepath = join2(rulesDir, filename);
220
+ const filepath = join3(rulesDir, filename);
165
221
  writeFileSync2(filepath, serializeFrontmatter(fm, rule.content));
166
222
  filesWritten.push(filepath);
167
- manifest.rules.push(filename);
168
223
  }
169
224
  }
170
225
  if (pack.agents.length > 0) {
171
- const agentsDir = join2(pluginDir, "agents");
226
+ const agentsDir = join3(pluginDir, "agents");
172
227
  ensureDir(agentsDir);
173
- manifest.agents = [];
228
+ manifest.agents = "agents";
174
229
  for (const agent of pack.agents) {
175
230
  const fm = {
176
231
  name: agent.name,
177
232
  description: agent.meta.description ?? ""
178
233
  };
179
234
  const filename = `${agent.name}.md`;
180
- const filepath = join2(agentsDir, filename);
235
+ const filepath = join3(agentsDir, filename);
181
236
  writeFileSync2(filepath, serializeFrontmatter(fm, agent.content));
182
237
  filesWritten.push(filepath);
183
- manifest.agents.push(filename);
184
238
  }
185
239
  }
186
240
  if (pack.skills.length > 0) {
187
- const skillsDir = join2(pluginDir, "skills");
241
+ const skillsDir = join3(pluginDir, "skills");
188
242
  ensureDir(skillsDir);
189
- manifest.skills = [];
243
+ manifest.skills = "skills";
190
244
  for (const skill of pack.skills) {
191
- const skillSubDir = join2(skillsDir, skill.name);
245
+ const skillSubDir = join3(skillsDir, skill.name);
192
246
  ensureDir(skillSubDir);
193
247
  const fm = {
194
248
  name: skill.name,
195
249
  description: skill.meta.description ?? ""
196
250
  };
197
- const filepath = join2(skillSubDir, "SKILL.md");
251
+ const filepath = join3(skillSubDir, "SKILL.md");
198
252
  writeFileSync2(filepath, serializeFrontmatter(fm, skill.content));
199
253
  filesWritten.push(filepath);
200
- manifest.skills.push(skill.name);
201
254
  }
202
255
  }
203
256
  if (pack.commands.length > 0) {
204
- const commandsDir = join2(pluginDir, "commands");
257
+ const commandsDir = join3(pluginDir, "commands");
205
258
  ensureDir(commandsDir);
206
- manifest.commands = [];
259
+ manifest.commands = "commands";
207
260
  for (const cmd of pack.commands) {
261
+ const fm = {
262
+ name: cmd.name
263
+ };
264
+ if (cmd.meta.description) {
265
+ fm.description = cmd.meta.description;
266
+ }
208
267
  const filename = `${cmd.name}.md`;
209
- const filepath = join2(commandsDir, filename);
210
- writeFileSync2(filepath, cmd.content);
268
+ const filepath = join3(commandsDir, filename);
269
+ writeFileSync2(filepath, serializeFrontmatter(fm, cmd.content));
270
+ filesWritten.push(filepath);
271
+ }
272
+ }
273
+ if (pack.hooks) {
274
+ const events = resolveHooksForTarget(pack.hooks, "cursor");
275
+ if (Object.keys(events).length > 0) {
276
+ const hooksDir = join3(pluginDir, "hooks");
277
+ ensureDir(hooksDir);
278
+ const filepath = join3(hooksDir, "hooks.json");
279
+ writeFileSync2(filepath, JSON.stringify({ version: pack.hooks.version ?? 1, hooks: events }, null, 2) + `
280
+ `);
211
281
  filesWritten.push(filepath);
212
- manifest.commands.push(filename);
282
+ manifest.hooks = "hooks/hooks.json";
213
283
  }
214
284
  }
215
285
  if (pack.mcp && Object.keys(pack.mcp.servers).length > 0) {
216
- manifest.mcp = true;
217
- const filepath = join2(pluginDir, "mcp.json");
286
+ manifest.mcpServers = ".mcp.json";
287
+ const filepath = join3(pluginDir, ".mcp.json");
218
288
  writeFileSync2(filepath, JSON.stringify({ mcpServers: pack.mcp.servers }, null, 2) + `
219
289
  `);
220
290
  filesWritten.push(filepath);
221
291
  }
222
- const manifestPath = join2(pluginDir, "manifest.json");
292
+ const manifestDir = join3(pluginDir, ".cursor-plugin");
293
+ ensureDir(manifestDir);
294
+ const manifestPath = join3(manifestDir, "plugin.json");
223
295
  writeFileSync2(manifestPath, JSON.stringify(manifest, null, 2) + `
224
296
  `);
225
297
  filesWritten.push(manifestPath);
226
298
  return { outputDir: pluginDir, filesWritten, manifest };
227
299
  }
300
+ function normalizeCursorPluginName(name) {
301
+ const normalized = name.toLowerCase().replace(/[^a-z0-9.-]+/g, "-").replace(/-+/g, "-").replace(/^[^a-z0-9]+/, "").replace(/[^a-z0-9]+$/, "");
302
+ return normalized.length > 0 ? normalized : "agentpacks-plugin";
303
+ }
304
+ function toCursorPluginAuthor(author) {
305
+ if (!author) {
306
+ return null;
307
+ }
308
+ if (typeof author === "string") {
309
+ return { name: author };
310
+ }
311
+ return {
312
+ name: author.name,
313
+ ...author.email ? { email: author.email } : {}
314
+ };
315
+ }
228
316
  export {
229
317
  exportCursorPlugin
230
318
  };
@@ -129,28 +129,84 @@ function serializeFrontmatter(data, content) {
129
129
  return matter.stringify(content, filtered);
130
130
  }
131
131
 
132
+ // src/features/hooks.ts
133
+ import { join as join2 } from "path";
134
+ var TARGET_OVERRIDE_KEYS = ["cursor", "claudecode", "opencode"];
135
+ function parseHooks(packDir, packName) {
136
+ const hooksPath = join2(packDir, "hooks", "hooks.json");
137
+ const raw = readJsonOrNull(hooksPath);
138
+ if (!raw)
139
+ return null;
140
+ const shared = raw.hooks ?? {};
141
+ const targetOverrides = {};
142
+ for (const key of TARGET_OVERRIDE_KEYS) {
143
+ const override = raw[key];
144
+ if (override && typeof override === "object" && "hooks" in override && override.hooks) {
145
+ targetOverrides[key] = override.hooks;
146
+ }
147
+ }
148
+ return {
149
+ packName,
150
+ sourcePath: hooksPath,
151
+ version: raw.version,
152
+ shared,
153
+ targetOverrides
154
+ };
155
+ }
156
+ function resolveHooksForTarget(hooks, targetId) {
157
+ const merged = {};
158
+ for (const [event, entries] of Object.entries(hooks.shared)) {
159
+ merged[event] = [...entries];
160
+ }
161
+ const overrides = hooks.targetOverrides[targetId];
162
+ if (overrides) {
163
+ for (const [event, entries] of Object.entries(overrides)) {
164
+ merged[event] = [...merged[event] ?? [], ...entries];
165
+ }
166
+ }
167
+ return merged;
168
+ }
169
+
132
170
  // src/exporters/cursor-plugin.ts
133
- import { resolve, join as join2 } from "path";
171
+ import { resolve, join as join3 } from "path";
134
172
  import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
135
173
  function exportCursorPlugin(pack, outputDir) {
136
174
  const filesWritten = [];
137
- const pluginDir = resolve(outputDir, pack.manifest.name);
175
+ const pluginName = normalizeCursorPluginName(pack.manifest.name);
176
+ const pluginDir = resolve(outputDir, pluginName);
138
177
  mkdirSync2(pluginDir, { recursive: true });
139
178
  const manifest = {
140
- name: pack.manifest.name,
141
- version: pack.manifest.version,
142
- description: pack.manifest.description
179
+ name: pluginName
143
180
  };
144
- if (pack.manifest.author) {
145
- manifest.author = typeof pack.manifest.author === "string" ? pack.manifest.author : pack.manifest.author.name;
181
+ if (pack.manifest.version) {
182
+ manifest.version = pack.manifest.version;
183
+ }
184
+ if (pack.manifest.description) {
185
+ manifest.description = pack.manifest.description;
186
+ }
187
+ const author = toCursorPluginAuthor(pack.manifest.author);
188
+ if (author) {
189
+ manifest.author = author;
190
+ }
191
+ if (pack.manifest.homepage) {
192
+ manifest.homepage = pack.manifest.homepage;
193
+ }
194
+ if (pack.manifest.repository) {
195
+ manifest.repository = typeof pack.manifest.repository === "string" ? pack.manifest.repository : pack.manifest.repository.url;
196
+ }
197
+ if (pack.manifest.license) {
198
+ manifest.license = pack.manifest.license;
199
+ }
200
+ if (pack.manifest.logo) {
201
+ manifest.logo = pack.manifest.logo;
146
202
  }
147
203
  if (pack.manifest.tags.length > 0) {
148
- manifest.tags = pack.manifest.tags;
204
+ manifest.keywords = pack.manifest.tags;
149
205
  }
150
206
  if (pack.rules.length > 0) {
151
- const rulesDir = join2(pluginDir, "rules");
207
+ const rulesDir = join3(pluginDir, "rules");
152
208
  ensureDir(rulesDir);
153
- manifest.rules = [];
209
+ manifest.rules = "rules";
154
210
  for (const rule of pack.rules) {
155
211
  const cursorMeta = rule.meta.cursor ?? {};
156
212
  const fm = {
@@ -161,70 +217,102 @@ function exportCursorPlugin(pack, outputDir) {
161
217
  if (globs)
162
218
  fm.globs = globs;
163
219
  const filename = `${rule.name}.mdc`;
164
- const filepath = join2(rulesDir, filename);
220
+ const filepath = join3(rulesDir, filename);
165
221
  writeFileSync2(filepath, serializeFrontmatter(fm, rule.content));
166
222
  filesWritten.push(filepath);
167
- manifest.rules.push(filename);
168
223
  }
169
224
  }
170
225
  if (pack.agents.length > 0) {
171
- const agentsDir = join2(pluginDir, "agents");
226
+ const agentsDir = join3(pluginDir, "agents");
172
227
  ensureDir(agentsDir);
173
- manifest.agents = [];
228
+ manifest.agents = "agents";
174
229
  for (const agent of pack.agents) {
175
230
  const fm = {
176
231
  name: agent.name,
177
232
  description: agent.meta.description ?? ""
178
233
  };
179
234
  const filename = `${agent.name}.md`;
180
- const filepath = join2(agentsDir, filename);
235
+ const filepath = join3(agentsDir, filename);
181
236
  writeFileSync2(filepath, serializeFrontmatter(fm, agent.content));
182
237
  filesWritten.push(filepath);
183
- manifest.agents.push(filename);
184
238
  }
185
239
  }
186
240
  if (pack.skills.length > 0) {
187
- const skillsDir = join2(pluginDir, "skills");
241
+ const skillsDir = join3(pluginDir, "skills");
188
242
  ensureDir(skillsDir);
189
- manifest.skills = [];
243
+ manifest.skills = "skills";
190
244
  for (const skill of pack.skills) {
191
- const skillSubDir = join2(skillsDir, skill.name);
245
+ const skillSubDir = join3(skillsDir, skill.name);
192
246
  ensureDir(skillSubDir);
193
247
  const fm = {
194
248
  name: skill.name,
195
249
  description: skill.meta.description ?? ""
196
250
  };
197
- const filepath = join2(skillSubDir, "SKILL.md");
251
+ const filepath = join3(skillSubDir, "SKILL.md");
198
252
  writeFileSync2(filepath, serializeFrontmatter(fm, skill.content));
199
253
  filesWritten.push(filepath);
200
- manifest.skills.push(skill.name);
201
254
  }
202
255
  }
203
256
  if (pack.commands.length > 0) {
204
- const commandsDir = join2(pluginDir, "commands");
257
+ const commandsDir = join3(pluginDir, "commands");
205
258
  ensureDir(commandsDir);
206
- manifest.commands = [];
259
+ manifest.commands = "commands";
207
260
  for (const cmd of pack.commands) {
261
+ const fm = {
262
+ name: cmd.name
263
+ };
264
+ if (cmd.meta.description) {
265
+ fm.description = cmd.meta.description;
266
+ }
208
267
  const filename = `${cmd.name}.md`;
209
- const filepath = join2(commandsDir, filename);
210
- writeFileSync2(filepath, cmd.content);
268
+ const filepath = join3(commandsDir, filename);
269
+ writeFileSync2(filepath, serializeFrontmatter(fm, cmd.content));
270
+ filesWritten.push(filepath);
271
+ }
272
+ }
273
+ if (pack.hooks) {
274
+ const events = resolveHooksForTarget(pack.hooks, "cursor");
275
+ if (Object.keys(events).length > 0) {
276
+ const hooksDir = join3(pluginDir, "hooks");
277
+ ensureDir(hooksDir);
278
+ const filepath = join3(hooksDir, "hooks.json");
279
+ writeFileSync2(filepath, JSON.stringify({ version: pack.hooks.version ?? 1, hooks: events }, null, 2) + `
280
+ `);
211
281
  filesWritten.push(filepath);
212
- manifest.commands.push(filename);
282
+ manifest.hooks = "hooks/hooks.json";
213
283
  }
214
284
  }
215
285
  if (pack.mcp && Object.keys(pack.mcp.servers).length > 0) {
216
- manifest.mcp = true;
217
- const filepath = join2(pluginDir, "mcp.json");
286
+ manifest.mcpServers = ".mcp.json";
287
+ const filepath = join3(pluginDir, ".mcp.json");
218
288
  writeFileSync2(filepath, JSON.stringify({ mcpServers: pack.mcp.servers }, null, 2) + `
219
289
  `);
220
290
  filesWritten.push(filepath);
221
291
  }
222
- const manifestPath = join2(pluginDir, "manifest.json");
292
+ const manifestDir = join3(pluginDir, ".cursor-plugin");
293
+ ensureDir(manifestDir);
294
+ const manifestPath = join3(manifestDir, "plugin.json");
223
295
  writeFileSync2(manifestPath, JSON.stringify(manifest, null, 2) + `
224
296
  `);
225
297
  filesWritten.push(manifestPath);
226
298
  return { outputDir: pluginDir, filesWritten, manifest };
227
299
  }
300
+ function normalizeCursorPluginName(name) {
301
+ const normalized = name.toLowerCase().replace(/[^a-z0-9.-]+/g, "-").replace(/-+/g, "-").replace(/^[^a-z0-9]+/, "").replace(/[^a-z0-9]+$/, "");
302
+ return normalized.length > 0 ? normalized : "agentpacks-plugin";
303
+ }
304
+ function toCursorPluginAuthor(author) {
305
+ if (!author) {
306
+ return null;
307
+ }
308
+ if (typeof author === "string") {
309
+ return { name: author };
310
+ }
311
+ return {
312
+ name: author.name,
313
+ ...author.email ? { email: author.email } : {}
314
+ };
315
+ }
228
316
  export {
229
317
  exportCursorPlugin
230
318
  };
package/dist/index.js CHANGED
@@ -49,6 +49,13 @@ var PackManifestSchema = z.object({
49
49
  z.string(),
50
50
  z.object({ name: z.string(), email: z.string().optional() })
51
51
  ]).optional(),
52
+ homepage: z.string().optional(),
53
+ repository: z.union([
54
+ z.string(),
55
+ z.object({ url: z.string(), type: z.string().optional() })
56
+ ]).optional(),
57
+ license: z.string().optional(),
58
+ logo: z.string().optional(),
52
59
  tags: z.array(z.string()).default([]),
53
60
  dependencies: z.array(z.string()).default([]),
54
61
  conflicts: z.array(z.string()).default([]),
@@ -1790,6 +1797,28 @@ class CursorTarget extends BaseTarget {
1790
1797
  filesWritten.push(filepath);
1791
1798
  }
1792
1799
  }
1800
+ if (effective.includes("hooks")) {
1801
+ const hooksFilepath = resolve8(cursorDir, "hooks.json");
1802
+ if (deleteExisting) {
1803
+ removeIfExists(hooksFilepath);
1804
+ filesDeleted.push(hooksFilepath);
1805
+ }
1806
+ const mergedHooks = {};
1807
+ for (const hookSet of features.hooks) {
1808
+ const events = resolveHooksForTarget(hookSet, TARGET_ID2);
1809
+ for (const [event, entries] of Object.entries(events)) {
1810
+ if (!mergedHooks[event]) {
1811
+ mergedHooks[event] = [];
1812
+ }
1813
+ mergedHooks[event].push(...entries);
1814
+ }
1815
+ }
1816
+ if (Object.keys(mergedHooks).length > 0) {
1817
+ const hooksVersion = features.hooks.find((h) => h.version !== undefined)?.version ?? 1;
1818
+ writeGeneratedJson(hooksFilepath, { version: hooksVersion, hooks: mergedHooks }, { header: false });
1819
+ filesWritten.push(hooksFilepath);
1820
+ }
1821
+ }
1793
1822
  if (effective.includes("mcp")) {
1794
1823
  const mcpEntries = Object.entries(features.mcpServers);
1795
1824
  if (mcpEntries.length > 0) {
@@ -2657,23 +2686,41 @@ import { resolve as resolve15, join as join17 } from "path";
2657
2686
  import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
2658
2687
  function exportCursorPlugin(pack, outputDir) {
2659
2688
  const filesWritten = [];
2660
- const pluginDir = resolve15(outputDir, pack.manifest.name);
2689
+ const pluginName = normalizeCursorPluginName(pack.manifest.name);
2690
+ const pluginDir = resolve15(outputDir, pluginName);
2661
2691
  mkdirSync4(pluginDir, { recursive: true });
2662
2692
  const manifest = {
2663
- name: pack.manifest.name,
2664
- version: pack.manifest.version,
2665
- description: pack.manifest.description
2693
+ name: pluginName
2666
2694
  };
2667
- if (pack.manifest.author) {
2668
- manifest.author = typeof pack.manifest.author === "string" ? pack.manifest.author : pack.manifest.author.name;
2695
+ if (pack.manifest.version) {
2696
+ manifest.version = pack.manifest.version;
2697
+ }
2698
+ if (pack.manifest.description) {
2699
+ manifest.description = pack.manifest.description;
2700
+ }
2701
+ const author = toCursorPluginAuthor(pack.manifest.author);
2702
+ if (author) {
2703
+ manifest.author = author;
2704
+ }
2705
+ if (pack.manifest.homepage) {
2706
+ manifest.homepage = pack.manifest.homepage;
2707
+ }
2708
+ if (pack.manifest.repository) {
2709
+ manifest.repository = typeof pack.manifest.repository === "string" ? pack.manifest.repository : pack.manifest.repository.url;
2710
+ }
2711
+ if (pack.manifest.license) {
2712
+ manifest.license = pack.manifest.license;
2713
+ }
2714
+ if (pack.manifest.logo) {
2715
+ manifest.logo = pack.manifest.logo;
2669
2716
  }
2670
2717
  if (pack.manifest.tags.length > 0) {
2671
- manifest.tags = pack.manifest.tags;
2718
+ manifest.keywords = pack.manifest.tags;
2672
2719
  }
2673
2720
  if (pack.rules.length > 0) {
2674
2721
  const rulesDir = join17(pluginDir, "rules");
2675
2722
  ensureDir(rulesDir);
2676
- manifest.rules = [];
2723
+ manifest.rules = "rules";
2677
2724
  for (const rule of pack.rules) {
2678
2725
  const cursorMeta = rule.meta.cursor ?? {};
2679
2726
  const fm = {
@@ -2687,13 +2734,12 @@ function exportCursorPlugin(pack, outputDir) {
2687
2734
  const filepath = join17(rulesDir, filename);
2688
2735
  writeFileSync4(filepath, serializeFrontmatter(fm, rule.content));
2689
2736
  filesWritten.push(filepath);
2690
- manifest.rules.push(filename);
2691
2737
  }
2692
2738
  }
2693
2739
  if (pack.agents.length > 0) {
2694
2740
  const agentsDir = join17(pluginDir, "agents");
2695
2741
  ensureDir(agentsDir);
2696
- manifest.agents = [];
2742
+ manifest.agents = "agents";
2697
2743
  for (const agent of pack.agents) {
2698
2744
  const fm = {
2699
2745
  name: agent.name,
@@ -2703,13 +2749,12 @@ function exportCursorPlugin(pack, outputDir) {
2703
2749
  const filepath = join17(agentsDir, filename);
2704
2750
  writeFileSync4(filepath, serializeFrontmatter(fm, agent.content));
2705
2751
  filesWritten.push(filepath);
2706
- manifest.agents.push(filename);
2707
2752
  }
2708
2753
  }
2709
2754
  if (pack.skills.length > 0) {
2710
2755
  const skillsDir = join17(pluginDir, "skills");
2711
2756
  ensureDir(skillsDir);
2712
- manifest.skills = [];
2757
+ manifest.skills = "skills";
2713
2758
  for (const skill of pack.skills) {
2714
2759
  const skillSubDir = join17(skillsDir, skill.name);
2715
2760
  ensureDir(skillSubDir);
@@ -2720,34 +2765,68 @@ function exportCursorPlugin(pack, outputDir) {
2720
2765
  const filepath = join17(skillSubDir, "SKILL.md");
2721
2766
  writeFileSync4(filepath, serializeFrontmatter(fm, skill.content));
2722
2767
  filesWritten.push(filepath);
2723
- manifest.skills.push(skill.name);
2724
2768
  }
2725
2769
  }
2726
2770
  if (pack.commands.length > 0) {
2727
2771
  const commandsDir = join17(pluginDir, "commands");
2728
2772
  ensureDir(commandsDir);
2729
- manifest.commands = [];
2773
+ manifest.commands = "commands";
2730
2774
  for (const cmd of pack.commands) {
2775
+ const fm = {
2776
+ name: cmd.name
2777
+ };
2778
+ if (cmd.meta.description) {
2779
+ fm.description = cmd.meta.description;
2780
+ }
2731
2781
  const filename = `${cmd.name}.md`;
2732
2782
  const filepath = join17(commandsDir, filename);
2733
- writeFileSync4(filepath, cmd.content);
2783
+ writeFileSync4(filepath, serializeFrontmatter(fm, cmd.content));
2784
+ filesWritten.push(filepath);
2785
+ }
2786
+ }
2787
+ if (pack.hooks) {
2788
+ const events = resolveHooksForTarget(pack.hooks, "cursor");
2789
+ if (Object.keys(events).length > 0) {
2790
+ const hooksDir = join17(pluginDir, "hooks");
2791
+ ensureDir(hooksDir);
2792
+ const filepath = join17(hooksDir, "hooks.json");
2793
+ writeFileSync4(filepath, JSON.stringify({ version: pack.hooks.version ?? 1, hooks: events }, null, 2) + `
2794
+ `);
2734
2795
  filesWritten.push(filepath);
2735
- manifest.commands.push(filename);
2796
+ manifest.hooks = "hooks/hooks.json";
2736
2797
  }
2737
2798
  }
2738
2799
  if (pack.mcp && Object.keys(pack.mcp.servers).length > 0) {
2739
- manifest.mcp = true;
2740
- const filepath = join17(pluginDir, "mcp.json");
2800
+ manifest.mcpServers = ".mcp.json";
2801
+ const filepath = join17(pluginDir, ".mcp.json");
2741
2802
  writeFileSync4(filepath, JSON.stringify({ mcpServers: pack.mcp.servers }, null, 2) + `
2742
2803
  `);
2743
2804
  filesWritten.push(filepath);
2744
2805
  }
2745
- const manifestPath = join17(pluginDir, "manifest.json");
2806
+ const manifestDir = join17(pluginDir, ".cursor-plugin");
2807
+ ensureDir(manifestDir);
2808
+ const manifestPath = join17(manifestDir, "plugin.json");
2746
2809
  writeFileSync4(manifestPath, JSON.stringify(manifest, null, 2) + `
2747
2810
  `);
2748
2811
  filesWritten.push(manifestPath);
2749
2812
  return { outputDir: pluginDir, filesWritten, manifest };
2750
2813
  }
2814
+ function normalizeCursorPluginName(name) {
2815
+ const normalized = name.toLowerCase().replace(/[^a-z0-9.-]+/g, "-").replace(/-+/g, "-").replace(/^[^a-z0-9]+/, "").replace(/[^a-z0-9]+$/, "");
2816
+ return normalized.length > 0 ? normalized : "agentpacks-plugin";
2817
+ }
2818
+ function toCursorPluginAuthor(author) {
2819
+ if (!author) {
2820
+ return null;
2821
+ }
2822
+ if (typeof author === "string") {
2823
+ return { name: author };
2824
+ }
2825
+ return {
2826
+ name: author.name,
2827
+ ...author.email ? { email: author.email } : {}
2828
+ };
2829
+ }
2751
2830
 
2752
2831
  // src/importers/rulesync.ts
2753
2832
  import { existsSync as existsSync10, readFileSync as readFileSync10, copyFileSync, writeFileSync as writeFileSync5 } from "fs";