agentpacks 0.9.0 → 1.0.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 (67) hide show
  1. package/README.md +41 -14
  2. package/dist/api.js +403 -179
  3. package/dist/cli/export-cmd.js +58 -5
  4. package/dist/cli/generate.js +268 -59
  5. package/dist/cli/import-cmd.js +203 -95
  6. package/dist/cli/install.js +1 -0
  7. package/dist/cli/models-explain.js +56 -0
  8. package/dist/cli/pack/list.js +56 -0
  9. package/dist/cli/pack/validate.js +143 -18
  10. package/dist/cli/publish.js +1 -0
  11. package/dist/core/config.d.ts +1 -1
  12. package/dist/core/config.js +1 -0
  13. package/dist/core/index.js +56 -0
  14. package/dist/core/metarepo.js +1 -0
  15. package/dist/core/pack-loader.js +56 -0
  16. package/dist/exporters/cursor-plugin.js +109 -22
  17. package/dist/exporters/index.js +109 -22
  18. package/dist/features/index.d.ts +1 -1
  19. package/dist/features/index.js +59 -0
  20. package/dist/features/skills.d.ts +22 -0
  21. package/dist/features/skills.js +60 -1
  22. package/dist/importers/cursor.js +122 -26
  23. package/dist/importers/opencode.js +138 -24
  24. package/dist/importers/rulesync.js +147 -33
  25. package/dist/index.js +484 -244
  26. package/dist/node/api.js +403 -179
  27. package/dist/node/cli/export-cmd.js +58 -5
  28. package/dist/node/cli/generate.js +268 -59
  29. package/dist/node/cli/import-cmd.js +203 -95
  30. package/dist/node/cli/install.js +1 -0
  31. package/dist/node/cli/models-explain.js +56 -0
  32. package/dist/node/cli/pack/list.js +56 -0
  33. package/dist/node/cli/pack/validate.js +143 -18
  34. package/dist/node/cli/publish.js +1 -0
  35. package/dist/node/core/config.js +1 -0
  36. package/dist/node/core/index.js +56 -0
  37. package/dist/node/core/metarepo.js +1 -0
  38. package/dist/node/core/pack-loader.js +56 -0
  39. package/dist/node/exporters/cursor-plugin.js +109 -22
  40. package/dist/node/exporters/index.js +109 -22
  41. package/dist/node/features/index.js +59 -0
  42. package/dist/node/features/skills.js +60 -1
  43. package/dist/node/importers/cursor.js +122 -26
  44. package/dist/node/importers/opencode.js +138 -24
  45. package/dist/node/importers/rulesync.js +147 -33
  46. package/dist/node/index.js +484 -244
  47. package/dist/node/targets/claude-code.js +56 -1
  48. package/dist/node/targets/codex-cli.js +56 -1
  49. package/dist/node/targets/copilot.js +56 -1
  50. package/dist/node/targets/cursor.js +56 -5
  51. package/dist/node/targets/index.js +268 -59
  52. package/dist/node/targets/mistral-vibe.js +661 -0
  53. package/dist/node/targets/opencode.js +56 -1
  54. package/dist/node/targets/registry.js +267 -59
  55. package/dist/node/utils/model-allowlist.js +6 -2
  56. package/dist/targets/claude-code.js +56 -1
  57. package/dist/targets/codex-cli.js +56 -1
  58. package/dist/targets/copilot.js +56 -1
  59. package/dist/targets/cursor.js +56 -5
  60. package/dist/targets/index.d.ts +1 -0
  61. package/dist/targets/index.js +268 -59
  62. package/dist/targets/mistral-vibe.d.ts +13 -0
  63. package/dist/targets/mistral-vibe.js +661 -0
  64. package/dist/targets/opencode.js +56 -1
  65. package/dist/targets/registry.js +267 -59
  66. package/dist/utils/model-allowlist.js +6 -2
  67. package/package.json +15 -3
@@ -111,15 +111,123 @@ function getHeader(type) {
111
111
  }
112
112
  }
113
113
 
114
+ // src/utils/frontmatter.ts
115
+ import matter from "gray-matter";
116
+ function parseFrontmatter(source) {
117
+ const { data, content } = matter(source);
118
+ return {
119
+ data,
120
+ content: content.trim(),
121
+ raw: source
122
+ };
123
+ }
124
+ function serializeFrontmatter(data, content) {
125
+ const filtered = Object.fromEntries(Object.entries(data).filter(([, v]) => v !== undefined));
126
+ if (Object.keys(filtered).length === 0) {
127
+ return content;
128
+ }
129
+ return matter.stringify(content, filtered);
130
+ }
131
+
132
+ // src/features/skills.ts
133
+ import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
134
+ import { basename, join as join2 } from "path";
135
+ var SKILL_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
136
+ var SKILL_NAME_MAX_LENGTH = 64;
137
+ function parseSkills(skillsDir, packName) {
138
+ const dirs = listDirs(skillsDir);
139
+ const skills = [];
140
+ for (const dir of dirs) {
141
+ const skillMd = join2(dir, "SKILL.md");
142
+ if (existsSync2(skillMd)) {
143
+ skills.push(parseSkillFile(skillMd, dir, packName));
144
+ }
145
+ }
146
+ return skills;
147
+ }
148
+ function parseSkillFile(filepath, skillDir, packName) {
149
+ const raw = readFileSync2(filepath, "utf-8");
150
+ const { data, content } = parseFrontmatter(raw);
151
+ return {
152
+ name: data.name ?? basename(skillDir),
153
+ sourcePath: filepath,
154
+ sourceDir: skillDir,
155
+ packName,
156
+ meta: data,
157
+ content
158
+ };
159
+ }
160
+ function buildSkillFrontmatter(skill) {
161
+ return {
162
+ ...skill.meta,
163
+ name: skill.name
164
+ };
165
+ }
166
+ function serializeSkill(skill) {
167
+ return serializeFrontmatter(buildSkillFrontmatter(skill), skill.content);
168
+ }
169
+ function normalizeImportedSkillMarkdown(source, skillName) {
170
+ const { data, content } = parseFrontmatter(source);
171
+ const normalized = {
172
+ ...data,
173
+ name: skillName
174
+ };
175
+ let addedDescription = false;
176
+ const description = normalized.description;
177
+ if (typeof description !== "string" || description.trim().length === 0) {
178
+ normalized.description = `Imported skill: ${skillName}`;
179
+ addedDescription = true;
180
+ }
181
+ return {
182
+ content: serializeFrontmatter(normalized, content),
183
+ addedDescription
184
+ };
185
+ }
186
+ function validateAgentSkillsFrontmatter(skill) {
187
+ const errors = [];
188
+ const dirName = basename(skill.sourceDir);
189
+ const declaredName = skill.meta.name;
190
+ if (typeof declaredName !== "string" || declaredName.trim().length === 0) {
191
+ errors.push('Missing required frontmatter field "name".');
192
+ } else {
193
+ if (declaredName.length > SKILL_NAME_MAX_LENGTH) {
194
+ errors.push(`Invalid "name": must be at most ${SKILL_NAME_MAX_LENGTH} characters.`);
195
+ }
196
+ if (!SKILL_NAME_PATTERN.test(declaredName)) {
197
+ errors.push('Invalid "name": use lowercase letters, numbers, and single hyphens only.');
198
+ }
199
+ if (declaredName !== dirName) {
200
+ errors.push(`Invalid "name": must match containing directory "${dirName}".`);
201
+ }
202
+ }
203
+ const description = skill.meta.description;
204
+ if (typeof description !== "string" || description.trim().length === 0) {
205
+ errors.push('Missing required frontmatter field "description".');
206
+ }
207
+ const allowedTools = skill.meta["allowed-tools"];
208
+ if (allowedTools !== undefined && (!Array.isArray(allowedTools) || allowedTools.some((tool) => typeof tool !== "string" || tool.length === 0))) {
209
+ errors.push('Invalid "allowed-tools": expected an array of non-empty strings.');
210
+ }
211
+ return errors;
212
+ }
213
+ function skillMatchesTarget(skill, targetId) {
214
+ const { targets } = skill.meta;
215
+ if (!targets || targets === "*")
216
+ return true;
217
+ if (Array.isArray(targets) && targets.includes("*"))
218
+ return true;
219
+ return Array.isArray(targets) && targets.includes(targetId);
220
+ }
221
+
114
222
  // src/importers/rulesync.ts
115
- import { existsSync as existsSync2, readFileSync as readFileSync2, copyFileSync, writeFileSync as writeFileSync2 } from "fs";
116
- import { resolve, join as join2, basename } from "path";
223
+ import { existsSync as existsSync3, readFileSync as readFileSync3, copyFileSync, writeFileSync as writeFileSync2 } from "fs";
224
+ import { resolve, join as join3, basename as basename2 } from "path";
117
225
  import { parse as parseJsonc } from "jsonc-parser";
118
226
  function importFromRulesync(projectRoot, outputPackDir) {
119
227
  const rulesyncDir = resolve(projectRoot, ".rulesync");
120
228
  const warnings = [];
121
229
  const filesImported = [];
122
- if (!existsSync2(rulesyncDir)) {
230
+ if (!existsSync3(rulesyncDir)) {
123
231
  return {
124
232
  packDir: "",
125
233
  filesImported: [],
@@ -130,76 +238,82 @@ function importFromRulesync(projectRoot, outputPackDir) {
130
238
  const packDir = outputPackDir ?? resolve(projectRoot, "packs", "default");
131
239
  ensureDir(packDir);
132
240
  const rulesDir = resolve(rulesyncDir, "rules");
133
- if (existsSync2(rulesDir)) {
241
+ if (existsSync3(rulesDir)) {
134
242
  const outRulesDir = resolve(packDir, "rules");
135
243
  ensureDir(outRulesDir);
136
244
  const files = listFiles(rulesDir, { extension: ".md" });
137
245
  for (const file of files) {
138
- const dest = join2(outRulesDir, basename(file));
246
+ const dest = join3(outRulesDir, basename2(file));
139
247
  copyFileSync(file, dest);
140
248
  filesImported.push(dest);
141
249
  }
142
250
  }
143
251
  const commandsDir = resolve(rulesyncDir, "commands");
144
- if (existsSync2(commandsDir)) {
252
+ if (existsSync3(commandsDir)) {
145
253
  const outCommandsDir = resolve(packDir, "commands");
146
254
  ensureDir(outCommandsDir);
147
255
  const files = listFiles(commandsDir, { extension: ".md" });
148
256
  for (const file of files) {
149
- const dest = join2(outCommandsDir, basename(file));
257
+ const dest = join3(outCommandsDir, basename2(file));
150
258
  copyFileSync(file, dest);
151
259
  filesImported.push(dest);
152
260
  }
153
261
  }
154
262
  const subagentsDir = resolve(rulesyncDir, "subagents");
155
- if (existsSync2(subagentsDir)) {
263
+ if (existsSync3(subagentsDir)) {
156
264
  const outAgentsDir = resolve(packDir, "agents");
157
265
  ensureDir(outAgentsDir);
158
266
  const files = listFiles(subagentsDir, { extension: ".md" });
159
267
  for (const file of files) {
160
- const dest = join2(outAgentsDir, basename(file));
268
+ const dest = join3(outAgentsDir, basename2(file));
161
269
  copyFileSync(file, dest);
162
270
  filesImported.push(dest);
163
271
  }
164
272
  }
165
273
  const skillsDir = resolve(rulesyncDir, "skills");
166
- if (existsSync2(skillsDir)) {
274
+ if (existsSync3(skillsDir)) {
167
275
  const outSkillsDir = resolve(packDir, "skills");
168
276
  ensureDir(outSkillsDir);
169
277
  const skillDirs = listDirs(skillsDir);
170
278
  for (const skillDir of skillDirs) {
171
- const skillName = basename(skillDir);
279
+ const skillName = basename2(skillDir);
172
280
  if (skillName.startsWith("."))
173
281
  continue;
174
- const skillMd = join2(skillDir, "SKILL.md");
175
- if (existsSync2(skillMd)) {
176
- const outSkillDir = join2(outSkillsDir, skillName);
282
+ const skillMd = join3(skillDir, "SKILL.md");
283
+ if (existsSync3(skillMd)) {
284
+ const outSkillDir = join3(outSkillsDir, skillName);
177
285
  ensureDir(outSkillDir);
178
- copyFileSync(skillMd, join2(outSkillDir, "SKILL.md"));
179
- filesImported.push(join2(outSkillDir, "SKILL.md"));
286
+ const rawSkill = readFileSync3(skillMd, "utf-8");
287
+ const normalized = normalizeImportedSkillMarkdown(rawSkill, skillName);
288
+ const dest = join3(outSkillDir, "SKILL.md");
289
+ writeFileSync2(dest, normalized.content);
290
+ filesImported.push(dest);
291
+ if (normalized.addedDescription) {
292
+ warnings.push(`skills/${skillName}/SKILL.md missing description; added import placeholder.`);
293
+ }
180
294
  }
181
295
  }
182
296
  }
183
297
  const hooksJson = resolve(rulesyncDir, "hooks.json");
184
- if (existsSync2(hooksJson)) {
298
+ if (existsSync3(hooksJson)) {
185
299
  const outHooksDir = resolve(packDir, "hooks");
186
300
  ensureDir(outHooksDir);
187
- copyFileSync(hooksJson, join2(outHooksDir, "hooks.json"));
188
- filesImported.push(join2(outHooksDir, "hooks.json"));
301
+ copyFileSync(hooksJson, join3(outHooksDir, "hooks.json"));
302
+ filesImported.push(join3(outHooksDir, "hooks.json"));
189
303
  }
190
304
  const mcpJson = resolve(rulesyncDir, "mcp.json");
191
- if (existsSync2(mcpJson)) {
192
- copyFileSync(mcpJson, join2(packDir, "mcp.json"));
193
- filesImported.push(join2(packDir, "mcp.json"));
305
+ if (existsSync3(mcpJson)) {
306
+ copyFileSync(mcpJson, join3(packDir, "mcp.json"));
307
+ filesImported.push(join3(packDir, "mcp.json"));
194
308
  }
195
309
  const aiIgnore = resolve(rulesyncDir, ".aiignore");
196
310
  const rulesyncIgnore = resolve(projectRoot, ".rulesyncignore");
197
- if (existsSync2(aiIgnore)) {
198
- copyFileSync(aiIgnore, join2(packDir, "ignore"));
199
- filesImported.push(join2(packDir, "ignore"));
200
- } else if (existsSync2(rulesyncIgnore)) {
201
- copyFileSync(rulesyncIgnore, join2(packDir, "ignore"));
202
- filesImported.push(join2(packDir, "ignore"));
311
+ if (existsSync3(aiIgnore)) {
312
+ copyFileSync(aiIgnore, join3(packDir, "ignore"));
313
+ filesImported.push(join3(packDir, "ignore"));
314
+ } else if (existsSync3(rulesyncIgnore)) {
315
+ copyFileSync(rulesyncIgnore, join3(packDir, "ignore"));
316
+ filesImported.push(join3(packDir, "ignore"));
203
317
  }
204
318
  const packJson = {
205
319
  name: "default",
@@ -211,12 +325,12 @@ function importFromRulesync(projectRoot, outputPackDir) {
211
325
  targets: "*",
212
326
  features: "*"
213
327
  };
214
- writeFileSync2(join2(packDir, "pack.json"), JSON.stringify(packJson, null, 2) + `
328
+ writeFileSync2(join3(packDir, "pack.json"), JSON.stringify(packJson, null, 2) + `
215
329
  `);
216
- filesImported.push(join2(packDir, "pack.json"));
330
+ filesImported.push(join3(packDir, "pack.json"));
217
331
  let configGenerated = false;
218
332
  const rulesyncConfig = resolve(projectRoot, "rulesync.jsonc");
219
- if (existsSync2(rulesyncConfig)) {
333
+ if (existsSync3(rulesyncConfig)) {
220
334
  const agentpacksConfig = convertRulesyncConfig(rulesyncConfig, packDir);
221
335
  const configPath = resolve(projectRoot, "agentpacks.jsonc");
222
336
  writeFileSync2(configPath, agentpacksConfig);
@@ -225,14 +339,14 @@ function importFromRulesync(projectRoot, outputPackDir) {
225
339
  return { packDir, filesImported, warnings, configGenerated };
226
340
  }
227
341
  function convertRulesyncConfig(rulesyncPath, _packDir) {
228
- const raw = readFileSync2(rulesyncPath, "utf-8");
342
+ const raw = readFileSync3(rulesyncPath, "utf-8");
229
343
  const parsed = parseJsonc(raw);
230
344
  const targets = parsed.targets ?? ["opencode", "cursor", "claudecode"];
231
345
  const features = parsed.features ?? ["*"];
232
346
  const baseDirs = parsed.baseDirs ?? ["."];
233
347
  const global = parsed.global ?? false;
234
348
  const deleteVal = parsed.delete ?? true;
235
- const relPackDir = "./" + join2("packs", "default");
349
+ const relPackDir = "./" + join3("packs", "default");
236
350
  const config = {
237
351
  $schema: "https://unpkg.com/agentpacks/schema.json",
238
352
  packs: [relPackDir],