@agentrules/core 0.0.9 → 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 +310 -27
  2. package/dist/index.js +229 -34
  3. package/package.json +1 -1
package/dist/index.d.ts CHANGED
@@ -9,6 +9,8 @@ declare const platformIdSchema: z.ZodEnum<{
9
9
  }>;
10
10
  declare const titleSchema: z.ZodString;
11
11
  declare const descriptionSchema: z.ZodString;
12
+ declare const tagSchema: z.ZodString;
13
+ declare const tagsSchema: z.ZodArray<z.ZodString>;
12
14
  declare const slugSchema: z.ZodString;
13
15
  declare const COMMON_LICENSES: readonly ["MIT", "Apache-2.0", "GPL-3.0-only", "BSD-3-Clause", "ISC", "Unlicense"];
14
16
  type CommonLicense = (typeof COMMON_LICENSES)[number];
@@ -19,7 +21,7 @@ declare const presetConfigSchema: z.ZodObject<{
19
21
  title: z.ZodString;
20
22
  version: z.ZodOptional<z.ZodNumber>;
21
23
  description: z.ZodString;
22
- tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
24
+ tags: z.ZodArray<z.ZodString>;
23
25
  features: z.ZodOptional<z.ZodArray<z.ZodString>>;
24
26
  license: z.ZodString;
25
27
  platform: z.ZodEnum<{
@@ -40,9 +42,12 @@ declare const bundledFileSchema: z.ZodObject<{
40
42
  /**
41
43
  * Schema for what clients send to publish a preset.
42
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"
43
48
  */
44
49
  declare const presetPublishInputSchema: z.ZodObject<{
45
- slug: z.ZodString;
50
+ name: z.ZodString;
46
51
  platform: z.ZodEnum<{
47
52
  opencode: "opencode";
48
53
  codex: "codex";
@@ -67,11 +72,12 @@ declare const presetPublishInputSchema: z.ZodObject<{
67
72
  }, z.core.$strip>;
68
73
  /**
69
74
  * Schema for what registries store and return.
70
- * Includes version (required) - full MAJOR.MINOR format assigned by registry.
75
+ * Includes full namespaced slug and version assigned by registry.
71
76
  */
72
77
  declare const presetBundleSchema: z.ZodObject<{
73
78
  title: z.ZodString;
74
79
  description: z.ZodString;
80
+ tags: z.ZodArray<z.ZodString>;
75
81
  license: z.ZodString;
76
82
  platform: z.ZodEnum<{
77
83
  opencode: "opencode";
@@ -79,9 +85,7 @@ declare const presetBundleSchema: z.ZodObject<{
79
85
  claude: "claude";
80
86
  cursor: "cursor";
81
87
  }>;
82
- tags: z.ZodArray<z.ZodString>;
83
88
  features: z.ZodOptional<z.ZodArray<z.ZodString>>;
84
- slug: z.ZodString;
85
89
  licenseContent: z.ZodOptional<z.ZodString>;
86
90
  readmeContent: z.ZodOptional<z.ZodString>;
87
91
  installMessage: z.ZodOptional<z.ZodString>;
@@ -91,11 +95,13 @@ declare const presetBundleSchema: z.ZodObject<{
91
95
  checksum: z.ZodString;
92
96
  contents: z.ZodString;
93
97
  }, z.core.$strip>>;
98
+ slug: z.ZodString;
94
99
  version: z.ZodString;
95
100
  }, z.core.$strip>;
96
101
  declare const presetSchema: z.ZodObject<{
97
102
  title: z.ZodString;
98
103
  description: z.ZodString;
104
+ tags: z.ZodArray<z.ZodString>;
99
105
  license: z.ZodString;
100
106
  platform: z.ZodEnum<{
101
107
  opencode: "opencode";
@@ -104,7 +110,6 @@ declare const presetSchema: z.ZodObject<{
104
110
  cursor: "cursor";
105
111
  }>;
106
112
  version: z.ZodString;
107
- tags: z.ZodArray<z.ZodString>;
108
113
  features: z.ZodOptional<z.ZodArray<z.ZodString>>;
109
114
  slug: z.ZodString;
110
115
  name: z.ZodString;
@@ -115,6 +120,7 @@ declare const presetSchema: z.ZodObject<{
115
120
  declare const presetIndexSchema: z.ZodRecord<z.ZodString, z.ZodObject<{
116
121
  title: z.ZodString;
117
122
  description: z.ZodString;
123
+ tags: z.ZodArray<z.ZodString>;
118
124
  license: z.ZodString;
119
125
  platform: z.ZodEnum<{
120
126
  opencode: "opencode";
@@ -123,7 +129,6 @@ declare const presetIndexSchema: z.ZodRecord<z.ZodString, z.ZodObject<{
123
129
  cursor: "cursor";
124
130
  }>;
125
131
  version: z.ZodString;
126
- tags: z.ZodArray<z.ZodString>;
127
132
  features: z.ZodOptional<z.ZodArray<z.ZodString>>;
128
133
  slug: z.ZodString;
129
134
  name: z.ZodString;
@@ -135,29 +140,192 @@ declare const presetIndexSchema: z.ZodRecord<z.ZodString, z.ZodObject<{
135
140
  //#endregion
136
141
  //#region src/platform/types.d.ts
137
142
  /**
138
- * Single source of truth for platform IDs.
139
- * Add new platforms here - types and config will follow.
143
+ * Platform and rule type definitions.
140
144
  */
141
145
  declare const PLATFORM_ID_TUPLE: readonly ["opencode", "codex", "claude", "cursor"];
142
- /** Union type of supported platform IDs, derived from PLATFORM_ID_TUPLE */
146
+ /** Union type of supported platform IDs */
143
147
  type PlatformId = (typeof PLATFORM_ID_TUPLE)[number];
144
- /** Configuration for a platform's directory paths */
145
- type PlatformConfig = {
146
- /** 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") */
147
176
  projectDir: string;
148
- /** Path for global installs (e.g., "~/.config/opencode") */
149
- 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";
150
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"];
151
205
 
152
206
  //#endregion
153
207
  //#region src/platform/config.d.ts
154
- /** List of supported platform IDs as a readonly tuple */
155
- declare const PLATFORM_IDS: readonly ["opencode", "codex", "claude", "cursor"];
208
+ declare const PLATFORM_IDS: [PlatformId, ...PlatformId[]];
156
209
  /**
157
- * Platform-specific configuration.
158
- * Single source of truth for all platform paths.
210
+ * Platform configuration including supported rule types and install paths.
159
211
  */
160
- 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;
161
329
 
162
330
  //#endregion
163
331
  //#region src/platform/utils.d.ts
@@ -200,9 +368,12 @@ type BundledFile = {
200
368
  /**
201
369
  * What clients send to publish a preset.
202
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"
203
374
  */
204
375
  type PresetPublishInput = {
205
- slug: string;
376
+ name: string;
206
377
  platform: PlatformId;
207
378
  title: string;
208
379
  description: string;
@@ -218,9 +389,11 @@ type PresetPublishInput = {
218
389
  };
219
390
  /**
220
391
  * What registries store and return.
221
- * Includes version (required) - full MAJOR.MINOR format assigned by registry.
392
+ * Includes full namespaced slug and version assigned by registry.
222
393
  */
223
- type PresetBundle = Omit<PresetPublishInput, "version"> & {
394
+ type PresetBundle = Omit<PresetPublishInput, "name" | "version"> & {
395
+ /** Full namespaced slug (e.g., "username/my-preset") */
396
+ slug: string;
224
397
  /** Full version in MAJOR.MINOR format (e.g., "1.3", "2.1") */
225
398
  version: string;
226
399
  };
@@ -244,7 +417,7 @@ type PresetFileInput = {
244
417
  contents: ArrayBuffer | ArrayBufferView | string;
245
418
  };
246
419
  type PresetInput = {
247
- slug: string;
420
+ name: string;
248
421
  config: PresetConfig;
249
422
  files: PresetFileInput[];
250
423
  /** Install message from INSTALL.txt file */
@@ -259,7 +432,7 @@ type PresetInput = {
259
432
  * Directory name for bundle files in static registry output.
260
433
  * Used by `agentrules registry build` to structure output.
261
434
  */
262
- declare const STATIC_BUNDLE_DIR = "r";
435
+ declare const STATIC_BUNDLE_DIR = "registry";
263
436
  /**
264
437
  * Options for building a PresetPublishInput (for CLI publish command).
265
438
  */
@@ -349,12 +522,20 @@ declare const PRESET_SCHEMA_URL = "https://agentrules.directory/schema/agentrule
349
522
  declare const LATEST_VERSION = "latest";
350
523
  /**
351
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.
352
533
  */
353
534
  declare const API_ENDPOINTS: {
354
535
  /** Preset endpoints */
355
536
  readonly presets: {
356
537
  /** Base path for preset operations */
357
- readonly base: "api/presets";
538
+ readonly base: "api/preset";
358
539
  /** Get preset by slug, platform, and version (defaults to "latest") */
359
540
  readonly get: (slug: string, platform: string, version?: string) => string;
360
541
  /** Unpublish preset version */
@@ -369,10 +550,112 @@ declare const API_ENDPOINTS: {
369
550
  /** Device token exchange */
370
551
  readonly deviceToken: "api/auth/device/token";
371
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
+ };
372
560
  };
373
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
+
374
656
  //#endregion
375
657
  //#region src/utils/diff.d.ts
658
+ /** Re-export platform-rule types for convenience */
376
659
  type DiffPreviewOptions = {
377
660
  context?: number;
378
661
  maxLines?: number;
@@ -395,4 +678,4 @@ declare function toUint8Array(payload: ArrayBuffer | ArrayBufferView): Uint8Arra
395
678
  declare function normalizeBundlePath(value: string): string;
396
679
 
397
680
  //#endregion
398
- 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, 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
@@ -154,7 +284,7 @@ const presetConfigSchema = z.object({
154
284
  title: titleSchema,
155
285
  version: majorVersionSchema.optional(),
156
286
  description: descriptionSchema,
157
- tags: tagsSchema.optional(),
287
+ tags: tagsSchema,
158
288
  features: featuresSchema.optional(),
159
289
  license: licenseSchema,
160
290
  platform: platformIdSchema,
@@ -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, 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.9",
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",