@agentrules/core 0.0.10 → 0.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.d.ts +304 -23
  2. package/dist/index.js +228 -33
  3. package/package.json +1 -1
package/dist/index.d.ts CHANGED
@@ -42,9 +42,12 @@ declare const bundledFileSchema: z.ZodObject<{
42
42
  /**
43
43
  * Schema for what clients send to publish a preset.
44
44
  * Version is optional major version. Registry assigns full MAJOR.MINOR.
45
+ *
46
+ * Note: Clients send `name` (e.g., "my-preset"), and the registry defines the format of the slug.
47
+ * For example, a namespaced slug could be returned as "username/my-preset"
45
48
  */
46
49
  declare const presetPublishInputSchema: z.ZodObject<{
47
- slug: z.ZodString;
50
+ name: z.ZodString;
48
51
  platform: z.ZodEnum<{
49
52
  opencode: "opencode";
50
53
  codex: "codex";
@@ -69,7 +72,7 @@ declare const presetPublishInputSchema: z.ZodObject<{
69
72
  }, z.core.$strip>;
70
73
  /**
71
74
  * Schema for what registries store and return.
72
- * Includes version (required) - full MAJOR.MINOR format assigned by registry.
75
+ * Includes full namespaced slug and version assigned by registry.
73
76
  */
74
77
  declare const presetBundleSchema: z.ZodObject<{
75
78
  title: z.ZodString;
@@ -83,7 +86,6 @@ declare const presetBundleSchema: z.ZodObject<{
83
86
  cursor: "cursor";
84
87
  }>;
85
88
  features: z.ZodOptional<z.ZodArray<z.ZodString>>;
86
- slug: z.ZodString;
87
89
  licenseContent: z.ZodOptional<z.ZodString>;
88
90
  readmeContent: z.ZodOptional<z.ZodString>;
89
91
  installMessage: z.ZodOptional<z.ZodString>;
@@ -93,6 +95,7 @@ declare const presetBundleSchema: z.ZodObject<{
93
95
  checksum: z.ZodString;
94
96
  contents: z.ZodString;
95
97
  }, z.core.$strip>>;
98
+ slug: z.ZodString;
96
99
  version: z.ZodString;
97
100
  }, z.core.$strip>;
98
101
  declare const presetSchema: z.ZodObject<{
@@ -137,29 +140,192 @@ declare const presetIndexSchema: z.ZodRecord<z.ZodString, z.ZodObject<{
137
140
  //#endregion
138
141
  //#region src/platform/types.d.ts
139
142
  /**
140
- * Single source of truth for platform IDs.
141
- * Add new platforms here - types and config will follow.
143
+ * Platform and rule type definitions.
142
144
  */
143
145
  declare const PLATFORM_ID_TUPLE: readonly ["opencode", "codex", "claude", "cursor"];
144
- /** Union type of supported platform IDs, derived from PLATFORM_ID_TUPLE */
146
+ /** Union type of supported platform IDs */
145
147
  type PlatformId = (typeof PLATFORM_ID_TUPLE)[number];
146
- /** Configuration for a platform's directory paths */
147
- type PlatformConfig = {
148
- /** Directory name for project installs (e.g., ".opencode") */
148
+ /** File format for a rule type */
149
+ type RuleFileFormat = "markdown" | "typescript" | "mdc";
150
+ /** Configuration for a single rule type */
151
+ type RuleTypeConfig = {
152
+ /** Human-readable description */
153
+ description: string;
154
+ /** File format */
155
+ format: RuleFileFormat;
156
+ /** File extension (without dot) */
157
+ extension: string;
158
+ /**
159
+ * Install path pattern relative to project root.
160
+ * Use {name} as placeholder for the rule slug/filename.
161
+ * null if project install not supported.
162
+ */
163
+ projectPath: string | null;
164
+ /**
165
+ * Install path pattern for global/user install.
166
+ * Use ~ for home directory.
167
+ * null if global install not supported.
168
+ */
169
+ globalPath: string | null;
170
+ };
171
+ /** Platform configuration with all its rule types */
172
+ type PlatformRuleConfig = {
173
+ /** Human-readable platform name */
174
+ label: string;
175
+ /** Platform's project directory (e.g., ".opencode") */
149
176
  projectDir: string;
150
- /** Path for global installs (e.g., "~/.config/opencode") */
151
- globalDir: string;
177
+ /** Platform's global config directory (null if not supported) */
178
+ globalDir: string | null;
179
+ /** Rule types supported by this platform */
180
+ types: Record<string, RuleTypeConfig>;
181
+ };
182
+ /**
183
+ * Discriminated union of valid platform + type combinations.
184
+ * Must be kept in sync with PLATFORMS in config.ts.
185
+ */
186
+ type PlatformRuleType = {
187
+ platform: "opencode";
188
+ type: "instruction" | "agent" | "command" | "tool";
189
+ } | {
190
+ platform: "claude";
191
+ type: "instruction" | "command" | "skill";
192
+ } | {
193
+ platform: "cursor";
194
+ type: "rule";
195
+ } | {
196
+ platform: "codex";
197
+ type: "instruction" | "command";
152
198
  };
199
+ /** Extract rule type for a specific platform */
200
+ type RuleTypeForPlatform<P extends PlatformId> = Extract<PlatformRuleType, {
201
+ platform: P;
202
+ }>["type"];
203
+ /** Union of all valid rule types across all platforms */
204
+ type RuleType = PlatformRuleType["type"];
153
205
 
154
206
  //#endregion
155
207
  //#region src/platform/config.d.ts
156
- /** List of supported platform IDs as a readonly tuple */
157
- declare const PLATFORM_IDS: readonly ["opencode", "codex", "claude", "cursor"];
208
+ declare const PLATFORM_IDS: [PlatformId, ...PlatformId[]];
158
209
  /**
159
- * Platform-specific configuration.
160
- * Single source of truth for all platform paths.
210
+ * Platform configuration including supported rule types and install paths.
161
211
  */
162
- declare const PLATFORMS: Record<PlatformId, PlatformConfig>;
212
+ declare const PLATFORMS: {
213
+ readonly opencode: {
214
+ readonly label: "OpenCode";
215
+ readonly projectDir: ".opencode";
216
+ readonly globalDir: "~/.config/opencode";
217
+ readonly types: {
218
+ readonly instruction: {
219
+ readonly description: "Project instructions (AGENTS.md)";
220
+ readonly format: "markdown";
221
+ readonly extension: "md";
222
+ readonly projectPath: "AGENTS.md";
223
+ readonly globalPath: "~/.config/opencode/AGENTS.md";
224
+ };
225
+ readonly agent: {
226
+ readonly description: "Specialized AI agent definition";
227
+ readonly format: "markdown";
228
+ readonly extension: "md";
229
+ readonly projectPath: ".opencode/agent/{name}.md";
230
+ readonly globalPath: "~/.config/opencode/agent/{name}.md";
231
+ };
232
+ readonly command: {
233
+ readonly description: "Custom slash command";
234
+ readonly format: "markdown";
235
+ readonly extension: "md";
236
+ readonly projectPath: ".opencode/command/{name}.md";
237
+ readonly globalPath: "~/.config/opencode/command/{name}.md";
238
+ };
239
+ readonly tool: {
240
+ readonly description: "Custom tool definition";
241
+ readonly format: "typescript";
242
+ readonly extension: "ts";
243
+ readonly projectPath: ".opencode/tool/{name}.ts";
244
+ readonly globalPath: "~/.config/opencode/tool/{name}.ts";
245
+ };
246
+ };
247
+ };
248
+ readonly claude: {
249
+ readonly label: "Claude Code";
250
+ readonly projectDir: ".claude";
251
+ readonly globalDir: "~/.claude";
252
+ readonly types: {
253
+ readonly instruction: {
254
+ readonly description: "Project instructions (CLAUDE.md)";
255
+ readonly format: "markdown";
256
+ readonly extension: "md";
257
+ readonly projectPath: "CLAUDE.md";
258
+ readonly globalPath: "~/.claude/CLAUDE.md";
259
+ };
260
+ readonly command: {
261
+ readonly description: "Custom slash command";
262
+ readonly format: "markdown";
263
+ readonly extension: "md";
264
+ readonly projectPath: ".claude/commands/{name}.md";
265
+ readonly globalPath: "~/.claude/commands/{name}.md";
266
+ };
267
+ readonly skill: {
268
+ readonly description: "Custom skill definition";
269
+ readonly format: "markdown";
270
+ readonly extension: "md";
271
+ readonly projectPath: ".claude/skills/{name}/SKILL.md";
272
+ readonly globalPath: "~/.claude/skills/{name}/SKILL.md";
273
+ };
274
+ };
275
+ };
276
+ readonly cursor: {
277
+ readonly label: "Cursor";
278
+ readonly projectDir: ".cursor";
279
+ readonly globalDir: null;
280
+ readonly types: {
281
+ readonly rule: {
282
+ readonly description: "Project rule (MDC format)";
283
+ readonly format: "mdc";
284
+ readonly extension: "mdc";
285
+ readonly projectPath: ".cursor/rules/{name}.mdc";
286
+ readonly globalPath: null;
287
+ };
288
+ };
289
+ };
290
+ readonly codex: {
291
+ readonly label: "Codex";
292
+ readonly projectDir: "";
293
+ readonly globalDir: "~/.codex";
294
+ readonly types: {
295
+ readonly instruction: {
296
+ readonly description: "Project instructions (AGENTS.md)";
297
+ readonly format: "markdown";
298
+ readonly extension: "md";
299
+ readonly projectPath: "AGENTS.md";
300
+ readonly globalPath: "~/.codex/AGENTS.md";
301
+ };
302
+ readonly command: {
303
+ readonly description: "Custom prompt (global only)";
304
+ readonly format: "markdown";
305
+ readonly extension: "md";
306
+ readonly projectPath: null;
307
+ readonly globalPath: "~/.codex/prompts/{name}.md";
308
+ };
309
+ };
310
+ };
311
+ };
312
+ /** Valid rule types for each platform. Must be kept in sync with PLATFORMS. */
313
+ declare const PLATFORM_RULE_TYPES: {
314
+ readonly opencode: readonly ["instruction", "agent", "command", "tool"];
315
+ readonly claude: readonly ["instruction", "command", "skill"];
316
+ readonly cursor: readonly ["rule"];
317
+ readonly codex: readonly ["instruction", "command"];
318
+ };
319
+ /** Get valid rule types for a specific platform */
320
+ declare function getValidRuleTypes(platform: PlatformId): readonly string[];
321
+ /** Check if a type is valid for a given platform */
322
+ declare function isValidRuleType(platform: PlatformId, type: string): boolean;
323
+ /** Get the configuration for a specific platform + type combination */
324
+ declare function getRuleTypeConfig(platform: PlatformId, type: string): RuleTypeConfig | undefined;
325
+ /** Get the install path for a rule, replacing {name} placeholder */
326
+ declare function getInstallPath(platform: PlatformId, type: string, name: string, location?: "project" | "global"): string | null;
327
+ /** Get platform configuration */
328
+ declare function getPlatformConfig(platform: PlatformId): PlatformRuleConfig;
163
329
 
164
330
  //#endregion
165
331
  //#region src/platform/utils.d.ts
@@ -202,9 +368,12 @@ type BundledFile = {
202
368
  /**
203
369
  * What clients send to publish a preset.
204
370
  * Version is optional major version. Registry assigns full MAJOR.MINOR.
371
+ *
372
+ * Note: Clients send `name` (e.g., "my-preset"), and the registry defines the format of the slug.
373
+ * For example, a namespaced slug could be returned as "username/my-preset"
205
374
  */
206
375
  type PresetPublishInput = {
207
- slug: string;
376
+ name: string;
208
377
  platform: PlatformId;
209
378
  title: string;
210
379
  description: string;
@@ -220,9 +389,11 @@ type PresetPublishInput = {
220
389
  };
221
390
  /**
222
391
  * What registries store and return.
223
- * Includes version (required) - full MAJOR.MINOR format assigned by registry.
392
+ * Includes full namespaced slug and version assigned by registry.
224
393
  */
225
- type PresetBundle = Omit<PresetPublishInput, "version"> & {
394
+ type PresetBundle = Omit<PresetPublishInput, "name" | "version"> & {
395
+ /** Full namespaced slug (e.g., "username/my-preset") */
396
+ slug: string;
226
397
  /** Full version in MAJOR.MINOR format (e.g., "1.3", "2.1") */
227
398
  version: string;
228
399
  };
@@ -246,7 +417,7 @@ type PresetFileInput = {
246
417
  contents: ArrayBuffer | ArrayBufferView | string;
247
418
  };
248
419
  type PresetInput = {
249
- slug: string;
420
+ name: string;
250
421
  config: PresetConfig;
251
422
  files: PresetFileInput[];
252
423
  /** Install message from INSTALL.txt file */
@@ -261,7 +432,7 @@ type PresetInput = {
261
432
  * Directory name for bundle files in static registry output.
262
433
  * Used by `agentrules registry build` to structure output.
263
434
  */
264
- declare const STATIC_BUNDLE_DIR = "r";
435
+ declare const STATIC_BUNDLE_DIR = "registry";
265
436
  /**
266
437
  * Options for building a PresetPublishInput (for CLI publish command).
267
438
  */
@@ -351,12 +522,20 @@ declare const PRESET_SCHEMA_URL = "https://agentrules.directory/schema/agentrule
351
522
  declare const LATEST_VERSION = "latest";
352
523
  /**
353
524
  * API endpoint paths (relative to registry base URL).
525
+ *
526
+ * Note: Path parameters (slug, platform, version) are NOT URI-encoded.
527
+ * - Slugs may contain slashes (e.g., "username/my-preset") which should flow
528
+ * through as path segments for static registry compatibility
529
+ * - Platform and version are constrained values (enums, validated formats)
530
+ * that only contain URL-safe characters
531
+ *
532
+ * The client is responsible for validating these values before making requests.
354
533
  */
355
534
  declare const API_ENDPOINTS: {
356
535
  /** Preset endpoints */
357
536
  readonly presets: {
358
537
  /** Base path for preset operations */
359
- readonly base: "api/presets";
538
+ readonly base: "api/preset";
360
539
  /** Get preset by slug, platform, and version (defaults to "latest") */
361
540
  readonly get: (slug: string, platform: string, version?: string) => string;
362
541
  /** Unpublish preset version */
@@ -371,10 +550,112 @@ declare const API_ENDPOINTS: {
371
550
  /** Device token exchange */
372
551
  readonly deviceToken: "api/auth/device/token";
373
552
  };
553
+ /** Rule endpoints */
554
+ readonly rule: {
555
+ /** Base path for rule operations */
556
+ readonly base: "api/rule";
557
+ /** Get or update rule by slug */
558
+ readonly get: (slug: string) => string;
559
+ };
374
560
  };
375
561
 
562
+ //#endregion
563
+ //#region src/rule/schema.d.ts
564
+ /**
565
+ * Schema for the rule name.
566
+ * This is what users provide when creating a rule.
567
+ */
568
+ declare const ruleNameSchema: z.ZodString;
569
+ declare const ruleTitleSchema: z.ZodString;
570
+ declare const ruleDescriptionSchema: z.ZodString;
571
+ declare const rulePlatformSchema: z.ZodEnum<{
572
+ opencode: "opencode";
573
+ codex: "codex";
574
+ claude: "claude";
575
+ cursor: "cursor";
576
+ }>;
577
+ declare const ruleTypeSchema: z.ZodString;
578
+ declare const ruleContentSchema: z.ZodString;
579
+ declare const ruleTagSchema: z.ZodString;
580
+ declare const ruleTagsSchema: z.ZodArray<z.ZodString>;
581
+ /**
582
+ * Discriminated union schema for platform + type combinations.
583
+ * Each platform has its own set of valid types.
584
+ */
585
+ declare const rulePlatformTypeSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
586
+ platform: z.ZodLiteral<"opencode">;
587
+ type: z.ZodEnum<{
588
+ instruction: "instruction";
589
+ agent: "agent";
590
+ command: "command";
591
+ tool: "tool";
592
+ }>;
593
+ }, z.core.$strip>, z.ZodObject<{
594
+ platform: z.ZodLiteral<"claude">;
595
+ type: z.ZodEnum<{
596
+ instruction: "instruction";
597
+ command: "command";
598
+ skill: "skill";
599
+ }>;
600
+ }, z.core.$strip>, z.ZodObject<{
601
+ platform: z.ZodLiteral<"cursor">;
602
+ type: z.ZodEnum<{
603
+ rule: "rule";
604
+ }>;
605
+ }, z.core.$strip>, z.ZodObject<{
606
+ platform: z.ZodLiteral<"codex">;
607
+ type: z.ZodEnum<{
608
+ instruction: "instruction";
609
+ command: "command";
610
+ }>;
611
+ }, z.core.$strip>], "platform">;
612
+ /** Schema for rule creation with discriminated union for platform+type */
613
+ declare const ruleCreateInputSchema: z.ZodIntersection<z.ZodObject<{
614
+ name: z.ZodString;
615
+ title: z.ZodString;
616
+ description: z.ZodOptional<z.ZodString>;
617
+ content: z.ZodString;
618
+ tags: z.ZodArray<z.ZodString>;
619
+ }, z.core.$strip>, z.ZodDiscriminatedUnion<[z.ZodObject<{
620
+ platform: z.ZodLiteral<"opencode">;
621
+ type: z.ZodEnum<{
622
+ instruction: "instruction";
623
+ agent: "agent";
624
+ command: "command";
625
+ tool: "tool";
626
+ }>;
627
+ }, z.core.$strip>, z.ZodObject<{
628
+ platform: z.ZodLiteral<"claude">;
629
+ type: z.ZodEnum<{
630
+ instruction: "instruction";
631
+ command: "command";
632
+ skill: "skill";
633
+ }>;
634
+ }, z.core.$strip>, z.ZodObject<{
635
+ platform: z.ZodLiteral<"cursor">;
636
+ type: z.ZodEnum<{
637
+ rule: "rule";
638
+ }>;
639
+ }, z.core.$strip>, z.ZodObject<{
640
+ platform: z.ZodLiteral<"codex">;
641
+ type: z.ZodEnum<{
642
+ instruction: "instruction";
643
+ command: "command";
644
+ }>;
645
+ }, z.core.$strip>], "platform">>;
646
+ declare const ruleUpdateInputSchema: z.ZodObject<{
647
+ name: z.ZodString;
648
+ title: z.ZodOptional<z.ZodString>;
649
+ description: z.ZodOptional<z.ZodString>;
650
+ content: z.ZodOptional<z.ZodString>;
651
+ tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
652
+ }, z.core.$strip>;
653
+ type RuleCreateInput = z.infer<typeof ruleCreateInputSchema>;
654
+ type RuleUpdateInput = z.infer<typeof ruleUpdateInputSchema>;
655
+
376
656
  //#endregion
377
657
  //#region src/utils/diff.d.ts
658
+ /** Re-export platform-rule types for convenience */
378
659
  type DiffPreviewOptions = {
379
660
  context?: number;
380
661
  maxLines?: number;
@@ -397,4 +678,4 @@ declare function toUint8Array(payload: ArrayBuffer | ArrayBufferView): Uint8Arra
397
678
  declare function normalizeBundlePath(value: string): string;
398
679
 
399
680
  //#endregion
400
- export { AGENT_RULES_DIR, API_ENDPOINTS, BuildPresetPublishInputOptions, BuildPresetRegistryOptions, BuildPresetRegistryResult, BundledFile, COMMON_LICENSES, CommonLicense, DiffPreviewOptions, LATEST_VERSION, PLATFORMS, PLATFORM_IDS, PLATFORM_ID_TUPLE, PRESET_CONFIG_FILENAME, PRESET_SCHEMA_URL, PlatformConfig, PlatformId, Preset, PresetBundle, PresetConfig, PresetFileInput, PresetIndex, PresetInput, PresetPublishInput, ResolvedPreset, STATIC_BUNDLE_DIR, buildPresetPublishInput, buildPresetRegistry, bundledFileSchema, cleanInstallMessage, createDiffPreview, decodeBundledFile, decodeUtf8, descriptionSchema, encodeItemName, encodeUtf8, fetchBundle, getPlatformFromDir, isLikelyText, isPlatformDir, isSupportedPlatform, licenseSchema, normalizeBundlePath, normalizePlatformInput, platformIdSchema, presetBundleSchema, presetConfigSchema, presetIndexSchema, presetPublishInputSchema, presetSchema, resolvePreset, slugSchema, tagSchema, tagsSchema, titleSchema, toPosixPath, toUint8Array, toUtf8String, validatePresetConfig, verifyBundledFileChecksum };
681
+ export { AGENT_RULES_DIR, API_ENDPOINTS, BuildPresetPublishInputOptions, BuildPresetRegistryOptions, BuildPresetRegistryResult, BundledFile, COMMON_LICENSES, CommonLicense, DiffPreviewOptions, LATEST_VERSION, PLATFORMS, PLATFORM_IDS, PLATFORM_ID_TUPLE, PLATFORM_RULE_TYPES, PRESET_CONFIG_FILENAME, PRESET_SCHEMA_URL, PlatformId, PlatformRuleConfig, PlatformRuleType, Preset, PresetBundle, PresetConfig, PresetFileInput, PresetIndex, PresetInput, PresetPublishInput, ResolvedPreset, RuleCreateInput, RuleFileFormat, RuleType, RuleTypeConfig, RuleTypeForPlatform, RuleUpdateInput, STATIC_BUNDLE_DIR, buildPresetPublishInput, buildPresetRegistry, bundledFileSchema, cleanInstallMessage, createDiffPreview, decodeBundledFile, decodeUtf8, descriptionSchema, encodeItemName, encodeUtf8, fetchBundle, getInstallPath, getPlatformConfig, getPlatformFromDir, getRuleTypeConfig, getValidRuleTypes, isLikelyText, isPlatformDir, isSupportedPlatform, isValidRuleType, licenseSchema, normalizeBundlePath, normalizePlatformInput, platformIdSchema, presetBundleSchema, presetConfigSchema, presetIndexSchema, presetPublishInputSchema, presetSchema, resolvePreset, ruleContentSchema, ruleCreateInputSchema, ruleDescriptionSchema, ruleNameSchema, rulePlatformSchema, rulePlatformTypeSchema, ruleTagSchema, ruleTagsSchema, ruleTitleSchema, ruleTypeSchema, ruleUpdateInputSchema, slugSchema, tagSchema, tagsSchema, titleSchema, toPosixPath, toUint8Array, toUtf8String, validatePresetConfig, verifyBundledFileChecksum };
package/dist/index.js CHANGED
@@ -17,25 +17,36 @@ const API_PATH = "api";
17
17
  const LATEST_VERSION = "latest";
18
18
  /**
19
19
  * API endpoint paths (relative to registry base URL).
20
+ *
21
+ * Note: Path parameters (slug, platform, version) are NOT URI-encoded.
22
+ * - Slugs may contain slashes (e.g., "username/my-preset") which should flow
23
+ * through as path segments for static registry compatibility
24
+ * - Platform and version are constrained values (enums, validated formats)
25
+ * that only contain URL-safe characters
26
+ *
27
+ * The client is responsible for validating these values before making requests.
20
28
  */
21
29
  const API_ENDPOINTS = {
22
30
  presets: {
23
- base: `${API_PATH}/presets`,
24
- get: (slug, platform, version = LATEST_VERSION) => `${API_PATH}/presets/${encodeURIComponent(slug)}/${encodeURIComponent(platform)}/${encodeURIComponent(version)}`,
25
- unpublish: (slug, platform, version) => `${API_PATH}/presets/${encodeURIComponent(slug)}/${encodeURIComponent(platform)}/${encodeURIComponent(version)}`
31
+ base: `${API_PATH}/preset`,
32
+ get: (slug, platform, version = LATEST_VERSION) => `${API_PATH}/preset/${slug}/${platform}/${version}`,
33
+ unpublish: (slug, platform, version) => `${API_PATH}/preset/${slug}/${platform}/${version}`
26
34
  },
27
35
  auth: {
28
36
  session: `${API_PATH}/auth/get-session`,
29
37
  deviceCode: `${API_PATH}/auth/device/code`,
30
38
  deviceToken: `${API_PATH}/auth/device/token`
39
+ },
40
+ rule: {
41
+ base: `${API_PATH}/rule`,
42
+ get: (slug) => `${API_PATH}/rule/${slug}`
31
43
  }
32
44
  };
33
45
 
34
46
  //#endregion
35
47
  //#region src/platform/types.ts
36
48
  /**
37
- * Single source of truth for platform IDs.
38
- * Add new platforms here - types and config will follow.
49
+ * Platform and rule type definitions.
39
50
  */
40
51
  const PLATFORM_ID_TUPLE = [
41
52
  "opencode",
@@ -46,30 +57,149 @@ const PLATFORM_ID_TUPLE = [
46
57
 
47
58
  //#endregion
48
59
  //#region src/platform/config.ts
49
- /** List of supported platform IDs as a readonly tuple */
50
60
  const PLATFORM_IDS = PLATFORM_ID_TUPLE;
51
61
  /**
52
- * Platform-specific configuration.
53
- * Single source of truth for all platform paths.
62
+ * Platform configuration including supported rule types and install paths.
54
63
  */
55
64
  const PLATFORMS = {
56
65
  opencode: {
66
+ label: "OpenCode",
57
67
  projectDir: ".opencode",
58
- globalDir: "~/.config/opencode"
59
- },
60
- codex: {
61
- projectDir: ".codex",
62
- globalDir: "~/.codex"
68
+ globalDir: "~/.config/opencode",
69
+ types: {
70
+ instruction: {
71
+ description: "Project instructions (AGENTS.md)",
72
+ format: "markdown",
73
+ extension: "md",
74
+ projectPath: "AGENTS.md",
75
+ globalPath: "~/.config/opencode/AGENTS.md"
76
+ },
77
+ agent: {
78
+ description: "Specialized AI agent definition",
79
+ format: "markdown",
80
+ extension: "md",
81
+ projectPath: ".opencode/agent/{name}.md",
82
+ globalPath: "~/.config/opencode/agent/{name}.md"
83
+ },
84
+ command: {
85
+ description: "Custom slash command",
86
+ format: "markdown",
87
+ extension: "md",
88
+ projectPath: ".opencode/command/{name}.md",
89
+ globalPath: "~/.config/opencode/command/{name}.md"
90
+ },
91
+ tool: {
92
+ description: "Custom tool definition",
93
+ format: "typescript",
94
+ extension: "ts",
95
+ projectPath: ".opencode/tool/{name}.ts",
96
+ globalPath: "~/.config/opencode/tool/{name}.ts"
97
+ }
98
+ }
63
99
  },
64
100
  claude: {
101
+ label: "Claude Code",
65
102
  projectDir: ".claude",
66
- globalDir: "~/.claude"
103
+ globalDir: "~/.claude",
104
+ types: {
105
+ instruction: {
106
+ description: "Project instructions (CLAUDE.md)",
107
+ format: "markdown",
108
+ extension: "md",
109
+ projectPath: "CLAUDE.md",
110
+ globalPath: "~/.claude/CLAUDE.md"
111
+ },
112
+ command: {
113
+ description: "Custom slash command",
114
+ format: "markdown",
115
+ extension: "md",
116
+ projectPath: ".claude/commands/{name}.md",
117
+ globalPath: "~/.claude/commands/{name}.md"
118
+ },
119
+ skill: {
120
+ description: "Custom skill definition",
121
+ format: "markdown",
122
+ extension: "md",
123
+ projectPath: ".claude/skills/{name}/SKILL.md",
124
+ globalPath: "~/.claude/skills/{name}/SKILL.md"
125
+ }
126
+ }
67
127
  },
68
128
  cursor: {
129
+ label: "Cursor",
69
130
  projectDir: ".cursor",
70
- globalDir: "~/.cursor"
131
+ globalDir: null,
132
+ types: { rule: {
133
+ description: "Project rule (MDC format)",
134
+ format: "mdc",
135
+ extension: "mdc",
136
+ projectPath: ".cursor/rules/{name}.mdc",
137
+ globalPath: null
138
+ } }
139
+ },
140
+ codex: {
141
+ label: "Codex",
142
+ projectDir: "",
143
+ globalDir: "~/.codex",
144
+ types: {
145
+ instruction: {
146
+ description: "Project instructions (AGENTS.md)",
147
+ format: "markdown",
148
+ extension: "md",
149
+ projectPath: "AGENTS.md",
150
+ globalPath: "~/.codex/AGENTS.md"
151
+ },
152
+ command: {
153
+ description: "Custom prompt (global only)",
154
+ format: "markdown",
155
+ extension: "md",
156
+ projectPath: null,
157
+ globalPath: "~/.codex/prompts/{name}.md"
158
+ }
159
+ }
71
160
  }
72
161
  };
162
+ /** Valid rule types for each platform. Must be kept in sync with PLATFORMS. */
163
+ const PLATFORM_RULE_TYPES = {
164
+ opencode: [
165
+ "instruction",
166
+ "agent",
167
+ "command",
168
+ "tool"
169
+ ],
170
+ claude: [
171
+ "instruction",
172
+ "command",
173
+ "skill"
174
+ ],
175
+ cursor: ["rule"],
176
+ codex: ["instruction", "command"]
177
+ };
178
+ /** Get valid rule types for a specific platform */
179
+ function getValidRuleTypes(platform) {
180
+ return PLATFORM_RULE_TYPES[platform];
181
+ }
182
+ /** Check if a type is valid for a given platform */
183
+ function isValidRuleType(platform, type) {
184
+ return PLATFORM_RULE_TYPES[platform].includes(type);
185
+ }
186
+ /** Get the configuration for a specific platform + type combination */
187
+ function getRuleTypeConfig(platform, type) {
188
+ const platformConfig = PLATFORMS[platform];
189
+ return platformConfig.types[type];
190
+ }
191
+ /** Get the install path for a rule, replacing {name} placeholder */
192
+ function getInstallPath(platform, type, name, location = "project") {
193
+ const config = getRuleTypeConfig(platform, type);
194
+ if (!config) return null;
195
+ const pathTemplate = location === "project" ? config.projectPath : config.globalPath;
196
+ if (!pathTemplate) return null;
197
+ return pathTemplate.replace("{name}", name);
198
+ }
199
+ /** Get platform configuration */
200
+ function getPlatformConfig(platform) {
201
+ return PLATFORMS[platform];
202
+ }
73
203
 
74
204
  //#endregion
75
205
  //#region src/platform/utils.ts
@@ -170,9 +300,12 @@ const bundledFileSchema = z.object({
170
300
  /**
171
301
  * Schema for what clients send to publish a preset.
172
302
  * Version is optional major version. Registry assigns full MAJOR.MINOR.
303
+ *
304
+ * Note: Clients send `name` (e.g., "my-preset"), and the registry defines the format of the slug.
305
+ * For example, a namespaced slug could be returned as "username/my-preset"
173
306
  */
174
307
  const presetPublishInputSchema = z.object({
175
- slug: z.string().trim().min(1),
308
+ name: slugSchema,
176
309
  platform: platformIdSchema,
177
310
  title: titleSchema,
178
311
  description: descriptionSchema,
@@ -187,9 +320,15 @@ const presetPublishInputSchema = z.object({
187
320
  });
188
321
  /**
189
322
  * Schema for what registries store and return.
190
- * Includes version (required) - full MAJOR.MINOR format assigned by registry.
323
+ * Includes full namespaced slug and version assigned by registry.
191
324
  */
192
- const presetBundleSchema = presetPublishInputSchema.omit({ version: true }).extend({ version: versionSchema });
325
+ const presetBundleSchema = presetPublishInputSchema.omit({
326
+ name: true,
327
+ version: true
328
+ }).extend({
329
+ slug: z.string().trim().min(1),
330
+ version: versionSchema
331
+ });
193
332
  const presetSchema = presetBundleSchema.omit({
194
333
  files: true,
195
334
  readmeContent: true,
@@ -235,7 +374,7 @@ const NAME_PATTERN = /^[a-z0-9-]+$/;
235
374
  * Directory name for bundle files in static registry output.
236
375
  * Used by `agentrules registry build` to structure output.
237
376
  */
238
- const STATIC_BUNDLE_DIR = "r";
377
+ const STATIC_BUNDLE_DIR = "registry";
239
378
  /**
240
379
  * Compute SHA-256 hash using Web Crypto API (works in browser and Node.js 15+)
241
380
  */
@@ -250,11 +389,11 @@ async function sha256(data) {
250
389
  */
251
390
  async function buildPresetPublishInput(options) {
252
391
  const { preset: presetInput, version } = options;
253
- if (!NAME_PATTERN.test(presetInput.slug)) throw new Error(`Invalid slug "${presetInput.slug}". Slugs must be lowercase kebab-case.`);
254
- const presetConfig = validatePresetConfig(presetInput.config, presetInput.slug);
392
+ if (!NAME_PATTERN.test(presetInput.name)) throw new Error(`Invalid name "${presetInput.name}". Names must be lowercase kebab-case.`);
393
+ const presetConfig = validatePresetConfig(presetInput.config, presetInput.name);
255
394
  const platform = presetConfig.platform;
256
- ensureKnownPlatform(platform, presetInput.slug);
257
- if (presetInput.files.length === 0) throw new Error(`Preset ${presetInput.slug} does not include any files.`);
395
+ ensureKnownPlatform(platform, presetInput.name);
396
+ if (presetInput.files.length === 0) throw new Error(`Preset ${presetInput.name} does not include any files.`);
258
397
  const files = await createBundledFilesFromInputs(presetInput.files);
259
398
  const installMessage = cleanInstallMessage(presetInput.installMessage);
260
399
  const features = presetConfig.features ?? [];
@@ -262,7 +401,7 @@ async function buildPresetPublishInput(options) {
262
401
  const licenseContent = presetInput.licenseContent?.trim() || void 0;
263
402
  const majorVersion = version ?? presetConfig.version;
264
403
  return {
265
- slug: presetInput.slug,
404
+ name: presetInput.name,
266
405
  platform,
267
406
  title: presetConfig.title,
268
407
  description: presetConfig.description,
@@ -286,11 +425,11 @@ async function buildPresetRegistry(options) {
286
425
  const entries = [];
287
426
  const bundles = [];
288
427
  for (const presetInput of options.presets) {
289
- if (!NAME_PATTERN.test(presetInput.slug)) throw new Error(`Invalid slug "${presetInput.slug}". Slugs must be lowercase kebab-case.`);
290
- const presetConfig = validatePresetConfig(presetInput.config, presetInput.slug);
428
+ if (!NAME_PATTERN.test(presetInput.name)) throw new Error(`Invalid name "${presetInput.name}". Names must be lowercase kebab-case.`);
429
+ const presetConfig = validatePresetConfig(presetInput.config, presetInput.name);
291
430
  const platform = presetConfig.platform;
292
- ensureKnownPlatform(platform, presetInput.slug);
293
- if (presetInput.files.length === 0) throw new Error(`Preset ${presetInput.slug} does not include any files.`);
431
+ ensureKnownPlatform(platform, presetInput.name);
432
+ if (presetInput.files.length === 0) throw new Error(`Preset ${presetInput.name} does not include any files.`);
294
433
  const files = await createBundledFilesFromInputs(presetInput.files);
295
434
  const totalSize = files.reduce((sum, file) => sum + file.size, 0);
296
435
  const installMessage = cleanInstallMessage(presetInput.installMessage);
@@ -299,9 +438,10 @@ async function buildPresetRegistry(options) {
299
438
  const licenseContent = presetInput.licenseContent?.trim() || void 0;
300
439
  const majorVersion = presetConfig.version ?? 1;
301
440
  const version = `${majorVersion}.0`;
441
+ const slug = presetInput.name;
302
442
  const entry = {
303
- name: encodeItemName(presetInput.slug, platform),
304
- slug: presetInput.slug,
443
+ name: encodeItemName(slug, platform),
444
+ slug,
305
445
  platform,
306
446
  title: presetConfig.title,
307
447
  version,
@@ -309,13 +449,13 @@ async function buildPresetRegistry(options) {
309
449
  tags: presetConfig.tags ?? [],
310
450
  license: presetConfig.license,
311
451
  features,
312
- bundleUrl: getBundlePath(bundleBase, presetInput.slug, platform, version),
452
+ bundleUrl: getBundlePath(bundleBase, slug, platform, version),
313
453
  fileCount: files.length,
314
454
  totalSize
315
455
  };
316
456
  entries.push(entry);
317
457
  const bundle = {
318
- slug: presetInput.slug,
458
+ slug,
319
459
  platform,
320
460
  title: presetConfig.title,
321
461
  version,
@@ -471,6 +611,61 @@ async function fetchBundle(bundleUrl) {
471
611
  }
472
612
  }
473
613
 
614
+ //#endregion
615
+ //#region src/rule/schema.ts
616
+ const NAME_REGEX = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
617
+ /**
618
+ * Schema for the rule name.
619
+ * This is what users provide when creating a rule.
620
+ */
621
+ const ruleNameSchema = z.string().trim().min(1, "Name is required").max(64, "Name must be 64 characters or less").regex(NAME_REGEX, "Must be lowercase alphanumeric with hyphens");
622
+ const ruleTitleSchema = z.string().trim().min(1, "Title is required").max(80, "Title must be 80 characters or less");
623
+ const ruleDescriptionSchema = z.string().trim().max(500, "Description must be 500 characters or less");
624
+ const rulePlatformSchema = z.enum(PLATFORM_IDS);
625
+ const ruleTypeSchema = z.string().trim().min(1).max(32);
626
+ const ruleContentSchema = z.string().min(1, "Content is required").max(1e5, "Content must be 100KB or less");
627
+ const ruleTagSchema = z.string().trim().min(1).max(32).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "Tags must be lowercase alphanumeric with single hyphens between words");
628
+ const ruleTagsSchema = z.array(ruleTagSchema).min(1, "At least 1 tag is required").max(10, "Maximum 10 tags allowed");
629
+ /** Common fields shared across all platform-type combinations */
630
+ const ruleCommonFields = {
631
+ name: ruleNameSchema,
632
+ title: ruleTitleSchema,
633
+ description: ruleDescriptionSchema.optional(),
634
+ content: ruleContentSchema,
635
+ tags: ruleTagsSchema
636
+ };
637
+ /**
638
+ * Discriminated union schema for platform + type combinations.
639
+ * Each platform has its own set of valid types.
640
+ */
641
+ const rulePlatformTypeSchema = z.discriminatedUnion("platform", [
642
+ z.object({
643
+ platform: z.literal("opencode"),
644
+ type: z.enum(PLATFORM_RULE_TYPES.opencode)
645
+ }),
646
+ z.object({
647
+ platform: z.literal("claude"),
648
+ type: z.enum(PLATFORM_RULE_TYPES.claude)
649
+ }),
650
+ z.object({
651
+ platform: z.literal("cursor"),
652
+ type: z.enum(PLATFORM_RULE_TYPES.cursor)
653
+ }),
654
+ z.object({
655
+ platform: z.literal("codex"),
656
+ type: z.enum(PLATFORM_RULE_TYPES.codex)
657
+ })
658
+ ]);
659
+ /** Schema for rule creation with discriminated union for platform+type */
660
+ const ruleCreateInputSchema = z.object(ruleCommonFields).and(rulePlatformTypeSchema);
661
+ const ruleUpdateInputSchema = z.object({
662
+ name: ruleNameSchema,
663
+ title: ruleTitleSchema.optional(),
664
+ description: ruleDescriptionSchema.optional(),
665
+ content: ruleContentSchema.optional(),
666
+ tags: ruleTagsSchema.optional()
667
+ });
668
+
474
669
  //#endregion
475
670
  //#region src/utils/diff.ts
476
671
  const DEFAULT_CONTEXT = 2;
@@ -495,4 +690,4 @@ function normalizeBundlePath(value) {
495
690
  }
496
691
 
497
692
  //#endregion
498
- export { AGENT_RULES_DIR, API_ENDPOINTS, COMMON_LICENSES, LATEST_VERSION, PLATFORMS, PLATFORM_IDS, PLATFORM_ID_TUPLE, PRESET_CONFIG_FILENAME, PRESET_SCHEMA_URL, STATIC_BUNDLE_DIR, buildPresetPublishInput, buildPresetRegistry, bundledFileSchema, cleanInstallMessage, createDiffPreview, decodeBundledFile, decodeUtf8, descriptionSchema, encodeItemName, encodeUtf8, fetchBundle, getPlatformFromDir, isLikelyText, isPlatformDir, isSupportedPlatform, licenseSchema, normalizeBundlePath, normalizePlatformInput, platformIdSchema, presetBundleSchema, presetConfigSchema, presetIndexSchema, presetPublishInputSchema, presetSchema, resolvePreset, slugSchema, tagSchema, tagsSchema, titleSchema, toPosixPath, toUint8Array, toUtf8String, validatePresetConfig, verifyBundledFileChecksum };
693
+ 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, encodeItemName, encodeUtf8, fetchBundle, getInstallPath, getPlatformConfig, getPlatformFromDir, getRuleTypeConfig, getValidRuleTypes, isLikelyText, isPlatformDir, isSupportedPlatform, isValidRuleType, licenseSchema, normalizeBundlePath, normalizePlatformInput, platformIdSchema, presetBundleSchema, presetConfigSchema, presetIndexSchema, presetPublishInputSchema, presetSchema, resolvePreset, ruleContentSchema, ruleCreateInputSchema, ruleDescriptionSchema, ruleNameSchema, rulePlatformSchema, rulePlatformTypeSchema, ruleTagSchema, ruleTagsSchema, ruleTitleSchema, ruleTypeSchema, ruleUpdateInputSchema, slugSchema, tagSchema, tagsSchema, titleSchema, toPosixPath, toUint8Array, toUtf8String, validatePresetConfig, verifyBundledFileChecksum };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentrules/core",
3
- "version": "0.0.10",
3
+ "version": "0.0.11",
4
4
  "author": "Brian Cheung <bcheung.dev@gmail.com> (https://github.com/bcheung)",
5
5
  "license": "MIT",
6
6
  "homepage": "https://agentrules.directory",