@agentrules/core 0.1.0 → 0.2.1

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 (4) hide show
  1. package/README.md +80 -54
  2. package/dist/index.d.ts +613 -786
  3. package/dist/index.js +318 -314
  4. package/package.json +15 -10
package/dist/index.js CHANGED
@@ -11,172 +11,189 @@ const PLATFORM_ID_TUPLE = [
11
11
  "claude",
12
12
  "cursor"
13
13
  ];
14
+ /** Tuple of all rule types for schema validation */
15
+ const RULE_TYPE_TUPLE = [
16
+ "instruction",
17
+ "rule",
18
+ "command",
19
+ "skill",
20
+ "agent",
21
+ "tool"
22
+ ];
14
23
 
15
24
  //#endregion
16
25
  //#region src/platform/config.ts
17
26
  const PLATFORM_IDS = PLATFORM_ID_TUPLE;
18
27
  /**
19
- * Platform configuration including supported rule types and install paths.
28
+ * Platform configuration including supported types and install paths.
20
29
  */
21
30
  const PLATFORMS = {
22
31
  opencode: {
23
32
  label: "OpenCode",
24
- projectDir: ".opencode",
33
+ platformDir: ".opencode",
25
34
  globalDir: "~/.config/opencode",
26
35
  types: {
27
36
  instruction: {
28
37
  description: "Project instructions",
29
- format: "markdown",
30
- extension: "md",
31
- projectPath: "AGENTS.md",
32
- globalPath: "~/.config/opencode/AGENTS.md"
38
+ project: "AGENTS.md",
39
+ global: "{platformDir}/AGENTS.md"
33
40
  },
34
41
  agent: {
35
42
  description: "Specialized AI agent",
36
- format: "markdown",
37
- extension: "md",
38
- projectPath: ".opencode/agent/{name}.md",
39
- globalPath: "~/.config/opencode/agent/{name}.md"
43
+ project: "{platformDir}/agent/{name}.md",
44
+ global: "{platformDir}/agent/{name}.md"
40
45
  },
41
46
  command: {
42
47
  description: "Custom slash command",
43
- format: "markdown",
44
- extension: "md",
45
- projectPath: ".opencode/command/{name}.md",
46
- globalPath: "~/.config/opencode/command/{name}.md"
48
+ project: "{platformDir}/command/{name}.md",
49
+ global: "{platformDir}/command/{name}.md"
47
50
  },
48
51
  tool: {
49
52
  description: "Custom tool",
50
- format: "typescript",
51
- extension: "ts",
52
- projectPath: ".opencode/tool/{name}.ts",
53
- globalPath: "~/.config/opencode/tool/{name}.ts"
53
+ project: "{platformDir}/tool/{name}.ts",
54
+ global: "{platformDir}/tool/{name}.ts"
54
55
  }
55
56
  }
56
57
  },
57
58
  claude: {
58
59
  label: "Claude Code",
59
- projectDir: ".claude",
60
+ platformDir: ".claude",
60
61
  globalDir: "~/.claude",
61
62
  types: {
62
63
  instruction: {
63
64
  description: "Project instructions",
64
- format: "markdown",
65
- extension: "md",
66
- projectPath: "CLAUDE.md",
67
- globalPath: "~/.claude/CLAUDE.md"
65
+ project: "CLAUDE.md",
66
+ global: "{platformDir}/CLAUDE.md"
67
+ },
68
+ rule: {
69
+ description: "Project rule",
70
+ project: "{platformDir}/rules/{name}.md",
71
+ global: "{platformDir}/rules/{name}.md"
68
72
  },
69
73
  command: {
70
74
  description: "Custom slash command",
71
- format: "markdown",
72
- extension: "md",
73
- projectPath: ".claude/commands/{name}.md",
74
- globalPath: "~/.claude/commands/{name}.md"
75
+ project: "{platformDir}/commands/{name}.md",
76
+ global: "{platformDir}/commands/{name}.md"
75
77
  },
76
78
  skill: {
77
79
  description: "Custom skill",
78
- format: "markdown",
79
- extension: "md",
80
- projectPath: ".claude/skills/{name}/SKILL.md",
81
- globalPath: "~/.claude/skills/{name}/SKILL.md"
80
+ project: "{platformDir}/skills/{name}/SKILL.md",
81
+ global: "{platformDir}/skills/{name}/SKILL.md"
82
82
  }
83
83
  }
84
84
  },
85
85
  cursor: {
86
86
  label: "Cursor",
87
- projectDir: ".cursor",
87
+ platformDir: ".cursor",
88
88
  globalDir: "~/.cursor",
89
89
  types: {
90
90
  instruction: {
91
91
  description: "Project instructions",
92
- format: "markdown",
93
- extension: "md",
94
- projectPath: "AGENTS.md",
95
- globalPath: null
92
+ project: "AGENTS.md",
93
+ global: null
96
94
  },
97
95
  rule: {
98
96
  description: "Custom rule",
99
- format: "mdc",
100
- extension: "mdc",
101
- projectPath: ".cursor/rules/{name}.mdc",
102
- globalPath: null
97
+ project: "{platformDir}/rules/{name}.mdc",
98
+ global: null
103
99
  },
104
100
  command: {
105
101
  description: "Custom slash command",
106
- format: "markdown",
107
- extension: "md",
108
- projectPath: ".cursor/commands/{name}.md",
109
- globalPath: "~/.cursor/commands/{name}.md"
102
+ project: "{platformDir}/commands/{name}.md",
103
+ global: "{platformDir}/commands/{name}.md"
110
104
  }
111
105
  }
112
106
  },
113
107
  codex: {
114
108
  label: "Codex",
115
- projectDir: ".codex",
109
+ platformDir: ".codex",
116
110
  globalDir: "~/.codex",
117
111
  types: {
118
112
  instruction: {
119
113
  description: "Project instructions",
120
- format: "markdown",
121
- extension: "md",
122
- projectPath: "AGENTS.md",
123
- globalPath: "~/.codex/AGENTS.md"
114
+ project: "AGENTS.md",
115
+ global: "{platformDir}/AGENTS.md"
124
116
  },
125
117
  command: {
126
118
  description: "Custom prompt",
127
- format: "markdown",
128
- extension: "md",
129
- projectPath: null,
130
- globalPath: "~/.codex/prompts/{name}.md"
119
+ project: null,
120
+ global: "{platformDir}/prompts/{name}.md"
131
121
  }
132
122
  }
133
123
  }
134
124
  };
135
- /** Valid rule types for each platform. Must be kept in sync with PLATFORMS. */
125
+ /** Get valid types for a specific platform */
126
+ function getValidTypes(platform) {
127
+ return Object.keys(PLATFORMS[platform].types);
128
+ }
129
+ /** Check if a type is valid for a given platform */
130
+ function isValidType(platform, type) {
131
+ return type in PLATFORMS[platform].types;
132
+ }
133
+ /** Get the configuration for a specific platform + type combination */
134
+ function getTypeConfig(platform, type) {
135
+ const platformConfig = PLATFORMS[platform];
136
+ return platformConfig.types[type];
137
+ }
138
+ function supportsInstallPath({ platform, type, scope = "project" }) {
139
+ const typeConfig = getTypeConfig(platform, type);
140
+ if (!typeConfig) return false;
141
+ const template = scope === "project" ? typeConfig.project : typeConfig.global;
142
+ return template !== null;
143
+ }
144
+ function getInstallPath({ platform, type, name, scope = "project" }) {
145
+ const platformConfig = PLATFORMS[platform];
146
+ const typeConfig = getTypeConfig(platform, type);
147
+ if (!typeConfig) return null;
148
+ const template = scope === "project" ? typeConfig.project : typeConfig.global;
149
+ if (!template) return null;
150
+ const rootDir = scope === "project" ? platformConfig.platformDir : platformConfig.globalDir;
151
+ if (template.includes("{name}") && !name) throw new Error(`Missing name for install path: platform="${platform}" type="${type}" scope="${scope}"`);
152
+ return template.replace("{platformDir}", rootDir).replace("{name}", name ?? "");
153
+ }
154
+ /** Get platform configuration */
155
+ function getPlatformConfig(platform) {
156
+ return PLATFORMS[platform];
157
+ }
158
+ /**
159
+ * Get the relative install path for a type (without the root directory prefix).
160
+ * Returns the path relative to the platform's root directory.
161
+ *
162
+ * Example: For codex instruction with scope="global", returns "AGENTS.md"
163
+ * (not "~/.codex/AGENTS.md")
164
+ */
165
+ function getRelativeInstallPath({ platform, type, name, scope = "project" }) {
166
+ const typeConfig = getTypeConfig(platform, type);
167
+ if (!typeConfig) return null;
168
+ const template = scope === "project" ? typeConfig.project : typeConfig.global;
169
+ if (!template) return null;
170
+ if (template.includes("{name}") && !name) throw new Error(`Missing name for install path: platform="${platform}" type="${type}" scope="${scope}"`);
171
+ return template.replace("{platformDir}/", "").replace("{platformDir}", "").replace("{name}", name ?? "");
172
+ }
173
+ /**
174
+ * Platform-specific type tuples for zod schema validation.
175
+ * Must be kept in sync with PLATFORMS types above.
176
+ */
136
177
  const PLATFORM_RULE_TYPES = {
137
178
  opencode: [
138
179
  "instruction",
139
- "command",
140
180
  "agent",
181
+ "command",
141
182
  "tool"
142
183
  ],
143
184
  claude: [
144
185
  "instruction",
186
+ "rule",
145
187
  "command",
146
188
  "skill"
147
189
  ],
148
- codex: ["instruction", "command"],
149
190
  cursor: [
150
191
  "instruction",
151
- "command",
152
- "rule"
153
- ]
192
+ "rule",
193
+ "command"
194
+ ],
195
+ codex: ["instruction", "command"]
154
196
  };
155
- /** Get valid rule types for a specific platform */
156
- function getValidRuleTypes(platform) {
157
- return PLATFORM_RULE_TYPES[platform];
158
- }
159
- /** Check if a type is valid for a given platform */
160
- function isValidRuleType(platform, type) {
161
- return PLATFORM_RULE_TYPES[platform].includes(type);
162
- }
163
- /** Get the configuration for a specific platform + type combination */
164
- function getRuleTypeConfig(platform, type) {
165
- const platformConfig = PLATFORMS[platform];
166
- return platformConfig.types[type];
167
- }
168
- /** Get the install path for a rule, replacing {name} placeholder */
169
- function getInstallPath(platform, type, name, location = "project") {
170
- const config = getRuleTypeConfig(platform, type);
171
- if (!config) return null;
172
- const pathTemplate = location === "project" ? config.projectPath : config.globalPath;
173
- if (!pathTemplate) return null;
174
- return pathTemplate.replace("{name}", name);
175
- }
176
- /** Get platform configuration */
177
- function getPlatformConfig(platform) {
178
- return PLATFORMS[platform];
179
- }
180
197
 
181
198
  //#endregion
182
199
  //#region src/platform/utils.ts
@@ -189,19 +206,89 @@ function normalizePlatformInput(value) {
189
206
  throw new Error(`Unknown platform "${value}". Supported platforms: ${PLATFORM_IDS.join(", ")}.`);
190
207
  }
191
208
  /**
192
- * Check if a directory name matches a platform's projectDir.
193
- * Used to detect if a preset config is inside a platform directory (in-project mode).
209
+ * Check if a directory name matches a platform's platformDir.
210
+ * Used to detect if a rule config is inside a platform directory (in-project mode).
194
211
  */
195
212
  function isPlatformDir(dirName) {
196
- return PLATFORM_IDS.some((id) => PLATFORMS[id].projectDir === dirName);
213
+ return PLATFORM_IDS.some((id) => PLATFORMS[id].platformDir === dirName);
197
214
  }
198
215
  /**
199
- * Get the platform ID from a directory name, if it matches a platform's projectDir.
216
+ * Get the platform ID from a directory name, if it matches a platform's platformDir.
200
217
  */
201
218
  function getPlatformFromDir(dirName) {
202
- for (const id of PLATFORM_IDS) if (PLATFORMS[id].projectDir === dirName) return id;
219
+ for (const id of PLATFORM_IDS) if (PLATFORMS[id].platformDir === dirName) return id;
203
220
  return;
204
221
  }
222
+ function normalizePathForInference(value) {
223
+ return value.replace(/\\/g, "/");
224
+ }
225
+ function getBasename(value) {
226
+ const normalized = normalizePathForInference(value);
227
+ const segments = normalized.split("/").filter(Boolean);
228
+ return segments.at(-1) ?? "";
229
+ }
230
+ /**
231
+ * Infer the platform from a file path by searching for platformDir segments.
232
+ *
233
+ * Example: "/repo/.claude/commands/foo.md" -> "claude"
234
+ */
235
+ function inferPlatformFromPath(value) {
236
+ const normalized = normalizePathForInference(value);
237
+ const segments = normalized.split("/").filter(Boolean);
238
+ const matches = PLATFORM_IDS.filter((id) => segments.includes(PLATFORMS[id].platformDir));
239
+ if (matches.length === 1) return matches[0];
240
+ return;
241
+ }
242
+ /**
243
+ * Return all platforms whose instruction file matches this basename.
244
+ *
245
+ * Example: "CLAUDE.md" -> ["claude"], "AGENTS.md" -> ["opencode", "cursor", "codex"]
246
+ */
247
+ function inferInstructionPlatformsFromFileName(fileName) {
248
+ const matches = [];
249
+ for (const id of PLATFORM_IDS) {
250
+ const instructionPath = getInstallPath({
251
+ platform: id,
252
+ type: "instruction",
253
+ scope: "project"
254
+ });
255
+ if (instructionPath === fileName) matches.push(id);
256
+ }
257
+ return matches;
258
+ }
259
+ function getProjectTypeDirMap(platform) {
260
+ const map = new Map();
261
+ for (const [type, cfg] of Object.entries(PLATFORMS[platform].types)) {
262
+ const template = cfg.project;
263
+ if (!template) continue;
264
+ if (!template.startsWith("{platformDir}/")) continue;
265
+ const rest = template.slice(14);
266
+ const dir = rest.split("/")[0];
267
+ if (!dir || dir.includes("{")) continue;
268
+ map.set(dir, type);
269
+ }
270
+ return map;
271
+ }
272
+ /**
273
+ * Infer a rule type from a file path for a known platform.
274
+ * Uses PLATFORMS templates as source-of-truth.
275
+ */
276
+ function inferTypeFromPath(platform, filePath) {
277
+ const base = getBasename(filePath);
278
+ const instructionPath = getInstallPath({
279
+ platform,
280
+ type: "instruction",
281
+ scope: "project"
282
+ });
283
+ if (instructionPath === base) return "instruction";
284
+ const normalized = normalizePathForInference(filePath);
285
+ const segments = normalized.split("/").filter(Boolean);
286
+ const platformDirIndex = segments.lastIndexOf(PLATFORMS[platform].platformDir);
287
+ if (platformDirIndex < 0) return;
288
+ const nextDir = segments[platformDirIndex + 1];
289
+ if (!nextDir) return;
290
+ return getProjectTypeDirMap(platform).get(nextDir);
291
+ }
205
292
 
206
293
  //#endregion
207
294
  //#region src/utils/encoding.ts
@@ -215,7 +302,10 @@ function encodeUtf8(value) {
215
302
  function decodeUtf8(payload) {
216
303
  const bytes = toUint8Array(payload);
217
304
  if (typeof Buffer !== "undefined") return Buffer.from(bytes).toString("utf8");
218
- return new TextDecoder("utf-8", { fatal: false }).decode(bytes);
305
+ return new TextDecoder("utf-8", {
306
+ fatal: false,
307
+ ignoreBOM: false
308
+ }).decode(bytes);
219
309
  }
220
310
  function toUint8Array(payload) {
221
311
  if (payload instanceof Uint8Array) return payload;
@@ -237,13 +327,13 @@ const TAG_ERROR = "Must be lowercase alphanumeric with hyphens (e.g., my-tag)";
237
327
  const tagSchema = z.string().trim().min(1, "Tag cannot be empty").max(35, "Tag must be 35 characters or less").regex(TAG_REGEX, TAG_ERROR).refine((tag) => !PLATFORM_ID_SET.has(tag), { message: "Platform names cannot be used as tags (redundant with platform field)" });
238
328
  /**
239
329
  * Schema for tags array.
240
- * - 1-10 tags required
330
+ * - 0-10 tags allowed
241
331
  */
242
- const tagsSchema = z.array(tagSchema).min(1, "At least one tag is required").max(10, "Maximum 10 tags allowed");
332
+ const tagsSchema = z.array(tagSchema).max(10, "Maximum 10 tags allowed");
243
333
  const NAME_REGEX = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
244
- const NAME_ERROR = "Must be lowercase alphanumeric with hyphens (e.g., my-preset)";
334
+ const NAME_ERROR = "Must be lowercase alphanumeric with hyphens (e.g., my-rule)";
245
335
  /**
246
- * Schema for preset/rule name.
336
+ * Schema for rule name.
247
337
  * - Max 64 characters
248
338
  * - Lowercase alphanumeric with hyphens
249
339
  */
@@ -260,14 +350,11 @@ const titleSchema = z.string().trim().min(1, "Title is required").max(80, "Title
260
350
  const descriptionSchema = z.string().trim().max(500, "Description must be 500 characters or less");
261
351
 
262
352
  //#endregion
263
- //#region src/preset/schema.ts
353
+ //#region src/rule/schema.ts
264
354
  const VERSION_REGEX$1 = /^[1-9]\d*\.\d+$/;
265
355
  const platformIdSchema = z.enum(PLATFORM_IDS);
266
- /**
267
- * Schema for required description (presets require a description).
268
- * Extends base descriptionSchema with min(1) constraint.
269
- */
270
- const requiredDescriptionSchema = descriptionSchema.min(1, "Description is required");
356
+ const normalizedDescriptionSchema = descriptionSchema.optional().default("");
357
+ const normalizedTagsSchema = tagsSchema.optional().default([]);
271
358
  const versionSchema = z.string().trim().regex(VERSION_REGEX$1, "Version must be in MAJOR.MINOR format (e.g., 1.3)");
272
359
  const majorVersionSchema = z.number().int().positive("Major version must be a positive integer");
273
360
  const featureSchema = z.string().trim().min(1, "Feature cannot be empty").max(100, "Feature must be 100 characters or less");
@@ -287,12 +374,6 @@ const pathSchema = z.string().trim().min(1);
287
374
  const ignorePatternSchema = z.string().trim().min(1, "Ignore pattern cannot be empty");
288
375
  const ignoreSchema = z.array(ignorePatternSchema).max(50, "Maximum 50 ignore patterns allowed");
289
376
  /**
290
- * Schema for agentrulesDir - directory containing metadata files (README, LICENSE, INSTALL).
291
- * Defaults to ".agentrules" if not specified.
292
- * Use "." to read metadata from the project root.
293
- */
294
- const agentrulesPathSchema = z.string().trim().min(1, "agentrulesDir cannot be empty");
295
- /**
296
377
  * Platform entry - either a platform ID string or an object with optional path.
297
378
  *
298
379
  * Examples:
@@ -305,24 +386,32 @@ const platformEntryObjectSchema = z.object({
305
386
  }).strict();
306
387
  const platformEntrySchema = z.union([platformIdSchema, platformEntryObjectSchema]);
307
388
  /**
308
- * Preset config schema.
389
+ * Schema for rule type.
390
+ * Valid types: instruction, rule, command, skill, agent, tool, multi.
391
+ * Optional - defaults to "multi" (freeform file structure).
392
+ * Platform-specific validation happens at publish time.
393
+ */
394
+ const ruleTypeSchema = z.enum(RULE_TYPE_TUPLE);
395
+ const typeSchema = ruleTypeSchema.optional();
396
+ /**
397
+ * Rule config schema.
309
398
  *
310
399
  * Uses a unified `platforms` array that accepts either:
311
400
  * - Platform ID strings: `["opencode", "claude"]`
312
401
  * - Objects with optional path: `[{ platform: "opencode", path: "rules" }]`
313
402
  * - Mixed: `["opencode", { platform: "claude", path: "my-claude" }]`
314
403
  */
315
- const presetConfigSchema = z.object({
404
+ const ruleConfigSchema = z.object({
316
405
  $schema: z.string().optional(),
317
406
  name: nameSchema,
407
+ type: typeSchema,
318
408
  title: titleSchema,
319
409
  version: majorVersionSchema.optional(),
320
- description: requiredDescriptionSchema,
321
- tags: tagsSchema,
410
+ description: normalizedDescriptionSchema,
411
+ tags: normalizedTagsSchema,
322
412
  features: featuresSchema.optional(),
323
413
  license: licenseSchema,
324
414
  ignore: ignoreSchema.optional(),
325
- agentrulesDir: agentrulesPathSchema.optional(),
326
415
  platforms: z.array(platformEntrySchema).min(1, "At least one platform is required")
327
416
  }).strict();
328
417
  const bundledFileSchema = z.object({
@@ -342,46 +431,74 @@ const publishVariantInputSchema = z.object({
342
431
  installMessage: installMessageSchema.optional()
343
432
  });
344
433
  /**
345
- * Schema for what clients send to publish a preset (multi-platform).
434
+ * Schema for what clients send to publish a rule.
346
435
  *
347
436
  * One publish call creates ONE version with ALL platform variants.
348
437
  * Version is optional major version. Registry assigns full MAJOR.MINOR.
349
438
  *
350
- * Note: Clients send `name` (e.g., "my-preset"), and the registry defines the format of the slug.
351
- * For example, a namespaced slug could be returned as "username/my-preset"
439
+ * Note: Clients send `name` (e.g., "my-rule"), and the registry defines the format of the slug.
440
+ * For example, a namespaced slug could be returned as "username/my-rule"
352
441
  */
353
- const presetPublishInputSchema = z.object({
442
+ const rulePublishInputSchema = z.object({
354
443
  name: nameSchema,
444
+ type: typeSchema,
355
445
  title: titleSchema,
356
- description: requiredDescriptionSchema,
357
- tags: tagsSchema,
446
+ description: normalizedDescriptionSchema,
447
+ tags: normalizedTagsSchema,
358
448
  license: licenseSchema,
359
449
  features: featuresSchema.optional(),
360
450
  variants: z.array(publishVariantInputSchema).min(1, "At least one platform variant is required"),
361
451
  version: majorVersionSchema.optional()
362
452
  });
363
453
  /**
364
- * Schema for what registries store and return.
365
- * Includes full namespaced slug and version assigned by registry.
454
+ * Schema for what registries store and return for a single platform bundle.
455
+ * This is per-platform (flat structure), stored in R2 and fetched via bundleUrl.
366
456
  */
367
- const presetBundleSchema = presetPublishInputSchema.omit({
368
- name: true,
369
- version: true
370
- }).extend({
457
+ const ruleBundleSchema = z.object({
458
+ name: nameSchema,
459
+ type: typeSchema,
460
+ platform: platformIdSchema,
461
+ title: titleSchema,
462
+ description: normalizedDescriptionSchema,
463
+ tags: normalizedTagsSchema,
464
+ license: licenseSchema,
465
+ features: featuresSchema.optional(),
466
+ files: z.array(bundledFileSchema).min(1, "At least one file is required"),
467
+ readmeContent: contentSchema.optional(),
468
+ licenseContent: contentSchema.optional(),
469
+ installMessage: installMessageSchema.optional(),
371
470
  slug: z.string().trim().min(1),
372
471
  version: versionSchema
373
472
  });
374
473
 
375
474
  //#endregion
376
- //#region src/preset/types.ts
377
- /**
378
- * Normalize a raw platform entry to the object form.
379
- */
475
+ //#region src/rule/types.ts
476
+ /** Normalize a raw platform entry to the object form */
380
477
  function normalizePlatformEntry(entry) {
381
478
  if (typeof entry === "string") return { platform: entry };
382
479
  return entry;
383
480
  }
384
481
 
482
+ //#endregion
483
+ //#region src/rule/validate.ts
484
+ function validateRule(config) {
485
+ const errors = [];
486
+ const warnings = [];
487
+ if (!config.platforms || config.platforms.length === 0) errors.push("At least one platform is required.");
488
+ for (const entry of config.platforms) if (!isSupportedPlatform(entry.platform)) errors.push(`Unknown platform "${entry.platform}". Supported: ${PLATFORM_IDS.join(", ")}`);
489
+ if (config.type) for (const entry of config.platforms) {
490
+ if (!isSupportedPlatform(entry.platform)) continue;
491
+ if (!isValidType(entry.platform, config.type)) errors.push(`Platform "${entry.platform}" does not support type "${config.type}". Rule "${config.name}" cannot target this platform with type "${config.type}".`);
492
+ }
493
+ const hasPlaceholderFeatures = config.features?.some((feature) => feature.trim().startsWith("//"));
494
+ if (hasPlaceholderFeatures) errors.push("Replace placeholder comments in features before publishing.");
495
+ return {
496
+ valid: errors.length === 0,
497
+ errors,
498
+ warnings
499
+ };
500
+ }
501
+
385
502
  //#endregion
386
503
  //#region src/builder/utils.ts
387
504
  function cleanInstallMessage(value) {
@@ -390,19 +507,19 @@ function cleanInstallMessage(value) {
390
507
  return trimmed.length > 0 ? trimmed : void 0;
391
508
  }
392
509
  /**
393
- * Validate raw preset config from JSON.
510
+ * Validate raw rule config from JSON.
394
511
  * Returns the raw config shape (before normalization).
395
512
  */
396
- function validatePresetConfig(config, slug) {
513
+ function validateConfig(config, slug) {
397
514
  try {
398
- return presetConfigSchema.parse(config);
515
+ return ruleConfigSchema.parse(config);
399
516
  } catch (e) {
400
517
  if (e instanceof ZodError) {
401
518
  const messages = e.issues.map((issue) => {
402
519
  const path = issue.path.length > 0 ? `${issue.path.join(".")}: ` : "";
403
520
  return `${path}${issue.message}`;
404
521
  });
405
- throw new Error(`Invalid preset config for ${slug}:\n - ${messages.join("\n - ")}`);
522
+ throw new Error(`Invalid rule config for ${slug}:\n - ${messages.join("\n - ")}`);
406
523
  }
407
524
  throw e;
408
525
  }
@@ -425,34 +542,39 @@ async function sha256(data) {
425
542
  return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
426
543
  }
427
544
  /**
428
- * Builds a PresetPublishInput from preset input.
545
+ * Builds a RulePublishInput from rule input.
429
546
  *
430
- * PresetInput always has platformFiles array (unified format).
547
+ * RuleInput always has platformFiles array (unified format).
431
548
  */
432
- async function buildPresetPublishInput(options) {
433
- const { preset, version } = options;
434
- if (!NAME_PATTERN.test(preset.name)) throw new Error(`Invalid name "${preset.name}". Names must be lowercase kebab-case.`);
435
- const config = validatePresetConfig(preset.config, preset.name);
549
+ async function buildPublishInput(options) {
550
+ const { rule, version } = options;
551
+ if (!NAME_PATTERN.test(rule.name)) throw new Error(`Invalid name "${rule.name}". Names must be lowercase kebab-case.`);
552
+ const config = validateConfig(rule.config, rule.name);
436
553
  const majorVersion = version ?? config.version;
437
- const platforms = preset.config.platforms;
438
- if (platforms.length === 0) throw new Error(`Preset ${preset.name} must specify at least one platform.`);
439
- for (const entry of platforms) ensureKnownPlatform(entry.platform, preset.name);
554
+ const platforms = rule.config.platforms;
555
+ if (platforms.length === 0) throw new Error(`Rule ${rule.name} must specify at least one platform.`);
556
+ for (const entry of platforms) ensureKnownPlatform(entry.platform, rule.name);
557
+ const ruleType = config.type;
558
+ if (ruleType) {
559
+ for (const entry of platforms) if (!isValidType(entry.platform, ruleType)) throw new Error(`Platform "${entry.platform}" does not support type "${ruleType}". Rule "${rule.name}" cannot target this platform with type "${ruleType}".`);
560
+ }
440
561
  const variants = [];
441
562
  for (const entry of platforms) {
442
- const platformData = preset.platformFiles.find((pf) => pf.platform === entry.platform);
443
- if (!platformData) throw new Error(`Preset ${preset.name} is missing files for platform "${entry.platform}".`);
444
- if (platformData.files.length === 0) throw new Error(`Preset ${preset.name} has no files for platform "${entry.platform}".`);
563
+ const platformData = rule.platformFiles.find((pf) => pf.platform === entry.platform);
564
+ if (!platformData) throw new Error(`Rule ${rule.name} is missing files for platform "${entry.platform}".`);
565
+ if (platformData.files.length === 0) throw new Error(`Rule ${rule.name} has no files for platform "${entry.platform}".`);
445
566
  const files = await createBundledFilesFromInputs(platformData.files);
446
567
  variants.push({
447
568
  platform: entry.platform,
448
569
  files,
449
- readmeContent: platformData.readmeContent?.trim() || preset.readmeContent?.trim() || void 0,
450
- licenseContent: platformData.licenseContent?.trim() || preset.licenseContent?.trim() || void 0,
451
- installMessage: cleanInstallMessage(platformData.installMessage) || cleanInstallMessage(preset.installMessage)
570
+ readmeContent: platformData.readmeContent?.trim() || rule.readmeContent?.trim() || void 0,
571
+ licenseContent: platformData.licenseContent?.trim() || rule.licenseContent?.trim() || void 0,
572
+ installMessage: cleanInstallMessage(platformData.installMessage) || cleanInstallMessage(rule.installMessage)
452
573
  });
453
574
  }
454
575
  return {
455
- name: preset.name,
576
+ name: rule.name,
577
+ ...ruleType && { type: ruleType },
456
578
  title: config.title,
457
579
  description: config.description,
458
580
  tags: config.tags ?? [],
@@ -466,28 +588,28 @@ async function buildPresetPublishInput(options) {
466
588
  * Builds a static registry with items and bundles.
467
589
  *
468
590
  * Uses the same model as dynamic publishing:
469
- * - Each PresetInput (single or multi-platform) becomes one item
591
+ * - Each RuleInput (single or multi-platform) becomes one item
470
592
  * - Each platform variant becomes one bundle
471
593
  */
472
- async function buildPresetRegistry(options) {
594
+ async function buildRegistry(options) {
473
595
  const bundleBase = normalizeBundleBase(options.bundleBase);
474
- const items = [];
596
+ const rules = [];
475
597
  const bundles = [];
476
- for (const presetInput of options.presets) {
477
- const publishInput = await buildPresetPublishInput({ preset: presetInput });
598
+ for (const ruleInput of options.rules) {
599
+ const publishInput = await buildPublishInput({ rule: ruleInput });
478
600
  const slug = publishInput.name;
479
601
  const version = `${publishInput.version ?? 1}.0`;
480
- const presetVariants = publishInput.variants.map((v) => ({
602
+ const ruleVariants = publishInput.variants.map((v) => ({
481
603
  platform: v.platform,
482
604
  bundleUrl: getBundlePath(bundleBase, slug, v.platform, version),
483
605
  fileCount: v.files.length,
484
- totalSize: v.files.reduce((sum, f) => sum + f.content.length, 0)
606
+ totalSize: v.files.reduce((sum, f) => sum + f.size, 0)
485
607
  }));
486
- presetVariants.sort((a, b) => a.platform.localeCompare(b.platform));
487
- const item = {
488
- kind: "preset",
608
+ ruleVariants.sort((a, b) => a.platform.localeCompare(b.platform));
609
+ const rule = {
489
610
  slug,
490
- name: publishInput.title,
611
+ name: publishInput.name,
612
+ ...publishInput.type && { type: publishInput.type },
491
613
  title: publishInput.title,
492
614
  description: publishInput.description,
493
615
  tags: publishInput.tags,
@@ -496,13 +618,14 @@ async function buildPresetRegistry(options) {
496
618
  versions: [{
497
619
  version,
498
620
  isLatest: true,
499
- variants: presetVariants
621
+ variants: ruleVariants
500
622
  }]
501
623
  };
502
- items.push(item);
624
+ rules.push(rule);
503
625
  for (const variant of publishInput.variants) {
504
626
  const bundle = {
505
627
  name: publishInput.name,
628
+ ...publishInput.type && { type: publishInput.type },
506
629
  slug,
507
630
  platform: variant.platform,
508
631
  title: publishInput.title,
@@ -519,13 +642,13 @@ async function buildPresetRegistry(options) {
519
642
  bundles.push(bundle);
520
643
  }
521
644
  }
522
- items.sort((a, b) => a.slug.localeCompare(b.slug));
645
+ rules.sort((a, b) => a.slug.localeCompare(b.slug));
523
646
  bundles.sort((a, b) => {
524
647
  if (a.slug === b.slug) return a.platform.localeCompare(b.platform);
525
648
  return a.slug.localeCompare(b.slug);
526
649
  });
527
650
  return {
528
- items,
651
+ rules,
529
652
  bundles
530
653
  };
531
654
  }
@@ -550,7 +673,10 @@ function normalizeFilePayload(content) {
550
673
  return new Uint8Array(content);
551
674
  }
552
675
  function encodeFilePayload(data, filePath) {
553
- const decoder = new TextDecoder("utf-8", { fatal: true });
676
+ const decoder = new TextDecoder("utf-8", {
677
+ fatal: true,
678
+ ignoreBOM: false
679
+ });
554
680
  try {
555
681
  return decoder.decode(data);
556
682
  } catch {
@@ -563,7 +689,7 @@ function normalizeBundleBase(base) {
563
689
  }
564
690
  function getBundlePath(base, slug, platform, version) {
565
691
  const prefix = base ? `${base}/` : "";
566
- return `${prefix}${STATIC_BUNDLE_DIR}/${slug}/${platform}/${version}`;
692
+ return `${prefix}${STATIC_BUNDLE_DIR}/${slug}/${version}/${platform}.json`;
567
693
  }
568
694
  function ensureKnownPlatform(platform, slug) {
569
695
  if (!isSupportedPlatform(platform)) throw new Error(`Unknown platform "${platform}" in ${slug}. Supported: ${PLATFORM_IDS.join(", ")}`);
@@ -602,40 +728,34 @@ async function sha256Hex(payload) {
602
728
  //#endregion
603
729
  //#region src/constants.ts
604
730
  /**
605
- * Shared constants for agentrules presets and registry.
731
+ * Shared constants for agentrules.
606
732
  */
607
- /** Filename for preset configuration */
608
- const PRESET_CONFIG_FILENAME = "agentrules.json";
609
- /** Directory name for preset metadata (README, LICENSE, etc.) */
610
- const AGENT_RULES_DIR = ".agentrules";
611
- /** JSON Schema URL for preset configuration */
612
- const PRESET_SCHEMA_URL = "https://agentrules.directory/schema/agentrules.json";
733
+ /** Filename for rule configuration */
734
+ const RULE_CONFIG_FILENAME = "agentrules.json";
735
+ /** JSON Schema URL for rule configuration */
736
+ const RULE_SCHEMA_URL = "https://agentrules.directory/schema/agentrules.json";
613
737
  /** API root path segment */
614
738
  const API_PATH = "api";
615
- /** Default version identifier for latest preset version */
739
+ /** Default version identifier for latest rule version */
616
740
  const LATEST_VERSION = "latest";
617
741
  /**
618
742
  * API endpoint paths (relative to registry base URL).
619
743
  *
620
744
  * Note on slug handling:
621
- * - Slugs may contain slashes (e.g., "username/my-preset") which flow through as path segments
745
+ * - Slugs may contain slashes (e.g., "username/my-rule") which flow through as path segments
622
746
  * - The client is responsible for validating values before making requests
623
747
  */
624
748
  const API_ENDPOINTS = {
625
- presets: {
626
- base: `${API_PATH}/presets`,
627
- unpublish: (slug, version) => `${API_PATH}/presets/${slug}/${version}`
749
+ rules: {
750
+ base: `${API_PATH}/rules`,
751
+ get: (slug) => `${API_PATH}/rules/${slug}`,
752
+ unpublish: (slug, version) => `${API_PATH}/rules/${slug}/${version}`
628
753
  },
629
754
  auth: {
630
755
  session: `${API_PATH}/auth/get-session`,
631
756
  deviceCode: `${API_PATH}/auth/device/code`,
632
757
  deviceToken: `${API_PATH}/auth/device/token`
633
- },
634
- rules: {
635
- base: `${API_PATH}/rules`,
636
- bySlug: (slug) => `${API_PATH}/rules/${slug}`
637
- },
638
- items: { get: (slug) => `${API_PATH}/items/${slug}` }
758
+ }
639
759
  };
640
760
 
641
761
  //#endregion
@@ -656,13 +776,13 @@ async function fetchBundle(bundleUrl) {
656
776
  * Resolves a slug to get all versions and platform variants.
657
777
  *
658
778
  * @param baseUrl - Registry base URL
659
- * @param slug - Content slug (may contain slashes, e.g., "username/my-preset")
779
+ * @param slug - Content slug (may contain slashes, e.g., "username/my-rule")
660
780
  * @param version - Optional version filter (server may ignore for static registries)
661
781
  * @returns Resolved data, or null if not found
662
782
  * @throws Error on network/server errors
663
783
  */
664
784
  async function resolveSlug(baseUrl, slug, version) {
665
- const url = new URL(API_ENDPOINTS.items.get(slug), baseUrl);
785
+ const url = new URL(API_ENDPOINTS.rules.get(slug), baseUrl);
666
786
  if (version) url.searchParams.set("version", version);
667
787
  let response;
668
788
  try {
@@ -680,47 +800,33 @@ async function resolveSlug(baseUrl, slug, version) {
680
800
  throw new Error(errorMessage);
681
801
  }
682
802
  const data = await response.json();
683
- if (data.kind === "preset") {
684
- for (const ver of data.versions) for (const variant of ver.variants) if ("bundleUrl" in variant) variant.bundleUrl = new URL(variant.bundleUrl, baseUrl).toString();
685
- }
803
+ for (const ver of data.versions) for (const variant of ver.variants) if ("bundleUrl" in variant) variant.bundleUrl = new URL(variant.bundleUrl, baseUrl).toString();
686
804
  return data;
687
805
  }
688
806
 
689
807
  //#endregion
690
808
  //#region src/resolve/schema.ts
691
809
  const VERSION_REGEX = /^[1-9]\d*\.\d+$/;
692
- const presetVariantBundleSchema = z.object({
810
+ const ruleVariantBundleSchema = z.object({
693
811
  platform: platformIdSchema,
694
812
  bundleUrl: z.string().min(1),
695
813
  fileCount: z.number().int().nonnegative(),
696
814
  totalSize: z.number().int().nonnegative()
697
815
  });
698
- const presetVariantInlineSchema = z.object({
816
+ const ruleVariantInlineSchema = z.object({
699
817
  platform: platformIdSchema,
700
818
  content: z.string().min(1),
701
819
  fileCount: z.number().int().nonnegative(),
702
820
  totalSize: z.number().int().nonnegative()
703
821
  });
704
- const presetVariantSchema = z.union([presetVariantBundleSchema, presetVariantInlineSchema]);
705
- const ruleVariantSchema = z.object({
706
- platform: platformIdSchema,
707
- type: z.string().min(1),
708
- content: z.string().min(1)
709
- });
710
- const presetVersionSchema = z.object({
711
- version: z.string().regex(VERSION_REGEX, "Version must be MAJOR.MINOR format"),
712
- isLatest: z.boolean(),
713
- publishedAt: z.string().datetime().optional(),
714
- variants: z.array(presetVariantSchema).min(1)
715
- });
822
+ const ruleVariantSchema = z.union([ruleVariantBundleSchema, ruleVariantInlineSchema]);
716
823
  const ruleVersionSchema = z.object({
717
824
  version: z.string().regex(VERSION_REGEX, "Version must be MAJOR.MINOR format"),
718
825
  isLatest: z.boolean(),
719
826
  publishedAt: z.string().datetime().optional(),
720
827
  variants: z.array(ruleVariantSchema).min(1)
721
828
  });
722
- const resolvedPresetSchema = z.object({
723
- kind: z.literal("preset"),
829
+ const resolvedRuleSchema = z.object({
724
830
  slug: z.string().min(1),
725
831
  name: z.string().min(1),
726
832
  title: z.string().min(1),
@@ -728,157 +834,55 @@ const resolvedPresetSchema = z.object({
728
834
  tags: z.array(z.string()),
729
835
  license: z.string(),
730
836
  features: z.array(z.string()),
731
- versions: z.array(presetVersionSchema).min(1)
732
- });
733
- const resolvedRuleSchema = z.object({
734
- kind: z.literal("rule"),
735
- slug: z.string().min(1),
736
- name: z.string().min(1),
737
- title: z.string().min(1),
738
- description: z.string(),
739
- tags: z.array(z.string()),
740
837
  versions: z.array(ruleVersionSchema).min(1)
741
838
  });
742
- const resolveResponseSchema = z.discriminatedUnion("kind", [resolvedPresetSchema, resolvedRuleSchema]);
839
+ const resolveResponseSchema = resolvedRuleSchema;
743
840
 
744
841
  //#endregion
745
842
  //#region src/resolve/utils.ts
746
843
  /**
747
- * Type guard for preset
748
- */
749
- function isPreset(item) {
750
- return item.kind === "preset";
751
- }
752
- /**
753
- * Type guard for rule
754
- */
755
- function isRule(item) {
756
- return item.kind === "rule";
757
- }
758
- /**
759
- * Type guard for preset variant with bundleUrl
844
+ * Type guard for rule variant with bundleUrl
760
845
  */
761
846
  function hasBundle(variant) {
762
847
  return "bundleUrl" in variant;
763
848
  }
764
849
  /**
765
- * Type guard for preset variant with inline content
850
+ * Type guard for rule variant with inline content
766
851
  */
767
852
  function hasInlineContent(variant) {
768
853
  return "content" in variant;
769
854
  }
770
855
  /**
771
- * Get the latest version from a resolved preset
772
- */
773
- function getLatestPresetVersion(item) {
774
- return item.versions.find((v) => v.isLatest);
775
- }
776
- /**
777
856
  * Get the latest version from a resolved rule
778
857
  */
779
- function getLatestRuleVersion(item) {
858
+ function getLatestVersion(item) {
780
859
  return item.versions.find((v) => v.isLatest);
781
860
  }
782
861
  /**
783
- * Get a specific version from a resolved preset
784
- */
785
- function getPresetVersion(item, version) {
786
- return item.versions.find((v) => v.version === version);
787
- }
788
- /**
789
862
  * Get a specific version from a resolved rule
790
863
  */
791
- function getRuleVersion(item, version) {
864
+ function getVersion(item, version) {
792
865
  return item.versions.find((v) => v.version === version);
793
866
  }
794
867
  /**
795
- * Get a specific platform variant from a preset version
796
- */
797
- function getPresetVariant(version, platform) {
798
- return version.variants.find((v) => v.platform === platform);
799
- }
800
- /**
801
868
  * Get a specific platform variant from a rule version
802
869
  */
803
- function getRuleVariant(version, platform) {
870
+ function getVariant(version, platform) {
804
871
  return version.variants.find((v) => v.platform === platform);
805
872
  }
806
873
  /**
807
- * Get all available platforms for a preset version
808
- */
809
- function getPresetPlatforms(version) {
810
- return version.variants.map((v) => v.platform);
811
- }
812
- /**
813
874
  * Get all available platforms for a rule version
814
875
  */
815
- function getRulePlatforms(version) {
876
+ function getPlatforms(version) {
816
877
  return version.variants.map((v) => v.platform);
817
878
  }
818
879
  /**
819
- * Check if a platform is available in any version of a preset
820
- */
821
- function presetHasPlatform(item, platform) {
822
- return item.versions.some((v) => v.variants.some((variant) => variant.platform === platform));
823
- }
824
- /**
825
880
  * Check if a platform is available in any version of a rule
826
881
  */
827
- function ruleHasPlatform(item, platform) {
882
+ function hasPlatform(item, platform) {
828
883
  return item.versions.some((v) => v.variants.some((variant) => variant.platform === platform));
829
884
  }
830
885
 
831
- //#endregion
832
- //#region src/rule/schema.ts
833
- /**
834
- * Rule-specific schema aliases.
835
- * All use shared schemas for consistency with presets:
836
- * - name: max 64 chars, lowercase kebab-case
837
- * - title: max 80 chars
838
- * - description: max 500 chars (optional for rules)
839
- * - tags: max 35 chars each, 1-10 required, platform names blocked
840
- */
841
- const ruleNameSchema = nameSchema;
842
- const ruleTitleSchema = titleSchema;
843
- const ruleDescriptionSchema = descriptionSchema;
844
- const ruleTagSchema = tagSchema;
845
- const ruleTagsSchema = tagsSchema;
846
- const rulePlatformSchema = z.enum(PLATFORM_IDS);
847
- const ruleTypeSchema = z.string().trim().min(1).max(32);
848
- const ruleContentSchema = z.string().min(1, "Content is required").max(1e5, "Content must be 100KB or less");
849
- /** Common fields shared across all platform-type combinations */
850
- const ruleCommonFields = {
851
- name: ruleNameSchema,
852
- title: ruleTitleSchema,
853
- description: ruleDescriptionSchema.optional(),
854
- content: ruleContentSchema,
855
- tags: ruleTagsSchema
856
- };
857
- /**
858
- * Discriminated union schema for platform + type combinations.
859
- * Each platform has its own set of valid types.
860
- */
861
- const rulePlatformTypeSchema = z.discriminatedUnion("platform", [
862
- z.object({
863
- platform: z.literal("opencode"),
864
- type: z.enum(PLATFORM_RULE_TYPES.opencode)
865
- }),
866
- z.object({
867
- platform: z.literal("claude"),
868
- type: z.enum(PLATFORM_RULE_TYPES.claude)
869
- }),
870
- z.object({
871
- platform: z.literal("cursor"),
872
- type: z.enum(PLATFORM_RULE_TYPES.cursor)
873
- }),
874
- z.object({
875
- platform: z.literal("codex"),
876
- type: z.enum(PLATFORM_RULE_TYPES.codex)
877
- })
878
- ]);
879
- /** Schema for rule creation with discriminated union for platform+type */
880
- const ruleCreateInputSchema = z.object(ruleCommonFields).and(rulePlatformTypeSchema);
881
-
882
886
  //#endregion
883
887
  //#region src/utils/diff.ts
884
888
  const DEFAULT_CONTEXT = 2;
@@ -903,4 +907,4 @@ function normalizeBundlePath(value) {
903
907
  }
904
908
 
905
909
  //#endregion
906
- export { AGENT_RULES_DIR, API_ENDPOINTS, COMMON_LICENSES, LATEST_VERSION, PLATFORMS, PLATFORM_IDS, PLATFORM_ID_TUPLE, PLATFORM_RULE_TYPES, PRESET_CONFIG_FILENAME, PRESET_SCHEMA_URL, STATIC_BUNDLE_DIR, buildPresetPublishInput, buildPresetRegistry, bundledFileSchema, cleanInstallMessage, createDiffPreview, decodeBundledFile, decodeUtf8, descriptionSchema, encodeUtf8, fetchBundle, getInstallPath, getLatestPresetVersion, getLatestRuleVersion, getPlatformConfig, getPlatformFromDir, getPresetPlatforms, getPresetVariant, getPresetVersion, getRulePlatforms, getRuleTypeConfig, getRuleVariant, getRuleVersion, getValidRuleTypes, hasBundle, hasInlineContent, isLikelyText, isPlatformDir, isPreset, isRule, isSupportedPlatform, isValidRuleType, licenseSchema, nameSchema, normalizeBundlePath, normalizePlatformEntry, normalizePlatformInput, platformIdSchema, presetBundleSchema, presetConfigSchema, presetHasPlatform, presetPublishInputSchema, presetVariantSchema, presetVersionSchema, publishVariantInputSchema, requiredDescriptionSchema, resolveResponseSchema, resolveSlug, resolvedPresetSchema, resolvedRuleSchema, ruleContentSchema, ruleCreateInputSchema, ruleDescriptionSchema, ruleHasPlatform, ruleNameSchema, rulePlatformSchema, rulePlatformTypeSchema, ruleTagSchema, ruleTagsSchema, ruleTitleSchema, ruleTypeSchema, ruleVariantSchema, ruleVersionSchema, tagSchema, tagsSchema, titleSchema, toPosixPath, toUint8Array, toUtf8String, validatePresetConfig, verifyBundledFileChecksum };
910
+ export { API_ENDPOINTS, COMMON_LICENSES, LATEST_VERSION, PLATFORMS, PLATFORM_IDS, PLATFORM_ID_TUPLE, PLATFORM_RULE_TYPES, RULE_CONFIG_FILENAME, RULE_SCHEMA_URL, RULE_TYPE_TUPLE, STATIC_BUNDLE_DIR, buildPublishInput, buildRegistry, bundledFileSchema, cleanInstallMessage, createDiffPreview, decodeBundledFile, decodeUtf8, descriptionSchema, encodeUtf8, fetchBundle, getInstallPath, getLatestVersion, getPlatformConfig, getPlatformFromDir, getPlatforms, getRelativeInstallPath, getTypeConfig, getValidTypes, getVariant, getVersion, hasBundle, hasInlineContent, hasPlatform, inferInstructionPlatformsFromFileName, inferPlatformFromPath, inferTypeFromPath, isLikelyText, isPlatformDir, isSupportedPlatform, isValidType, licenseSchema, nameSchema, normalizeBundlePath, normalizePlatformEntry, normalizePlatformInput, platformIdSchema, publishVariantInputSchema, resolveResponseSchema, resolveSlug, resolvedRuleSchema, ruleBundleSchema, ruleConfigSchema, rulePublishInputSchema, ruleTypeSchema, ruleVariantSchema, ruleVersionSchema, supportsInstallPath, tagSchema, tagsSchema, titleSchema, toPosixPath, toUint8Array, toUtf8String, validateConfig, validateRule, verifyBundledFileChecksum };