@agentrules/core 0.0.6 → 0.0.7

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.
package/README.md CHANGED
@@ -2,28 +2,49 @@
2
2
 
3
3
  Shared types and utilities for the AGENT_RULES ecosystem.
4
4
 
5
+ **This package is for developers building custom registries or alternative clients.** If you just want to install or publish presets, use the [CLI](../cli) instead.
6
+
5
7
  ## Installation
6
8
 
7
9
  ```bash
8
10
  npm install @agentrules/core
9
11
  ```
10
12
 
11
- ## Features
13
+ ## What's Included
14
+
15
+ | Module | Description |
16
+ |--------|-------------|
17
+ | **Types & Schemas** | TypeScript types and Zod schemas for presets, bundles, configs |
18
+ | **Registry Builder** | Build registry artifacts from preset inputs |
19
+ | **Registry Client** | Fetch and resolve presets from registries |
20
+ | **Bundle Utilities** | Encode/decode bundles, verify checksums |
21
+ | **Platform Config** | Platform IDs and directory paths |
12
22
 
13
- - **Types** - TypeScript definitions for presets, bundles, and registry entries
14
- - **Validation** - Zod schemas for validating `agentrules.json` configs
15
- - **Registry Builder** - Transform preset inputs into registry JSON artifacts
16
- - **Bundle Utilities** - Checksum verification, encoding/decoding helpers
17
- - **Diff Utilities** - Generate previews for file conflicts
23
+ This package contains **pure functions with no environment assumptions**. It doesn't touch the file system or make network requests directly — that's left to the consumer (like the CLI).
18
24
 
19
25
  ## Usage
20
26
 
21
- ### Building Registry Data
27
+ ### Validating Preset Config
22
28
 
23
29
  ```ts
24
- import { buildRegistryData } from "@agentrules/core";
30
+ import { presetConfigSchema, validatePresetConfig } from "@agentrules/core";
31
+
32
+ // Using Zod schema directly
33
+ const result = presetConfigSchema.safeParse(jsonData);
34
+ if (!result.success) {
35
+ console.error(result.error.issues);
36
+ }
37
+
38
+ // Or use the helper (throws on error)
39
+ const config = validatePresetConfig(jsonData, "my-preset");
40
+ ```
41
+
42
+ ### Building Registry Artifacts
25
43
 
26
- const result = await buildRegistryData({
44
+ ```ts
45
+ import { buildPresetRegistry } from "@agentrules/core";
46
+
47
+ const result = await buildPresetRegistry({
27
48
  presets: [
28
49
  {
29
50
  slug: "my-preset",
@@ -32,48 +53,36 @@ const result = await buildRegistryData({
32
53
  title: "My Preset",
33
54
  version: 1,
34
55
  description: "A helpful preset",
56
+ tags: ["starter"],
35
57
  license: "MIT",
36
58
  platform: "opencode",
37
- path: ".opencode",
59
+ path: "files",
38
60
  },
39
61
  files: [
40
62
  { path: "AGENT_RULES.md", contents: "# Rules\n" },
41
- { path: "config.json", contents: '{"key": "value"}' },
42
63
  ],
43
64
  },
44
65
  ],
45
66
  });
46
67
 
47
- // result.entries → array for registry.json
48
- // result.index → object for registry.index.json
49
- // result.bundles → bundle payloads
50
- ```
51
-
52
- ### Validating Preset Config
53
-
54
- ```ts
55
- import { validatePresetConfig, presetConfigSchema } from "@agentrules/core";
56
-
57
- // Quick validation (throws on error)
58
- const config = validatePresetConfig(jsonData, "my-preset");
59
-
60
- // Zod schema for custom handling
61
- const result = presetConfigSchema.safeParse(jsonData);
62
- if (!result.success) {
63
- console.error(result.error.issues);
64
- }
68
+ // result.entries → Preset[] for registry listing
69
+ // result.index → PresetIndex for lookups
70
+ // result.bundles → PresetBundle[] with encoded files
65
71
  ```
66
72
 
67
- ### Fetching from Registry
73
+ ### Fetching from a Registry
68
74
 
69
75
  ```ts
70
76
  import { resolvePreset, fetchBundle } from "@agentrules/core";
71
77
 
72
- const { entry, bundleUrl } = await resolvePreset(
78
+ // Resolve a preset (gets metadata and bundle URL)
79
+ const { preset, bundleUrl } = await resolvePreset(
73
80
  "https://agentrules.directory/",
74
- "agentic-dev-starter",
81
+ "my-preset",
75
82
  "opencode"
76
83
  );
84
+
85
+ // Fetch the bundle
77
86
  const bundle = await fetchBundle(bundleUrl);
78
87
  ```
79
88
 
@@ -87,45 +96,71 @@ import {
87
96
  } from "@agentrules/core";
88
97
 
89
98
  for (const file of bundle.files) {
99
+ // Decode base64 contents
90
100
  const data = decodeBundledFile(file);
101
+
102
+ // Verify integrity
91
103
  await verifyBundledFileChecksum(file, data);
92
104
 
105
+ // Check if it's text or binary
93
106
  if (isLikelyText(data)) {
94
- console.log(`Text file: ${file.path}`);
107
+ const text = new TextDecoder().decode(data);
95
108
  }
96
109
  }
97
110
  ```
98
111
 
99
- ## Preset Config Format
100
-
101
- Presets use `agentrules.json`:
102
-
103
- ```json
104
- {
105
- "$schema": "https://agentrules.directory/schema/agentrules.json",
106
- "name": "my-preset",
107
- "title": "My Preset",
108
- "version": 1,
109
- "description": "Description here",
110
- "license": "MIT",
111
- "tags": ["starter", "typescript"],
112
- "features": ["Feature 1", "Feature 2"],
113
- "platform": "opencode",
114
- "path": "files"
115
- }
112
+ ### Platform Configuration
113
+
114
+ ```ts
115
+ import { PLATFORMS, PLATFORM_IDS } from "@agentrules/core";
116
+
117
+ // All supported platform IDs
118
+ console.log(PLATFORM_IDS); // ["opencode", "claude", "cursor", "codex"]
119
+
120
+ // Get paths for a platform
121
+ const opencode = PLATFORMS.opencode;
122
+ console.log(opencode.projectDir); // ".opencode"
123
+ console.log(opencode.globalDir); // "~/.config/opencode"
116
124
  ```
117
125
 
118
- ### Versioning
126
+ ## Types
119
127
 
120
- Presets use two-segment versioning (`MAJOR.MINOR`):
121
- - **Major version**: Set by the publisher in config (defaults to 1)
122
- - **Minor version**: Auto-incremented by the registry on each publish
128
+ Key types exported:
123
129
 
124
- ## Development
130
+ ```ts
131
+ import type {
132
+ // Preset configuration (agentrules.json)
133
+ PresetConfig,
134
+
135
+ // What clients send to publish
136
+ PresetPublishInput,
137
+
138
+ // What registries store and return
139
+ PresetBundle,
140
+ Preset,
141
+ PresetIndex,
142
+
143
+ // Bundle file structure
144
+ BundledFile,
145
+
146
+ // Platform types
147
+ PlatformId,
148
+ PlatformConfig,
149
+ } from "@agentrules/core";
150
+ ```
125
151
 
126
- ```bash
127
- bun install
128
- bun run build # build with tsdown
129
- bun run test # run tests
130
- bun run typecheck # type checking
152
+ ## Schemas
153
+
154
+ Zod schemas for validation:
155
+
156
+ ```ts
157
+ import {
158
+ presetConfigSchema,
159
+ presetBundleSchema,
160
+ presetPublishInputSchema,
161
+ platformIdSchema,
162
+ slugSchema,
163
+ titleSchema,
164
+ descriptionSchema,
165
+ } from "@agentrules/core";
131
166
  ```
package/dist/index.d.ts CHANGED
@@ -1,112 +1,6 @@
1
1
  import { z } from "zod";
2
2
 
3
- //#region src/types/platform.d.ts
4
-
5
- /**
6
- * Single source of truth for platform IDs.
7
- * Add new platforms here - types and config will follow.
8
- */
9
- declare const PLATFORM_ID_TUPLE: readonly ["opencode", "codex", "claude", "cursor"];
10
- /** Union type of supported platform IDs, derived from PLATFORM_ID_TUPLE */
11
- type PlatformId = (typeof PLATFORM_ID_TUPLE)[number];
12
- /** List of supported platform IDs as a readonly tuple */
13
- declare const PLATFORM_IDS: readonly ["opencode", "codex", "claude", "cursor"];
14
- type PlatformConfig = {
15
- /** Directory name for project installs (e.g., ".opencode") */
16
- projectDir: string;
17
- /** Path for global installs (e.g., "~/.config/opencode") */
18
- globalDir: string;
19
- };
20
- /**
21
- * Platform-specific configuration.
22
- * Single source of truth for all platform paths.
23
- */
24
- declare const PLATFORMS: Record<PlatformId, PlatformConfig>;
25
- /**
26
- * Convention: preset files under this directory map to the platform config directory.
27
- * e.g., `config/agent.md` → `.opencode/agent.md` (project) or `agent.md` (global)
28
- */
29
- declare const CONFIG_DIR_NAME = "config";
30
- declare function isSupportedPlatform(value: string): value is PlatformId;
31
- declare function normalizePlatformInput(value: string): PlatformId;
32
- //#endregion
33
- //#region src/types/definitions.d.ts
34
- type PresetConfig = {
35
- $schema?: string;
36
- name: string;
37
- title: string;
38
- version?: number;
39
- description: string;
40
- tags?: string[];
41
- features?: string[];
42
- license: string;
43
- platform: PlatformId;
44
- /** Path to config files. Defaults to platform's projectDir (e.g., ".claude") */
45
- path?: string;
46
- };
47
- type BundledFile = {
48
- path: string;
49
- /** File size in bytes */
50
- size: number;
51
- checksum: string;
52
- contents: string;
53
- };
54
- /**
55
- * What clients send to publish a preset.
56
- * Version is optional major version. Registry assigns full MAJOR.MINOR.
57
- */
58
- type PublishInput = {
59
- slug: string;
60
- platform: PlatformId;
61
- title: string;
62
- description: string;
63
- tags: string[];
64
- license: string;
65
- licenseContent?: string;
66
- readmeContent?: string;
67
- features?: string[];
68
- installMessage?: string;
69
- files: BundledFile[];
70
- /** Major version. Defaults to 1 if not specified. */
71
- version?: number;
72
- };
73
- /**
74
- * What registries store and return.
75
- * Includes version (required) - full MAJOR.MINOR format assigned by registry.
76
- */
77
- type RegistryBundle = Omit<PublishInput, "version"> & {
78
- /** Full version in MAJOR.MINOR format (e.g., "1.3", "2.1") */
79
- version: string;
80
- };
81
- type RegistryEntry = {
82
- name: string;
83
- slug: string;
84
- platform: PlatformId;
85
- title: string;
86
- version: string;
87
- description: string;
88
- tags: string[];
89
- license: string;
90
- features?: string[];
91
- bundleUrl: string;
92
- fileCount: number;
93
- totalSize: number;
94
- };
95
- type RegistryIndex = Record<string, RegistryEntry>;
96
- type RegistryFileInput = {
97
- path: string;
98
- contents: ArrayBuffer | ArrayBufferView | string;
99
- };
100
- type RegistryPresetInput = {
101
- slug: string;
102
- config: PresetConfig;
103
- files: RegistryFileInput[];
104
- /** Install message from INSTALL.txt file */
105
- installMessage?: string;
106
- readmeContent?: string;
107
- licenseContent?: string;
108
- }; //#endregion
109
- //#region src/types/schema.d.ts
3
+ //#region src/preset/schema.d.ts
110
4
  declare const platformIdSchema: z.ZodEnum<{
111
5
  opencode: "opencode";
112
6
  codex: "codex";
@@ -115,18 +9,10 @@ declare const platformIdSchema: z.ZodEnum<{
115
9
  }>;
116
10
  declare const titleSchema: z.ZodString;
117
11
  declare const descriptionSchema: z.ZodString;
118
- /** Validate a title string and return error message if invalid, undefined if valid */
119
- declare function validateTitle(value: string): string | undefined;
120
- /** Validate a description string and return error message if invalid, undefined if valid */
121
- declare function validateDescription(value: string): string | undefined;
122
12
  declare const slugSchema: z.ZodString;
123
- /** Validate a slug string and return error message if invalid, undefined if valid */
124
- declare function validateSlug(value: string): string | undefined;
125
13
  declare const COMMON_LICENSES: readonly ["MIT", "Apache-2.0", "GPL-3.0-only", "BSD-3-Clause", "ISC", "Unlicense"];
126
14
  type CommonLicense = (typeof COMMON_LICENSES)[number];
127
15
  declare const licenseSchema: z.ZodString;
128
- /** Validate a license string and return error message if invalid, undefined if valid */
129
- declare function validateLicense(value: string): string | undefined;
130
16
  declare const presetConfigSchema: z.ZodObject<{
131
17
  $schema: z.ZodOptional<z.ZodString>;
132
18
  name: z.ZodString;
@@ -154,7 +40,7 @@ declare const bundledFileSchema: z.ZodObject<{
154
40
  * Schema for what clients send to publish a preset.
155
41
  * Version is optional major version. Registry assigns full MAJOR.MINOR.
156
42
  */
157
- declare const publishInputSchema: z.ZodObject<{
43
+ declare const presetPublishInputSchema: z.ZodObject<{
158
44
  slug: z.ZodString;
159
45
  platform: z.ZodEnum<{
160
46
  opencode: "opencode";
@@ -182,7 +68,7 @@ declare const publishInputSchema: z.ZodObject<{
182
68
  * Schema for what registries store and return.
183
69
  * Includes version (required) - full MAJOR.MINOR format assigned by registry.
184
70
  */
185
- declare const registryBundleSchema: z.ZodObject<{
71
+ declare const presetBundleSchema: z.ZodObject<{
186
72
  title: z.ZodString;
187
73
  description: z.ZodString;
188
74
  license: z.ZodString;
@@ -206,7 +92,7 @@ declare const registryBundleSchema: z.ZodObject<{
206
92
  }, z.core.$strip>>;
207
93
  version: z.ZodString;
208
94
  }, z.core.$strip>;
209
- declare const registryEntrySchema: z.ZodObject<{
95
+ declare const presetSchema: z.ZodObject<{
210
96
  title: z.ZodString;
211
97
  description: z.ZodString;
212
98
  license: z.ZodString;
@@ -225,7 +111,7 @@ declare const registryEntrySchema: z.ZodObject<{
225
111
  fileCount: z.ZodNumber;
226
112
  totalSize: z.ZodNumber;
227
113
  }, z.core.$strip>;
228
- declare const registryIndexSchema: z.ZodRecord<z.ZodString, z.ZodObject<{
114
+ declare const presetIndexSchema: z.ZodRecord<z.ZodString, z.ZodObject<{
229
115
  title: z.ZodString;
230
116
  description: z.ZodString;
231
117
  license: z.ZodString;
@@ -245,6 +131,121 @@ declare const registryIndexSchema: z.ZodRecord<z.ZodString, z.ZodObject<{
245
131
  totalSize: z.ZodNumber;
246
132
  }, z.core.$strip>>;
247
133
 
134
+ //#endregion
135
+ //#region src/platform/types.d.ts
136
+ /**
137
+ * Single source of truth for platform IDs.
138
+ * Add new platforms here - types and config will follow.
139
+ */
140
+ declare const PLATFORM_ID_TUPLE: readonly ["opencode", "codex", "claude", "cursor"];
141
+ /** Union type of supported platform IDs, derived from PLATFORM_ID_TUPLE */
142
+ type PlatformId = (typeof PLATFORM_ID_TUPLE)[number];
143
+ /** Configuration for a platform's directory paths */
144
+ type PlatformConfig = {
145
+ /** Directory name for project installs (e.g., ".opencode") */
146
+ projectDir: string;
147
+ /** Path for global installs (e.g., "~/.config/opencode") */
148
+ globalDir: string;
149
+ };
150
+
151
+ //#endregion
152
+ //#region src/platform/config.d.ts
153
+ /** List of supported platform IDs as a readonly tuple */
154
+ declare const PLATFORM_IDS: readonly ["opencode", "codex", "claude", "cursor"];
155
+ /**
156
+ * Platform-specific configuration.
157
+ * Single source of truth for all platform paths.
158
+ */
159
+ declare const PLATFORMS: Record<PlatformId, PlatformConfig>;
160
+ /**
161
+ * Convention: preset files under this directory map to the platform config directory.
162
+ * e.g., `config/agent.md` → `.opencode/agent.md` (project) or `agent.md` (global)
163
+ */
164
+ declare const CONFIG_DIR_NAME = "config";
165
+
166
+ //#endregion
167
+ //#region src/platform/utils.d.ts
168
+ declare function isSupportedPlatform(value: string): value is PlatformId;
169
+ declare function normalizePlatformInput(value: string): PlatformId;
170
+
171
+ //#endregion
172
+ //#region src/preset/types.d.ts
173
+ type PresetConfig = {
174
+ $schema?: string;
175
+ name: string;
176
+ title: string;
177
+ version?: number;
178
+ description: string;
179
+ tags?: string[];
180
+ features?: string[];
181
+ license: string;
182
+ platform: PlatformId;
183
+ /** Path to config files. Defaults to platform's projectDir (e.g., ".claude") */
184
+ path?: string;
185
+ };
186
+ type BundledFile = {
187
+ path: string;
188
+ /** File size in bytes */
189
+ size: number;
190
+ checksum: string;
191
+ contents: string;
192
+ };
193
+ /**
194
+ * What clients send to publish a preset.
195
+ * Version is optional major version. Registry assigns full MAJOR.MINOR.
196
+ */
197
+ type PresetPublishInput = {
198
+ slug: string;
199
+ platform: PlatformId;
200
+ title: string;
201
+ description: string;
202
+ tags: string[];
203
+ license: string;
204
+ licenseContent?: string;
205
+ readmeContent?: string;
206
+ features?: string[];
207
+ installMessage?: string;
208
+ files: BundledFile[];
209
+ /** Major version. Defaults to 1 if not specified. */
210
+ version?: number;
211
+ };
212
+ /**
213
+ * What registries store and return.
214
+ * Includes version (required) - full MAJOR.MINOR format assigned by registry.
215
+ */
216
+ type PresetBundle = Omit<PresetPublishInput, "version"> & {
217
+ /** Full version in MAJOR.MINOR format (e.g., "1.3", "2.1") */
218
+ version: string;
219
+ };
220
+ type Preset = {
221
+ name: string;
222
+ slug: string;
223
+ platform: PlatformId;
224
+ title: string;
225
+ version: string;
226
+ description: string;
227
+ tags: string[];
228
+ license: string;
229
+ features?: string[];
230
+ bundleUrl: string;
231
+ fileCount: number;
232
+ totalSize: number;
233
+ };
234
+ type PresetIndex = Record<string, Preset>;
235
+ type PresetFileInput = {
236
+ path: string;
237
+ contents: ArrayBuffer | ArrayBufferView | string;
238
+ };
239
+ type PresetInput = {
240
+ slug: string;
241
+ config: PresetConfig;
242
+ files: PresetFileInput[];
243
+ /** Install message from INSTALL.txt file */
244
+ installMessage?: string;
245
+ readmeContent?: string;
246
+ licenseContent?: string;
247
+ };
248
+
248
249
  //#endregion
249
250
  //#region src/builder/registry.d.ts
250
251
  /**
@@ -253,23 +254,23 @@ declare const registryIndexSchema: z.ZodRecord<z.ZodString, z.ZodObject<{
253
254
  */
254
255
  declare const STATIC_BUNDLE_DIR = "r";
255
256
  /**
256
- * Options for building a PublishInput (for CLI publish command).
257
+ * Options for building a PresetPublishInput (for CLI publish command).
257
258
  */
258
- type BuildPublishInputOptions = {
259
- preset: RegistryPresetInput;
259
+ type BuildPresetPublishInputOptions = {
260
+ preset: PresetInput;
260
261
  /** Major version. Defaults to 1 if not specified. */
261
262
  version?: number;
262
263
  };
263
264
  /**
264
- * Builds a PublishInput from preset input.
265
+ * Builds a PresetPublishInput from preset input.
265
266
  * Used by CLI to prepare data for publishing to a registry.
266
267
  */
267
- declare function buildPublishInput(options: BuildPublishInputOptions): Promise<PublishInput>;
268
+ declare function buildPresetPublishInput(options: BuildPresetPublishInputOptions): Promise<PresetPublishInput>;
268
269
  /**
269
270
  * Options for building a static registry.
270
271
  */
271
- type BuildRegistryDataOptions = {
272
- presets: RegistryPresetInput[];
272
+ type BuildPresetRegistryOptions = {
273
+ presets: PresetInput[];
273
274
  /**
274
275
  * Optional base path or URL prefix for bundle locations.
275
276
  * Format: {bundleBase}/{STATIC_BUNDLE_DIR}/{slug}/{platform}
@@ -277,17 +278,17 @@ type BuildRegistryDataOptions = {
277
278
  */
278
279
  bundleBase?: string;
279
280
  };
280
- type BuildRegistryDataResult = {
281
- entries: RegistryEntry[];
282
- index: RegistryIndex;
283
- bundles: RegistryBundle[];
281
+ type BuildPresetRegistryResult = {
282
+ entries: Preset[];
283
+ index: PresetIndex;
284
+ bundles: PresetBundle[];
284
285
  };
285
286
  /**
286
287
  * Builds a static registry with entries, index, and bundles.
287
288
  * Used for building static registry files (e.g., community-presets).
288
289
  * Each preset uses its version from config (default: major 1, minor 0).
289
290
  */
290
- declare function buildRegistryData(options: BuildRegistryDataOptions): Promise<BuildRegistryDataResult>;
291
+ declare function buildPresetRegistry(options: BuildPresetRegistryOptions): Promise<BuildPresetRegistryResult>;
291
292
 
292
293
  //#endregion
293
294
  //#region src/builder/utils.d.ts
@@ -308,7 +309,7 @@ declare function toUtf8String(payload: ArrayBuffer | ArrayBufferView): string;
308
309
  * Resolved preset with absolute bundle URL
309
310
  */
310
311
  type ResolvedPreset = {
311
- entry: RegistryEntry;
312
+ preset: Preset;
312
313
  bundleUrl: string;
313
314
  };
314
315
  /**
@@ -324,7 +325,7 @@ declare function resolvePreset(baseUrl: string, slug: string, platform: Platform
324
325
  /**
325
326
  * Fetches a bundle from an absolute URL or resolves it relative to the registry.
326
327
  */
327
- declare function fetchBundle(bundleUrl: string): Promise<RegistryBundle>;
328
+ declare function fetchBundle(bundleUrl: string): Promise<PresetBundle>;
328
329
 
329
330
  //#endregion
330
331
  //#region src/constants.d.ts
@@ -345,8 +346,8 @@ declare const API_ENDPOINTS: {
345
346
  readonly presets: {
346
347
  /** Base path for preset operations */
347
348
  readonly base: "api/presets";
348
- /** Get preset entry by version (defaults to "latest") */
349
- readonly entry: (slug: string, platform: string, version?: string) => string;
349
+ /** Get preset by slug, platform, and version (defaults to "latest") */
350
+ readonly get: (slug: string, platform: string, version?: string) => string;
350
351
  /** Unpublish preset version */
351
352
  readonly unpublish: (slug: string, platform: string, version: string) => string;
352
353
  };
@@ -383,4 +384,4 @@ declare function normalizePathFragment(value?: string): string | undefined;
383
384
  declare function maybeStripPrefix(pathInput: string, prefix?: string): string;
384
385
 
385
386
  //#endregion
386
- export { API_ENDPOINTS, BuildPublishInputOptions, BuildRegistryDataOptions, BuildRegistryDataResult, BundledFile, COMMON_LICENSES, CONFIG_DIR_NAME, CommonLicense, DiffPreviewOptions, LATEST_VERSION, PLATFORMS, PLATFORM_IDS, PRESET_CONFIG_FILENAME, PRESET_SCHEMA_URL, PlatformId, PresetConfig, PublishInput, RegistryBundle, RegistryEntry, RegistryFileInput, RegistryIndex, RegistryPresetInput, ResolvedPreset, STATIC_BUNDLE_DIR, buildPublishInput, buildRegistryData, bundledFileSchema, cleanInstallMessage, createDiffPreview, decodeBundledFile, decodeUtf8, descriptionSchema, encodeItemName, encodeUtf8, fetchBundle, isLikelyText, isSupportedPlatform, licenseSchema, maybeStripPrefix, normalizeBundlePath, normalizePathFragment, normalizePlatformInput, platformIdSchema, presetConfigSchema, publishInputSchema, registryBundleSchema, registryEntrySchema, registryIndexSchema, resolvePreset, slugSchema, titleSchema, toPosixPath, toUint8Array, toUtf8String, validateDescription, validateLicense, validatePresetConfig, validateSlug, validateTitle, verifyBundledFileChecksum };
387
+ export { API_ENDPOINTS, BuildPresetPublishInputOptions, BuildPresetRegistryOptions, BuildPresetRegistryResult, BundledFile, COMMON_LICENSES, CONFIG_DIR_NAME, 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, isLikelyText, isSupportedPlatform, licenseSchema, maybeStripPrefix, normalizeBundlePath, normalizePathFragment, normalizePlatformInput, platformIdSchema, presetBundleSchema, presetConfigSchema, presetIndexSchema, presetPublishInputSchema, presetSchema, resolvePreset, slugSchema, titleSchema, toPosixPath, toUint8Array, toUtf8String, validatePresetConfig, verifyBundledFileChecksum };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { z } from "zod";
1
+ import { ZodError, z } from "zod";
2
2
  import { createTwoFilesPatch } from "diff";
3
3
 
4
4
  //#region src/constants.ts
@@ -19,7 +19,7 @@ const LATEST_VERSION = "latest";
19
19
  const API_ENDPOINTS = {
20
20
  presets: {
21
21
  base: `${API_PATH}/presets`,
22
- entry: (slug, platform, version = LATEST_VERSION) => `${API_PATH}/presets/${encodeURIComponent(slug)}/${encodeURIComponent(platform)}/${encodeURIComponent(version)}`,
22
+ get: (slug, platform, version = LATEST_VERSION) => `${API_PATH}/presets/${encodeURIComponent(slug)}/${encodeURIComponent(platform)}/${encodeURIComponent(version)}`,
23
23
  unpublish: (slug, platform, version) => `${API_PATH}/presets/${encodeURIComponent(slug)}/${encodeURIComponent(platform)}/${encodeURIComponent(version)}`
24
24
  },
25
25
  auth: {
@@ -30,7 +30,7 @@ const API_ENDPOINTS = {
30
30
  };
31
31
 
32
32
  //#endregion
33
- //#region src/types/platform.ts
33
+ //#region src/platform/types.ts
34
34
  /**
35
35
  * Single source of truth for platform IDs.
36
36
  * Add new platforms here - types and config will follow.
@@ -41,6 +41,9 @@ const PLATFORM_ID_TUPLE = [
41
41
  "claude",
42
42
  "cursor"
43
43
  ];
44
+
45
+ //#endregion
46
+ //#region src/platform/config.ts
44
47
  /** List of supported platform IDs as a readonly tuple */
45
48
  const PLATFORM_IDS = PLATFORM_ID_TUPLE;
46
49
  /**
@@ -70,6 +73,9 @@ const PLATFORMS = {
70
73
  * e.g., `config/agent.md` → `.opencode/agent.md` (project) or `agent.md` (global)
71
74
  */
72
75
  const CONFIG_DIR_NAME = "config";
76
+
77
+ //#endregion
78
+ //#region src/platform/utils.ts
73
79
  function isSupportedPlatform(value) {
74
80
  return PLATFORM_ID_TUPLE.includes(value);
75
81
  }
@@ -80,44 +86,42 @@ function normalizePlatformInput(value) {
80
86
  }
81
87
 
82
88
  //#endregion
83
- //#region src/types/schema.ts
84
- const VERSION_REGEX = /^[1-9]\d*\.\d+$/;
85
- const platformIdSchema = z.enum(PLATFORM_IDS);
86
- const titleSchema = z.string().trim().min(1).max(120);
87
- const descriptionSchema = z.string().trim().min(1).max(500);
88
- /** Validate a title string and return error message if invalid, undefined if valid */
89
- function validateTitle(value) {
90
- const trimmed = value.trim();
91
- if (!trimmed) return "Title is required";
92
- if (trimmed.length > 120) return "Title must be 120 characters or less";
93
- return;
89
+ //#region src/utils/encoding.ts
90
+ function toPosixPath(pathValue) {
91
+ return pathValue.split("\\").join("/");
94
92
  }
95
- /** Validate a description string and return error message if invalid, undefined if valid */
96
- function validateDescription(value) {
97
- const trimmed = value.trim();
98
- if (!trimmed) return "Description is required";
99
- if (trimmed.length > 500) return "Description must be 500 characters or less";
100
- return;
93
+ function encodeUtf8(value) {
94
+ if (typeof Buffer !== "undefined") return new Uint8Array(Buffer.from(value, "utf8"));
95
+ return new TextEncoder().encode(value);
101
96
  }
97
+ function decodeUtf8(payload) {
98
+ const bytes = toUint8Array(payload);
99
+ if (typeof Buffer !== "undefined") return Buffer.from(bytes).toString("utf8");
100
+ return new TextDecoder("utf-8", { fatal: false }).decode(bytes);
101
+ }
102
+ function toUint8Array(payload) {
103
+ if (payload instanceof Uint8Array) return payload;
104
+ if (ArrayBuffer.isView(payload)) return new Uint8Array(payload.buffer, payload.byteOffset, payload.byteLength);
105
+ return new Uint8Array(payload);
106
+ }
107
+
108
+ //#endregion
109
+ //#region src/preset/schema.ts
110
+ const VERSION_REGEX = /^[1-9]\d*\.\d+$/;
111
+ const platformIdSchema = z.enum(PLATFORM_IDS);
112
+ const titleSchema = z.string().trim().min(1, "Title is required").max(80, "Title must be 80 characters or less");
113
+ const descriptionSchema = z.string().trim().min(1, "Description is required").max(500, "Description must be 500 characters or less");
102
114
  const versionSchema = z.string().trim().regex(VERSION_REGEX, "Version must be in MAJOR.MINOR format (e.g., 1.3)");
103
115
  const majorVersionSchema = z.number().int().positive("Major version must be a positive integer");
104
- const tagSchema = z.string().trim().min(1).max(48);
105
- const tagsSchema = z.array(tagSchema).max(10);
106
- const featureSchema = z.string().trim().min(1).max(160);
107
- const featuresSchema = z.array(featureSchema).max(10);
108
- const installMessageSchema = z.string().trim().max(4e3);
116
+ const tagSchema = z.string().trim().min(1, "Tag cannot be empty").max(35, "Tag must be 35 characters or less");
117
+ const tagsSchema = z.array(tagSchema).min(1, "At least one tag is required").max(10, "Maximum 10 tags allowed");
118
+ const featureSchema = z.string().trim().min(1, "Feature cannot be empty").max(100, "Feature must be 100 characters or less");
119
+ const featuresSchema = z.array(featureSchema).max(5, "Maximum 5 features allowed");
120
+ const installMessageSchema = z.string().trim().max(2e3, "Install message must be 2000 characters or less");
109
121
  const contentSchema = z.string();
110
122
  const SLUG_REGEX = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
111
123
  const SLUG_ERROR = "Must be lowercase alphanumeric with hyphens (e.g., my-preset)";
112
- const slugSchema = z.string().trim().min(1).max(64).regex(SLUG_REGEX, SLUG_ERROR);
113
- /** Validate a slug string and return error message if invalid, undefined if valid */
114
- function validateSlug(value) {
115
- const trimmed = value.trim();
116
- if (!trimmed) return "Name is required";
117
- if (trimmed.length > 64) return "Name must be 64 characters or less";
118
- if (!SLUG_REGEX.test(trimmed)) return SLUG_ERROR;
119
- return;
120
- }
124
+ const slugSchema = z.string().trim().min(1, "Name is required").max(64, "Name must be 64 characters or less").regex(SLUG_REGEX, SLUG_ERROR);
121
125
  const COMMON_LICENSES = [
122
126
  "MIT",
123
127
  "Apache-2.0",
@@ -126,14 +130,7 @@ const COMMON_LICENSES = [
126
130
  "ISC",
127
131
  "Unlicense"
128
132
  ];
129
- const licenseSchema = z.string().trim().min(1).max(128);
130
- /** Validate a license string and return error message if invalid, undefined if valid */
131
- function validateLicense(value) {
132
- const trimmed = value.trim();
133
- if (!trimmed) return "License is required";
134
- if (trimmed.length > 128) return "License must be 128 characters or less";
135
- return;
136
- }
133
+ const licenseSchema = z.string().trim().min(1, "License is required").max(128, "License must be 128 characters or less");
137
134
  const pathSchema = z.string().trim().min(1);
138
135
  const presetConfigSchema = z.object({
139
136
  $schema: z.string().optional(),
@@ -157,7 +154,7 @@ const bundledFileSchema = z.object({
157
154
  * Schema for what clients send to publish a preset.
158
155
  * Version is optional major version. Registry assigns full MAJOR.MINOR.
159
156
  */
160
- const publishInputSchema = z.object({
157
+ const presetPublishInputSchema = z.object({
161
158
  slug: z.string().trim().min(1),
162
159
  platform: platformIdSchema,
163
160
  title: titleSchema,
@@ -175,8 +172,8 @@ const publishInputSchema = z.object({
175
172
  * Schema for what registries store and return.
176
173
  * Includes version (required) - full MAJOR.MINOR format assigned by registry.
177
174
  */
178
- const registryBundleSchema = publishInputSchema.omit({ version: true }).extend({ version: versionSchema });
179
- const registryEntrySchema = registryBundleSchema.omit({
175
+ const presetBundleSchema = presetPublishInputSchema.omit({ version: true }).extend({ version: versionSchema });
176
+ const presetSchema = presetBundleSchema.omit({
180
177
  files: true,
181
178
  readmeContent: true,
182
179
  licenseContent: true,
@@ -187,27 +184,7 @@ const registryEntrySchema = registryBundleSchema.omit({
187
184
  fileCount: z.number().int().nonnegative(),
188
185
  totalSize: z.number().int().nonnegative()
189
186
  });
190
- const registryIndexSchema = z.record(z.string(), registryEntrySchema);
191
-
192
- //#endregion
193
- //#region src/utils/encoding.ts
194
- function toPosixPath(pathValue) {
195
- return pathValue.split("\\").join("/");
196
- }
197
- function encodeUtf8(value) {
198
- if (typeof Buffer !== "undefined") return new Uint8Array(Buffer.from(value, "utf8"));
199
- return new TextEncoder().encode(value);
200
- }
201
- function decodeUtf8(payload) {
202
- const bytes = toUint8Array(payload);
203
- if (typeof Buffer !== "undefined") return Buffer.from(bytes).toString("utf8");
204
- return new TextDecoder("utf-8", { fatal: false }).decode(bytes);
205
- }
206
- function toUint8Array(payload) {
207
- if (payload instanceof Uint8Array) return payload;
208
- if (ArrayBuffer.isView(payload)) return new Uint8Array(payload.buffer, payload.byteOffset, payload.byteLength);
209
- return new Uint8Array(payload);
210
- }
187
+ const presetIndexSchema = z.record(z.string(), presetSchema);
211
188
 
212
189
  //#endregion
213
190
  //#region src/builder/utils.ts
@@ -220,16 +197,18 @@ function encodeItemName(slug, platform) {
220
197
  return `${slug}.${platform}`;
221
198
  }
222
199
  function validatePresetConfig(config, slug) {
223
- if (!config || typeof config !== "object") throw new Error(`Invalid preset config for ${slug}`);
224
- const preset = config;
225
- if (!preset.name || typeof preset.name !== "string") throw new Error(`Preset ${slug} is missing a name`);
226
- if (!preset.title || typeof preset.title !== "string") throw new Error(`Preset ${slug} is missing a title`);
227
- if (preset.version !== void 0 && typeof preset.version !== "number") throw new Error(`Preset ${slug} has invalid version (must be a positive integer or omitted)`);
228
- if (!preset.description || typeof preset.description !== "string") throw new Error(`Preset ${slug} is missing a description`);
229
- if (!preset.license || typeof preset.license !== "string") throw new Error(`Preset ${slug} is missing a license (SPDX identifier required)`);
230
- if (!preset.platform || typeof preset.platform !== "string") throw new Error(`Preset ${slug} is missing a platform`);
231
- if (!PLATFORM_IDS.includes(preset.platform)) throw new Error(`Preset ${slug} has unknown platform: ${preset.platform}. Supported platforms: ${PLATFORM_IDS.join(", ")}`);
232
- return preset;
200
+ try {
201
+ return presetConfigSchema.parse(config);
202
+ } catch (e) {
203
+ if (e instanceof ZodError) {
204
+ const messages = e.issues.map((issue) => {
205
+ const path = issue.path.length > 0 ? `${issue.path.join(".")}: ` : "";
206
+ return `${path}${issue.message}`;
207
+ });
208
+ throw new Error(`Invalid preset config for ${slug}:\n - ${messages.join("\n - ")}`);
209
+ }
210
+ throw e;
211
+ }
233
212
  }
234
213
 
235
214
  //#endregion
@@ -249,10 +228,10 @@ async function sha256(data) {
249
228
  return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
250
229
  }
251
230
  /**
252
- * Builds a PublishInput from preset input.
231
+ * Builds a PresetPublishInput from preset input.
253
232
  * Used by CLI to prepare data for publishing to a registry.
254
233
  */
255
- async function buildPublishInput(options) {
234
+ async function buildPresetPublishInput(options) {
256
235
  const { preset: presetInput, version } = options;
257
236
  if (!NAME_PATTERN.test(presetInput.slug)) throw new Error(`Invalid slug "${presetInput.slug}". Slugs must be lowercase kebab-case.`);
258
237
  const presetConfig = validatePresetConfig(presetInput.config, presetInput.slug);
@@ -285,7 +264,7 @@ async function buildPublishInput(options) {
285
264
  * Used for building static registry files (e.g., community-presets).
286
265
  * Each preset uses its version from config (default: major 1, minor 0).
287
266
  */
288
- async function buildRegistryData(options) {
267
+ async function buildPresetRegistry(options) {
289
268
  const bundleBase = normalizeBundleBase(options.bundleBase);
290
269
  const entries = [];
291
270
  const bundles = [];
@@ -442,7 +421,7 @@ async function sha256Hex(payload) {
442
421
  * @param version - Version to resolve (defaults to "latest")
443
422
  */
444
423
  async function resolvePreset(baseUrl, slug, platform, version = LATEST_VERSION) {
445
- const apiUrl = new URL(API_ENDPOINTS.presets.entry(slug, platform, version), baseUrl);
424
+ const apiUrl = new URL(API_ENDPOINTS.presets.get(slug, platform, version), baseUrl);
446
425
  const response = await fetch(apiUrl);
447
426
  if (!response.ok) {
448
427
  if (response.status === 404) {
@@ -452,10 +431,10 @@ async function resolvePreset(baseUrl, slug, platform, version = LATEST_VERSION)
452
431
  throw new Error(`Failed to resolve preset (${response.status} ${response.statusText}).`);
453
432
  }
454
433
  try {
455
- const entry = await response.json();
456
- const resolvedBundleUrl = new URL(entry.bundleUrl, baseUrl).toString();
434
+ const preset = await response.json();
435
+ const resolvedBundleUrl = new URL(preset.bundleUrl, baseUrl).toString();
457
436
  return {
458
- entry,
437
+ preset,
459
438
  bundleUrl: resolvedBundleUrl
460
439
  };
461
440
  } catch (error) {
@@ -506,4 +485,4 @@ function maybeStripPrefix(pathInput, prefix) {
506
485
  }
507
486
 
508
487
  //#endregion
509
- export { API_ENDPOINTS, COMMON_LICENSES, CONFIG_DIR_NAME, LATEST_VERSION, PLATFORMS, PLATFORM_IDS, PRESET_CONFIG_FILENAME, PRESET_SCHEMA_URL, STATIC_BUNDLE_DIR, buildPublishInput, buildRegistryData, bundledFileSchema, cleanInstallMessage, createDiffPreview, decodeBundledFile, decodeUtf8, descriptionSchema, encodeItemName, encodeUtf8, fetchBundle, isLikelyText, isSupportedPlatform, licenseSchema, maybeStripPrefix, normalizeBundlePath, normalizePathFragment, normalizePlatformInput, platformIdSchema, presetConfigSchema, publishInputSchema, registryBundleSchema, registryEntrySchema, registryIndexSchema, resolvePreset, slugSchema, titleSchema, toPosixPath, toUint8Array, toUtf8String, validateDescription, validateLicense, validatePresetConfig, validateSlug, validateTitle, verifyBundledFileChecksum };
488
+ export { API_ENDPOINTS, COMMON_LICENSES, CONFIG_DIR_NAME, 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, isLikelyText, isSupportedPlatform, licenseSchema, maybeStripPrefix, normalizeBundlePath, normalizePathFragment, normalizePlatformInput, platformIdSchema, presetBundleSchema, presetConfigSchema, presetIndexSchema, presetPublishInputSchema, presetSchema, resolvePreset, slugSchema, 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.6",
3
+ "version": "0.0.7",
4
4
  "author": "Brian Cheung <bcheung.dev@gmail.com> (https://github.com/bcheung)",
5
5
  "license": "MIT",
6
6
  "homepage": "https://agentrules.directory",