@agentrules/core 0.0.4 → 0.0.5

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
@@ -23,28 +23,22 @@ npm install @agentrules/core
23
23
  ```ts
24
24
  import { buildRegistryData } from "@agentrules/core";
25
25
 
26
- const result = buildRegistryData({
27
- bundleBase: "/r",
26
+ const result = await buildRegistryData({
28
27
  presets: [
29
28
  {
30
29
  slug: "my-preset",
31
30
  config: {
32
31
  name: "my-preset",
33
32
  title: "My Preset",
34
- version: "1.0.0",
33
+ version: 1,
35
34
  description: "A helpful preset",
36
- platforms: {
37
- opencode: { path: ".opencode" },
38
- },
35
+ license: "MIT",
36
+ platform: "opencode",
37
+ path: ".opencode",
39
38
  },
40
- platforms: [
41
- {
42
- platform: "opencode",
43
- files: [
44
- { path: "AGENT_RULES.md", contents: "# Rules\n" },
45
- { path: "config.json", contents: '{"key": "value"}' },
46
- ],
47
- },
39
+ files: [
40
+ { path: "AGENT_RULES.md", contents: "# Rules\n" },
41
+ { path: "config.json", contents: '{"key": "value"}' },
48
42
  ],
49
43
  },
50
44
  ],
@@ -52,7 +46,7 @@ const result = buildRegistryData({
52
46
 
53
47
  // result.entries → array for registry.json
54
48
  // result.index → object for registry.index.json
55
- // result.bundles → per-platform bundle payloads
49
+ // result.bundles → bundle payloads
56
50
  ```
57
51
 
58
52
  ### Validating Preset Config
@@ -73,18 +67,14 @@ if (!result.success) {
73
67
  ### Fetching from Registry
74
68
 
75
69
  ```ts
76
- import {
77
- fetchRegistryIndex,
78
- fetchRegistryBundle,
79
- resolveRegistryEntry,
80
- } from "@agentrules/core";
70
+ import { resolvePreset, fetchBundle } from "@agentrules/core";
81
71
 
82
- const index = await fetchRegistryIndex("https://agentrules.directory/r/");
83
- const entry = resolveRegistryEntry(index, "agentic-dev-starter", "opencode");
84
- const { bundle } = await fetchRegistryBundle(
85
- "https://agentrules.directory/r/",
86
- entry.bundlePath
72
+ const { entry, bundleUrl } = await resolvePreset(
73
+ "https://agentrules.directory/",
74
+ "agentic-dev-starter",
75
+ "opencode"
87
76
  );
77
+ const bundle = await fetchBundle(bundleUrl);
88
78
  ```
89
79
 
90
80
  ### Working with Bundles
@@ -115,21 +105,22 @@ Presets use `agentrules.json`:
115
105
  "$schema": "https://agentrules.directory/schema/agentrules.json",
116
106
  "name": "my-preset",
117
107
  "title": "My Preset",
118
- "version": "1.0.0",
108
+ "version": 1,
119
109
  "description": "Description here",
120
- "author": { "name": "Your Name" },
121
110
  "license": "MIT",
122
111
  "tags": ["starter", "typescript"],
123
- "platforms": {
124
- "opencode": {
125
- "path": "opencode/files/.opencode",
126
- "features": ["Feature 1", "Feature 2"],
127
- "installMessage": "Thanks for installing!"
128
- }
129
- }
112
+ "features": ["Feature 1", "Feature 2"],
113
+ "platform": "opencode",
114
+ "path": "files"
130
115
  }
131
116
  ```
132
117
 
118
+ ### Versioning
119
+
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
123
+
133
124
  ## Development
134
125
 
135
126
  ```bash
package/dist/index.d.ts CHANGED
@@ -1,20 +1,7 @@
1
1
  import { z } from "zod";
2
2
 
3
- //#region src/types/constants.d.ts
4
- /**
5
- * Shared constants for agentrules presets and registry.
6
- */
7
- /** Filename for preset configuration */
8
- /**
9
- * Shared constants for agentrules presets and registry.
10
- */
11
- /** Filename for preset configuration */
12
- declare const PRESET_CONFIG_FILENAME = "agentrules.json";
13
- /** JSON Schema URL for preset configuration */
14
- declare const PRESET_SCHEMA_URL = "https://agentrules.directory/schema/agentrules.json";
15
-
16
- //#endregion
17
3
  //#region src/types/platform.d.ts
4
+
18
5
  /**
19
6
  * Single source of truth for platform IDs.
20
7
  * Add new platforms here - types and config will follow.
@@ -42,30 +29,20 @@ declare const PLATFORMS: Record<PlatformId, PlatformConfig>;
42
29
  declare const CONFIG_DIR_NAME = "config";
43
30
  declare function isSupportedPlatform(value: string): value is PlatformId;
44
31
  declare function normalizePlatformInput(value: string): PlatformId;
45
-
46
32
  //#endregion
47
33
  //#region src/types/definitions.d.ts
48
- type AuthorInfo = {
49
- name: string;
50
- email?: string;
51
- url?: string;
52
- };
53
- type PlatformPresetConfig = {
54
- /** Path to platform config files. Defaults to platform's projectDir (e.g., ".opencode") */
55
- path?: string;
56
- features?: string[];
57
- installMessage?: string;
58
- };
59
34
  type PresetConfig = {
60
35
  $schema?: string;
61
36
  name: string;
62
37
  title: string;
63
- version?: string;
38
+ version?: number;
64
39
  description: string;
65
40
  tags?: string[];
66
- author?: AuthorInfo;
41
+ features?: string[];
67
42
  license: string;
68
- platforms: Partial<Record<PlatformId, PlatformPresetConfig>>;
43
+ platform: PlatformId;
44
+ /** Path to config files. Defaults to platform's projectDir (e.g., ".claude") */
45
+ path?: string;
69
46
  };
70
47
  type BundledFile = {
71
48
  path: string;
@@ -74,20 +51,32 @@ type BundledFile = {
74
51
  checksum: string;
75
52
  contents: string;
76
53
  };
77
- type RegistryBundle = {
54
+ /**
55
+ * What clients send to publish a preset.
56
+ * Version is optional major version. Registry assigns full MAJOR.MINOR.
57
+ */
58
+ type PublishInput = {
78
59
  slug: string;
79
60
  platform: PlatformId;
80
61
  title: string;
81
- version: string;
82
62
  description: string;
83
63
  tags: string[];
84
- author?: AuthorInfo;
85
64
  license: string;
86
65
  licenseContent?: string;
87
66
  readmeContent?: string;
88
67
  features?: string[];
89
68
  installMessage?: string;
90
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;
91
80
  };
92
81
  type RegistryEntry = {
93
82
  name: string;
@@ -97,39 +86,23 @@ type RegistryEntry = {
97
86
  version: string;
98
87
  description: string;
99
88
  tags: string[];
100
- author?: AuthorInfo;
101
89
  license: string;
102
90
  features?: string[];
103
- installMessage?: string;
104
- bundlePath: string;
91
+ bundleUrl: string;
105
92
  fileCount: number;
106
- /** Total size of all files in bytes */
107
93
  totalSize: number;
108
- /** Whether the preset has a README.md */
109
- hasReadmeContent?: boolean;
110
- /** Whether the preset has a LICENSE.md */
111
- hasLicenseContent?: boolean;
112
- };
113
- type RegistryData = {
114
- $schema: string;
115
- items: RegistryEntry[];
116
94
  };
117
95
  type RegistryIndex = Record<string, RegistryEntry>;
118
- type RegistryIndexItem = RegistryEntry;
119
96
  type RegistryFileInput = {
120
97
  path: string;
121
98
  contents: ArrayBuffer | ArrayBufferView | string;
122
99
  };
123
- type RegistryPlatformInput = {
124
- platform: PlatformId;
125
- files: RegistryFileInput[];
126
- /** Install message from INSTALL.txt file */
127
- installMessage?: string;
128
- };
129
100
  type RegistryPresetInput = {
130
101
  slug: string;
131
102
  config: PresetConfig;
132
- platforms: RegistryPlatformInput[];
103
+ files: RegistryFileInput[];
104
+ /** Install message from INSTALL.txt file */
105
+ installMessage?: string;
133
106
  readmeContent?: string;
134
107
  licenseContent?: string;
135
108
  }; //#endregion
@@ -140,11 +113,6 @@ declare const platformIdSchema: z.ZodEnum<{
140
113
  claude: "claude";
141
114
  cursor: "cursor";
142
115
  }>;
143
- declare const authorSchema: z.ZodObject<{
144
- name: z.ZodString;
145
- email: z.ZodOptional<z.ZodEmail>;
146
- url: z.ZodOptional<z.ZodURL>;
147
- }, z.core.$strict>;
148
116
  declare const titleSchema: z.ZodString;
149
117
  declare const descriptionSchema: z.ZodString;
150
118
  /** Validate a title string and return error message if invalid, undefined if valid */
@@ -159,29 +127,22 @@ type CommonLicense = (typeof COMMON_LICENSES)[number];
159
127
  declare const licenseSchema: z.ZodString;
160
128
  /** Validate a license string and return error message if invalid, undefined if valid */
161
129
  declare function validateLicense(value: string): string | undefined;
162
- declare const platformPresetConfigSchema: z.ZodObject<{
163
- path: z.ZodOptional<z.ZodString>;
164
- features: z.ZodOptional<z.ZodArray<z.ZodString>>;
165
- }, z.core.$strict>;
166
130
  declare const presetConfigSchema: z.ZodObject<{
167
131
  $schema: z.ZodOptional<z.ZodString>;
168
132
  name: z.ZodString;
169
133
  title: z.ZodString;
170
- version: z.ZodOptional<z.ZodString>;
134
+ version: z.ZodOptional<z.ZodNumber>;
171
135
  description: z.ZodString;
172
136
  tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
173
- author: z.ZodOptional<z.ZodObject<{
174
- name: z.ZodString;
175
- email: z.ZodOptional<z.ZodEmail>;
176
- url: z.ZodOptional<z.ZodURL>;
177
- }, z.core.$strict>>;
137
+ features: z.ZodOptional<z.ZodArray<z.ZodString>>;
178
138
  license: z.ZodString;
179
- platforms: z.ZodObject<{
180
- [x: string]: z.ZodOptional<z.ZodObject<{
181
- path: z.ZodOptional<z.ZodString>;
182
- features: z.ZodOptional<z.ZodArray<z.ZodString>>;
183
- }, z.core.$strict>>;
184
- }, z.core.$strip>;
139
+ platform: z.ZodEnum<{
140
+ opencode: "opencode";
141
+ codex: "codex";
142
+ claude: "claude";
143
+ cursor: "cursor";
144
+ }>;
145
+ path: z.ZodOptional<z.ZodString>;
185
146
  }, z.core.$strict>;
186
147
  declare const bundledFileSchema: z.ZodObject<{
187
148
  path: z.ZodString;
@@ -189,7 +150,11 @@ declare const bundledFileSchema: z.ZodObject<{
189
150
  checksum: z.ZodString;
190
151
  contents: z.ZodString;
191
152
  }, z.core.$strip>;
192
- declare const registryBundleSchema: z.ZodObject<{
153
+ /**
154
+ * Schema for what clients send to publish a preset.
155
+ * Version is optional major version. Registry assigns full MAJOR.MINOR.
156
+ */
157
+ declare const publishInputSchema: z.ZodObject<{
193
158
  slug: z.ZodString;
194
159
  platform: z.ZodEnum<{
195
160
  opencode: "opencode";
@@ -198,14 +163,8 @@ declare const registryBundleSchema: z.ZodObject<{
198
163
  cursor: "cursor";
199
164
  }>;
200
165
  title: z.ZodString;
201
- version: z.ZodString;
202
166
  description: z.ZodString;
203
167
  tags: z.ZodArray<z.ZodString>;
204
- author: z.ZodOptional<z.ZodObject<{
205
- name: z.ZodString;
206
- email: z.ZodOptional<z.ZodEmail>;
207
- url: z.ZodOptional<z.ZodURL>;
208
- }, z.core.$strict>>;
209
168
  license: z.ZodString;
210
169
  licenseContent: z.ZodOptional<z.ZodString>;
211
170
  readmeContent: z.ZodOptional<z.ZodString>;
@@ -217,88 +176,124 @@ declare const registryBundleSchema: z.ZodObject<{
217
176
  checksum: z.ZodString;
218
177
  contents: z.ZodString;
219
178
  }, z.core.$strip>>;
179
+ version: z.ZodOptional<z.ZodNumber>;
220
180
  }, z.core.$strip>;
221
- declare const registryEntrySchema: z.ZodObject<{
222
- features: z.ZodOptional<z.ZodArray<z.ZodString>>;
181
+ /**
182
+ * Schema for what registries store and return.
183
+ * Includes version (required) - full MAJOR.MINOR format assigned by registry.
184
+ */
185
+ declare const registryBundleSchema: z.ZodObject<{
223
186
  title: z.ZodString;
224
187
  description: z.ZodString;
225
188
  license: z.ZodString;
226
- version: z.ZodString;
189
+ platform: z.ZodEnum<{
190
+ opencode: "opencode";
191
+ codex: "codex";
192
+ claude: "claude";
193
+ cursor: "cursor";
194
+ }>;
227
195
  tags: z.ZodArray<z.ZodString>;
228
- author: z.ZodOptional<z.ZodObject<{
229
- name: z.ZodString;
230
- email: z.ZodOptional<z.ZodEmail>;
231
- url: z.ZodOptional<z.ZodURL>;
232
- }, z.core.$strict>>;
196
+ features: z.ZodOptional<z.ZodArray<z.ZodString>>;
197
+ slug: z.ZodString;
198
+ licenseContent: z.ZodOptional<z.ZodString>;
199
+ readmeContent: z.ZodOptional<z.ZodString>;
200
+ installMessage: z.ZodOptional<z.ZodString>;
201
+ files: z.ZodArray<z.ZodObject<{
202
+ path: z.ZodString;
203
+ size: z.ZodNumber;
204
+ checksum: z.ZodString;
205
+ contents: z.ZodString;
206
+ }, z.core.$strip>>;
207
+ version: z.ZodString;
208
+ }, z.core.$strip>;
209
+ declare const registryEntrySchema: z.ZodObject<{
210
+ title: z.ZodString;
211
+ description: z.ZodString;
212
+ license: z.ZodString;
233
213
  platform: z.ZodEnum<{
234
214
  opencode: "opencode";
235
215
  codex: "codex";
236
216
  claude: "claude";
237
217
  cursor: "cursor";
238
218
  }>;
219
+ version: z.ZodString;
220
+ tags: z.ZodArray<z.ZodString>;
221
+ features: z.ZodOptional<z.ZodArray<z.ZodString>>;
239
222
  slug: z.ZodString;
240
223
  name: z.ZodString;
241
- bundlePath: z.ZodString;
224
+ bundleUrl: z.ZodString;
242
225
  fileCount: z.ZodNumber;
243
226
  totalSize: z.ZodNumber;
244
- hasReadmeContent: z.ZodOptional<z.ZodBoolean>;
245
- hasLicenseContent: z.ZodOptional<z.ZodBoolean>;
246
227
  }, z.core.$strip>;
247
228
  declare const registryIndexSchema: z.ZodRecord<z.ZodString, z.ZodObject<{
248
- features: z.ZodOptional<z.ZodArray<z.ZodString>>;
249
229
  title: z.ZodString;
250
230
  description: z.ZodString;
251
231
  license: z.ZodString;
252
- version: z.ZodString;
253
- tags: z.ZodArray<z.ZodString>;
254
- author: z.ZodOptional<z.ZodObject<{
255
- name: z.ZodString;
256
- email: z.ZodOptional<z.ZodEmail>;
257
- url: z.ZodOptional<z.ZodURL>;
258
- }, z.core.$strict>>;
259
232
  platform: z.ZodEnum<{
260
233
  opencode: "opencode";
261
234
  codex: "codex";
262
235
  claude: "claude";
263
236
  cursor: "cursor";
264
237
  }>;
238
+ version: z.ZodString;
239
+ tags: z.ZodArray<z.ZodString>;
240
+ features: z.ZodOptional<z.ZodArray<z.ZodString>>;
265
241
  slug: z.ZodString;
266
242
  name: z.ZodString;
267
- bundlePath: z.ZodString;
243
+ bundleUrl: z.ZodString;
268
244
  fileCount: z.ZodNumber;
269
245
  totalSize: z.ZodNumber;
270
- hasReadmeContent: z.ZodOptional<z.ZodBoolean>;
271
- hasLicenseContent: z.ZodOptional<z.ZodBoolean>;
272
246
  }, z.core.$strip>>;
273
247
 
274
248
  //#endregion
275
249
  //#region src/builder/registry.d.ts
250
+ /**
251
+ * Directory name for bundle files in static registry output.
252
+ * Used by `agentrules registry build` to structure output.
253
+ */
254
+ declare const STATIC_BUNDLE_DIR = "r";
255
+ /**
256
+ * Options for building a PublishInput (for CLI publish command).
257
+ */
258
+ type BuildPublishInputOptions = {
259
+ preset: RegistryPresetInput;
260
+ /** Major version. Defaults to 1 if not specified. */
261
+ version?: number;
262
+ };
263
+ /**
264
+ * Builds a PublishInput from preset input.
265
+ * Used by CLI to prepare data for publishing to a registry.
266
+ */
267
+ declare function buildPublishInput(options: BuildPublishInputOptions): Promise<PublishInput>;
268
+ /**
269
+ * Options for building a static registry.
270
+ */
276
271
  type BuildRegistryDataOptions = {
277
272
  presets: RegistryPresetInput[];
273
+ /**
274
+ * Optional base path or URL prefix for bundle locations.
275
+ * Format: {bundleBase}/{STATIC_BUNDLE_DIR}/{slug}/{platform}
276
+ * Default: no prefix (bundleUrl starts with STATIC_BUNDLE_DIR)
277
+ */
278
278
  bundleBase?: string;
279
- /** Override the auto-generated version. If not provided, uses current UTC date. */
280
- version?: string;
281
279
  };
282
280
  type BuildRegistryDataResult = {
283
281
  entries: RegistryEntry[];
284
282
  index: RegistryIndex;
285
283
  bundles: RegistryBundle[];
286
284
  };
287
- declare function buildRegistryData(options: BuildRegistryDataOptions): BuildRegistryDataResult;
285
+ /**
286
+ * Builds a static registry with entries, index, and bundles.
287
+ * Used for building static registry files (e.g., community-presets).
288
+ * Each preset uses its version from config (default: major 1, minor 0).
289
+ */
290
+ declare function buildRegistryData(options: BuildRegistryDataOptions): Promise<BuildRegistryDataResult>;
288
291
 
289
292
  //#endregion
290
293
  //#region src/builder/utils.d.ts
291
- /**
292
- * Generates a date-based version string in format YYYY.MM.DD
293
- * Uses UTC to ensure consistent versioning across timezones
294
- */
295
- declare function generateDateVersion(date?: Date): string;
296
- declare function normalizeBundlePublicBase(value: string): string;
297
- declare function isAbsoluteUrl(value: string): boolean;
298
294
  declare function cleanInstallMessage(value: unknown): string | undefined;
299
295
  declare function encodeItemName(slug: string, platform: PlatformId): string;
300
296
  declare function validatePresetConfig(config: unknown, slug: string): PresetConfig;
301
- declare function collectBundledFiles(files: Record<string, string>): BundledFile[];
302
297
 
303
298
  //#endregion
304
299
  //#region src/client/bundle.d.ts
@@ -309,9 +304,62 @@ declare function toUtf8String(payload: ArrayBuffer | ArrayBufferView): string;
309
304
 
310
305
  //#endregion
311
306
  //#region src/client/registry.d.ts
312
- declare function fetchRegistryIndex(baseUrl: string): Promise<RegistryIndex>;
313
- declare function fetchRegistryBundle(baseUrl: string, bundlePath: string): Promise<RegistryBundle>;
314
- declare function resolveRegistryEntry(index: RegistryIndex, input: string, explicitPlatform?: PlatformId): RegistryEntry;
307
+ /**
308
+ * Resolved preset with absolute bundle URL
309
+ */
310
+ type ResolvedPreset = {
311
+ entry: RegistryEntry;
312
+ bundleUrl: string;
313
+ };
314
+ /**
315
+ * Resolves a preset from the registry via API endpoint.
316
+ * Returns the entry metadata and the absolute bundle URL.
317
+ *
318
+ * @param baseUrl - Registry base URL
319
+ * @param slug - Preset slug
320
+ * @param platform - Target platform
321
+ * @param version - Version to resolve (defaults to "latest")
322
+ */
323
+ declare function resolvePreset(baseUrl: string, slug: string, platform: PlatformId, version?: string): Promise<ResolvedPreset>;
324
+ /**
325
+ * Fetches a bundle from an absolute URL or resolves it relative to the registry.
326
+ */
327
+ declare function fetchBundle(bundleUrl: string): Promise<RegistryBundle>;
328
+
329
+ //#endregion
330
+ //#region src/constants.d.ts
331
+ /**
332
+ * Shared constants for agentrules presets and registry.
333
+ */
334
+ /** Filename for preset configuration */
335
+ declare const PRESET_CONFIG_FILENAME = "agentrules.json";
336
+ /** JSON Schema URL for preset configuration */
337
+ declare const PRESET_SCHEMA_URL = "https://agentrules.directory/schema/agentrules.json";
338
+ /** Default version identifier for latest preset version */
339
+ declare const LATEST_VERSION = "latest";
340
+ /**
341
+ * API endpoint paths (relative to registry base URL).
342
+ */
343
+ declare const API_ENDPOINTS: {
344
+ /** Preset endpoints */
345
+ readonly presets: {
346
+ /** Base path for preset operations */
347
+ readonly base: "api/presets";
348
+ /** Get preset entry by version (defaults to "latest") */
349
+ readonly entry: (slug: string, platform: string, version?: string) => string;
350
+ /** Unpublish preset version */
351
+ readonly unpublish: (slug: string, platform: string, version: string) => string;
352
+ };
353
+ /** Auth endpoints */
354
+ readonly auth: {
355
+ /** Get current session */
356
+ readonly session: "api/auth/get-session";
357
+ /** Device authorization code request */
358
+ readonly deviceCode: "api/auth/device/code";
359
+ /** Device token exchange */
360
+ readonly deviceToken: "api/auth/device/token";
361
+ };
362
+ };
315
363
 
316
364
  //#endregion
317
365
  //#region src/utils/diff.d.ts
@@ -335,4 +383,4 @@ declare function normalizePathFragment(value?: string): string | undefined;
335
383
  declare function maybeStripPrefix(pathInput: string, prefix?: string): string;
336
384
 
337
385
  //#endregion
338
- export { AuthorInfo, BuildRegistryDataOptions, BuildRegistryDataResult, BundledFile, COMMON_LICENSES, CONFIG_DIR_NAME, CommonLicense, DiffPreviewOptions, PLATFORMS, PLATFORM_IDS, PRESET_CONFIG_FILENAME, PRESET_SCHEMA_URL, PlatformId, PlatformPresetConfig, PresetConfig, RegistryBundle, RegistryData, RegistryEntry, RegistryFileInput, RegistryIndex, RegistryIndexItem, RegistryPlatformInput, RegistryPresetInput, authorSchema, buildRegistryData, bundledFileSchema, cleanInstallMessage, collectBundledFiles, createDiffPreview, decodeBundledFile, decodeUtf8, descriptionSchema, encodeItemName, encodeUtf8, fetchRegistryBundle, fetchRegistryIndex, generateDateVersion, isAbsoluteUrl, isLikelyText, isSupportedPlatform, licenseSchema, maybeStripPrefix, normalizeBundlePath, normalizeBundlePublicBase, normalizePathFragment, normalizePlatformInput, platformIdSchema, platformPresetConfigSchema, presetConfigSchema, registryBundleSchema, registryEntrySchema, registryIndexSchema, resolveRegistryEntry, slugSchema, titleSchema, toPosixPath, toUint8Array, toUtf8String, validateDescription, validateLicense, validatePresetConfig, validateSlug, validateTitle, verifyBundledFileChecksum };
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 };
package/dist/index.js CHANGED
@@ -1,8 +1,7 @@
1
- import { createHash } from "crypto";
2
1
  import { z } from "zod";
3
2
  import { createTwoFilesPatch } from "diff";
4
3
 
5
- //#region src/types/constants.ts
4
+ //#region src/constants.ts
6
5
  /**
7
6
  * Shared constants for agentrules presets and registry.
8
7
  */
@@ -10,6 +9,25 @@ import { createTwoFilesPatch } from "diff";
10
9
  const PRESET_CONFIG_FILENAME = "agentrules.json";
11
10
  /** JSON Schema URL for preset configuration */
12
11
  const PRESET_SCHEMA_URL = "https://agentrules.directory/schema/agentrules.json";
12
+ /** API root path segment */
13
+ const API_PATH = "api";
14
+ /** Default version identifier for latest preset version */
15
+ const LATEST_VERSION = "latest";
16
+ /**
17
+ * API endpoint paths (relative to registry base URL).
18
+ */
19
+ const API_ENDPOINTS = {
20
+ presets: {
21
+ base: `${API_PATH}/presets`,
22
+ entry: (slug, platform, version = LATEST_VERSION) => `${API_PATH}/presets/${encodeURIComponent(slug)}/${encodeURIComponent(platform)}/${encodeURIComponent(version)}`,
23
+ unpublish: (slug, platform, version) => `${API_PATH}/presets/${encodeURIComponent(slug)}/${encodeURIComponent(platform)}/${encodeURIComponent(version)}`
24
+ },
25
+ auth: {
26
+ session: `${API_PATH}/auth/get-session`,
27
+ deviceCode: `${API_PATH}/auth/device/code`,
28
+ deviceToken: `${API_PATH}/auth/device/token`
29
+ }
30
+ };
13
31
 
14
32
  //#endregion
15
33
  //#region src/types/platform.ts
@@ -63,13 +81,8 @@ function normalizePlatformInput(value) {
63
81
 
64
82
  //#endregion
65
83
  //#region src/types/schema.ts
66
- const DATE_VERSION_REGEX = /^\d{4}\.(0[1-9]|1[0-2])\.(0[1-9]|[12]\d|3[01])(-\d+)?$/;
84
+ const VERSION_REGEX = /^[1-9]\d*\.\d+$/;
67
85
  const platformIdSchema = z.enum(PLATFORM_IDS);
68
- const authorSchema = z.object({
69
- name: z.string().trim().min(1),
70
- email: z.email().trim().optional(),
71
- url: z.url().trim().optional()
72
- }).strict();
73
86
  const titleSchema = z.string().trim().min(1).max(120);
74
87
  const descriptionSchema = z.string().trim().min(1).max(500);
75
88
  /** Validate a title string and return error message if invalid, undefined if valid */
@@ -86,7 +99,8 @@ function validateDescription(value) {
86
99
  if (trimmed.length > 500) return "Description must be 500 characters or less";
87
100
  return;
88
101
  }
89
- const versionSchema = z.string().trim().regex(DATE_VERSION_REGEX, "Version must be date-based (YYYY.MM.DD or YYYY.MM.DD-N)");
102
+ const versionSchema = z.string().trim().regex(VERSION_REGEX, "Version must be in MAJOR.MINOR format (e.g., 1.3)");
103
+ const majorVersionSchema = z.number().int().positive("Major version must be a positive integer");
90
104
  const tagSchema = z.string().trim().min(1).max(48);
91
105
  const tagsSchema = z.array(tagSchema).max(10);
92
106
  const featureSchema = z.string().trim().min(1).max(160);
@@ -121,21 +135,17 @@ function validateLicense(value) {
121
135
  return;
122
136
  }
123
137
  const pathSchema = z.string().trim().min(1);
124
- const platformPresetConfigSchema = z.object({
125
- path: pathSchema.optional(),
126
- features: featuresSchema.optional()
127
- }).strict();
128
- const platformsObjectSchema = z.object(Object.fromEntries(PLATFORM_IDS.map((id) => [id, platformPresetConfigSchema.optional()]))).refine((p) => Object.keys(p).length > 0, { message: "At least one platform must be configured" });
129
138
  const presetConfigSchema = z.object({
130
139
  $schema: z.string().optional(),
131
140
  name: slugSchema,
132
141
  title: titleSchema,
133
- version: versionSchema.optional(),
142
+ version: majorVersionSchema.optional(),
134
143
  description: descriptionSchema,
135
144
  tags: tagsSchema.optional(),
136
- author: authorSchema.optional(),
145
+ features: featuresSchema.optional(),
137
146
  license: licenseSchema,
138
- platforms: platformsObjectSchema
147
+ platform: platformIdSchema,
148
+ path: pathSchema.optional()
139
149
  }).strict();
140
150
  const bundledFileSchema = z.object({
141
151
  path: z.string().min(1),
@@ -143,21 +153,29 @@ const bundledFileSchema = z.object({
143
153
  checksum: z.string().length(64),
144
154
  contents: z.string()
145
155
  });
146
- const registryBundleSchema = z.object({
156
+ /**
157
+ * Schema for what clients send to publish a preset.
158
+ * Version is optional major version. Registry assigns full MAJOR.MINOR.
159
+ */
160
+ const publishInputSchema = z.object({
147
161
  slug: z.string().trim().min(1),
148
162
  platform: platformIdSchema,
149
163
  title: titleSchema,
150
- version: versionSchema,
151
164
  description: descriptionSchema,
152
165
  tags: tagsSchema,
153
- author: authorSchema.optional(),
154
166
  license: licenseSchema,
155
167
  licenseContent: contentSchema.optional(),
156
168
  readmeContent: contentSchema.optional(),
157
169
  features: featuresSchema.optional(),
158
170
  installMessage: installMessageSchema.optional(),
159
- files: z.array(bundledFileSchema).min(1)
171
+ files: z.array(bundledFileSchema).min(1),
172
+ version: majorVersionSchema.optional()
160
173
  });
174
+ /**
175
+ * Schema for what registries store and return.
176
+ * Includes version (required) - full MAJOR.MINOR format assigned by registry.
177
+ */
178
+ const registryBundleSchema = publishInputSchema.omit({ version: true }).extend({ version: versionSchema });
161
179
  const registryEntrySchema = registryBundleSchema.omit({
162
180
  files: true,
163
181
  readmeContent: true,
@@ -165,11 +183,9 @@ const registryEntrySchema = registryBundleSchema.omit({
165
183
  installMessage: true
166
184
  }).extend({
167
185
  name: z.string().trim().min(1),
168
- bundlePath: z.string().trim().min(1),
186
+ bundleUrl: z.string().trim().min(1),
169
187
  fileCount: z.number().int().nonnegative(),
170
- totalSize: z.number().int().nonnegative(),
171
- hasReadmeContent: z.boolean().optional(),
172
- hasLicenseContent: z.boolean().optional()
188
+ totalSize: z.number().int().nonnegative()
173
189
  });
174
190
  const registryIndexSchema = z.record(z.string(), registryEntrySchema);
175
191
 
@@ -195,28 +211,6 @@ function toUint8Array(payload) {
195
211
 
196
212
  //#endregion
197
213
  //#region src/builder/utils.ts
198
- /**
199
- * Generates a date-based version string in format YYYY.MM.DD
200
- * Uses UTC to ensure consistent versioning across timezones
201
- */
202
- function generateDateVersion(date = new Date()) {
203
- const year = date.getUTCFullYear();
204
- const month = String(date.getUTCMonth() + 1).padStart(2, "0");
205
- const day = String(date.getUTCDate()).padStart(2, "0");
206
- return `${year}.${month}.${day}`;
207
- }
208
- function normalizeBundlePublicBase(value) {
209
- const trimmed = value.trim();
210
- if (!trimmed) throw new Error("--bundle-base must be a non-empty string");
211
- if (isAbsoluteUrl(trimmed)) return trimmed.replace(/\/+$/, "");
212
- let normalized = trimmed;
213
- if (!normalized.startsWith("/")) normalized = `/${normalized}`;
214
- normalized = normalized.replace(/\/+$/, "");
215
- return normalized === "" ? "/" : normalized;
216
- }
217
- function isAbsoluteUrl(value) {
218
- return /^[a-zA-Z][a-zA-Z\d+-.]*:/.test(value);
219
- }
220
214
  function cleanInstallMessage(value) {
221
215
  if (typeof value !== "string") return;
222
216
  const trimmed = value.trim();
@@ -230,84 +224,115 @@ function validatePresetConfig(config, slug) {
230
224
  const preset = config;
231
225
  if (!preset.name || typeof preset.name !== "string") throw new Error(`Preset ${slug} is missing a name`);
232
226
  if (!preset.title || typeof preset.title !== "string") throw new Error(`Preset ${slug} is missing a title`);
233
- if (preset.version !== void 0 && typeof preset.version !== "string") throw new Error(`Preset ${slug} has invalid version (must be string or omitted)`);
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)`);
234
228
  if (!preset.description || typeof preset.description !== "string") throw new Error(`Preset ${slug} is missing a description`);
235
229
  if (!preset.license || typeof preset.license !== "string") throw new Error(`Preset ${slug} is missing a license (SPDX identifier required)`);
236
- if (!preset.platforms || typeof preset.platforms !== "object") throw new Error(`Preset ${slug} is missing platforms map`);
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(", ")}`);
237
232
  return preset;
238
233
  }
239
- function collectBundledFiles(files) {
240
- return Object.entries(files).map(([path, contents]) => {
241
- const normalizedPath = toPosixPath(path);
242
- const payload = encodeUtf8(contents);
243
- return {
244
- path: normalizedPath,
245
- size: payload.length,
246
- checksum: "",
247
- contents
248
- };
249
- }).sort((a, b) => a.path.localeCompare(b.path));
250
- }
251
234
 
252
235
  //#endregion
253
236
  //#region src/builder/registry.ts
254
237
  const NAME_PATTERN = /^[a-z0-9-]+$/;
255
- function buildRegistryData(options) {
256
- const bundleBase = normalizeBundlePublicBase(options.bundleBase ?? "/r");
257
- const buildVersion = options.version ?? generateDateVersion();
238
+ /**
239
+ * Directory name for bundle files in static registry output.
240
+ * Used by `agentrules registry build` to structure output.
241
+ */
242
+ const STATIC_BUNDLE_DIR = "r";
243
+ /**
244
+ * Compute SHA-256 hash using Web Crypto API (works in browser and Node.js 15+)
245
+ */
246
+ async function sha256(data) {
247
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
248
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
249
+ return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
250
+ }
251
+ /**
252
+ * Builds a PublishInput from preset input.
253
+ * Used by CLI to prepare data for publishing to a registry.
254
+ */
255
+ async function buildPublishInput(options) {
256
+ const { preset: presetInput, version } = options;
257
+ if (!NAME_PATTERN.test(presetInput.slug)) throw new Error(`Invalid slug "${presetInput.slug}". Slugs must be lowercase kebab-case.`);
258
+ const presetConfig = validatePresetConfig(presetInput.config, presetInput.slug);
259
+ const platform = presetConfig.platform;
260
+ ensureKnownPlatform(platform, presetInput.slug);
261
+ if (presetInput.files.length === 0) throw new Error(`Preset ${presetInput.slug} does not include any files.`);
262
+ const files = await createBundledFilesFromInputs(presetInput.files);
263
+ const installMessage = cleanInstallMessage(presetInput.installMessage);
264
+ const features = presetConfig.features ?? [];
265
+ const readmeContent = presetInput.readmeContent?.trim() || void 0;
266
+ const licenseContent = presetInput.licenseContent?.trim() || void 0;
267
+ const majorVersion = version ?? presetConfig.version;
268
+ return {
269
+ slug: presetInput.slug,
270
+ platform,
271
+ title: presetConfig.title,
272
+ description: presetConfig.description,
273
+ tags: presetConfig.tags ?? [],
274
+ license: presetConfig.license,
275
+ licenseContent,
276
+ readmeContent,
277
+ features,
278
+ installMessage,
279
+ files,
280
+ ...majorVersion !== void 0 && { version: majorVersion }
281
+ };
282
+ }
283
+ /**
284
+ * Builds a static registry with entries, index, and bundles.
285
+ * Used for building static registry files (e.g., community-presets).
286
+ * Each preset uses its version from config (default: major 1, minor 0).
287
+ */
288
+ async function buildRegistryData(options) {
289
+ const bundleBase = normalizeBundleBase(options.bundleBase);
258
290
  const entries = [];
259
291
  const bundles = [];
260
292
  for (const presetInput of options.presets) {
261
293
  if (!NAME_PATTERN.test(presetInput.slug)) throw new Error(`Invalid slug "${presetInput.slug}". Slugs must be lowercase kebab-case.`);
262
294
  const presetConfig = validatePresetConfig(presetInput.config, presetInput.slug);
263
- if (presetInput.platforms.length === 0) throw new Error(`Preset ${presetInput.slug} has no platform inputs.`);
264
- for (const platformInput of presetInput.platforms) {
265
- const platform = platformInput.platform;
266
- ensureKnownPlatform(platform, presetInput.slug);
267
- const platformConfig = presetConfig.platforms?.[platform];
268
- if (!platformConfig) throw new Error(`Preset ${presetInput.slug} has files for platform "${platform}" but no config entry.`);
269
- if (platformInput.files.length === 0) throw new Error(`Preset ${presetInput.slug}/${platform} does not include any files.`);
270
- const files = createBundledFilesFromInputs(platformInput.files);
271
- const totalSize = files.reduce((sum, file) => sum + file.size, 0);
272
- const installMessage = cleanInstallMessage(platformInput.installMessage);
273
- const features = platformConfig.features ?? [];
274
- const readmeContent = presetInput.readmeContent?.trim() || void 0;
275
- const licenseContent = presetInput.licenseContent?.trim() || void 0;
276
- const entry = {
277
- name: encodeItemName(presetInput.slug, platform),
278
- slug: presetInput.slug,
279
- platform,
280
- title: presetConfig.title,
281
- version: buildVersion,
282
- description: presetConfig.description,
283
- tags: presetConfig.tags ?? [],
284
- author: presetConfig.author,
285
- license: presetConfig.license,
286
- features,
287
- bundlePath: getBundlePublicPath(bundleBase, presetInput.slug, platform, buildVersion),
288
- fileCount: files.length,
289
- totalSize,
290
- hasReadmeContent: Boolean(readmeContent),
291
- hasLicenseContent: Boolean(licenseContent)
292
- };
293
- const bundle = {
294
- slug: presetInput.slug,
295
- platform,
296
- title: presetConfig.title,
297
- version: buildVersion,
298
- description: presetConfig.description,
299
- tags: presetConfig.tags ?? [],
300
- author: presetConfig.author,
301
- license: presetConfig.license,
302
- licenseContent,
303
- readmeContent,
304
- features,
305
- installMessage,
306
- files
307
- };
308
- entries.push(entry);
309
- bundles.push(bundle);
310
- }
295
+ const platform = presetConfig.platform;
296
+ ensureKnownPlatform(platform, presetInput.slug);
297
+ if (presetInput.files.length === 0) throw new Error(`Preset ${presetInput.slug} does not include any files.`);
298
+ const files = await createBundledFilesFromInputs(presetInput.files);
299
+ const totalSize = files.reduce((sum, file) => sum + file.size, 0);
300
+ const installMessage = cleanInstallMessage(presetInput.installMessage);
301
+ const features = presetConfig.features ?? [];
302
+ const readmeContent = presetInput.readmeContent?.trim() || void 0;
303
+ const licenseContent = presetInput.licenseContent?.trim() || void 0;
304
+ const majorVersion = presetConfig.version ?? 1;
305
+ const version = `${majorVersion}.0`;
306
+ const entry = {
307
+ name: encodeItemName(presetInput.slug, platform),
308
+ slug: presetInput.slug,
309
+ platform,
310
+ title: presetConfig.title,
311
+ version,
312
+ description: presetConfig.description,
313
+ tags: presetConfig.tags ?? [],
314
+ license: presetConfig.license,
315
+ features,
316
+ bundleUrl: getBundlePath(bundleBase, presetInput.slug, platform, version),
317
+ fileCount: files.length,
318
+ totalSize
319
+ };
320
+ entries.push(entry);
321
+ const bundle = {
322
+ slug: presetInput.slug,
323
+ platform,
324
+ title: presetConfig.title,
325
+ version,
326
+ description: presetConfig.description,
327
+ tags: presetConfig.tags ?? [],
328
+ license: presetConfig.license,
329
+ licenseContent,
330
+ readmeContent,
331
+ features,
332
+ installMessage,
333
+ files
334
+ };
335
+ bundles.push(bundle);
311
336
  }
312
337
  sortBySlugAndPlatform(entries);
313
338
  sortBySlugAndPlatform(bundles);
@@ -321,33 +346,49 @@ function buildRegistryData(options) {
321
346
  bundles
322
347
  };
323
348
  }
324
- function createBundledFilesFromInputs(files) {
325
- return files.map((file) => {
349
+ async function createBundledFilesFromInputs(files) {
350
+ const results = await Promise.all(files.map(async (file) => {
326
351
  const payload = normalizeFilePayload(file.contents);
327
352
  const contents = encodeFilePayload(payload, file.path);
328
- const checksum = createHash("sha256").update(payload).digest("hex");
353
+ const checksum = await sha256(payload);
329
354
  return {
330
355
  path: toPosixPath(file.path),
331
356
  size: payload.length,
332
357
  checksum,
333
358
  contents
334
359
  };
335
- }).sort((a, b) => a.path.localeCompare(b.path));
360
+ }));
361
+ return results.sort((a, b) => a.path.localeCompare(b.path));
336
362
  }
337
363
  function normalizeFilePayload(contents) {
338
- if (typeof contents === "string") return Buffer.from(contents, "utf8");
339
- if (contents instanceof ArrayBuffer) return Buffer.from(contents);
340
- if (ArrayBuffer.isView(contents)) return Buffer.from(contents.buffer, contents.byteOffset, contents.byteLength);
341
- return Buffer.from(contents);
364
+ if (typeof contents === "string") return new TextEncoder().encode(contents);
365
+ if (contents instanceof ArrayBuffer) return new Uint8Array(contents);
366
+ if (ArrayBuffer.isView(contents)) return new Uint8Array(contents.buffer, contents.byteOffset, contents.byteLength);
367
+ return new Uint8Array(contents);
342
368
  }
343
- function encodeFilePayload(buffer, filePath) {
344
- const utf8 = buffer.toString("utf8");
345
- if (!Buffer.from(utf8, "utf8").equals(buffer)) throw new Error(`Binary files are not supported: "${filePath}". Only UTF-8 text files are allowed.`);
346
- return utf8;
369
+ function encodeFilePayload(data, filePath) {
370
+ const decoder = new TextDecoder("utf-8", { fatal: true });
371
+ try {
372
+ return decoder.decode(data);
373
+ } catch {
374
+ throw new Error(`Binary files are not supported: "${filePath}". Only UTF-8 text files are allowed.`);
375
+ }
347
376
  }
348
- function getBundlePublicPath(base, slug, platform, version) {
349
- const prefix = base === "/" ? "" : base;
350
- return `${prefix}/${slug}/${platform}.${version}.json`;
377
+ /**
378
+ * Normalize bundle base by removing trailing slashes.
379
+ * Returns empty string if base is undefined/empty (use default relative path).
380
+ */
381
+ function normalizeBundleBase(base) {
382
+ if (!base) return "";
383
+ return base.replace(/\/+$/, "");
384
+ }
385
+ /**
386
+ * Returns the bundle URL/path for a preset.
387
+ * Format: {base}/{STATIC_BUNDLE_DIR}/{slug}/{platform}/{version}
388
+ */
389
+ function getBundlePath(base, slug, platform, version = LATEST_VERSION) {
390
+ const prefix = base ? `${base}/` : "";
391
+ return `${prefix}${STATIC_BUNDLE_DIR}/${slug}/${platform}/${version}`;
351
392
  }
352
393
  function ensureKnownPlatform(platform, slug) {
353
394
  if (!isSupportedPlatform(platform)) throw new Error(`Unknown platform "${platform}" in ${slug}. Supported: ${PLATFORM_IDS.join(", ")}`);
@@ -383,26 +424,48 @@ function toUtf8String(payload) {
383
424
  return decodeUtf8(payload);
384
425
  }
385
426
  async function sha256Hex(payload) {
386
- const crypto = globalThis.crypto;
387
- if (!crypto?.subtle) throw new Error("SHA-256 hashing requires Web Crypto API support.");
388
- const digest = await crypto.subtle.digest("SHA-256", payload);
427
+ const crypto$1 = globalThis.crypto;
428
+ if (!crypto$1?.subtle) throw new Error("SHA-256 hashing requires Web Crypto API support.");
429
+ const digest = await crypto$1.subtle.digest("SHA-256", payload);
389
430
  return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
390
431
  }
391
432
 
392
433
  //#endregion
393
434
  //#region src/client/registry.ts
394
- async function fetchRegistryIndex(baseUrl) {
395
- const indexUrl = new URL("registry.index.json", baseUrl);
396
- const response = await fetch(indexUrl);
397
- if (!response.ok) throw new Error(`Failed to load registry index (${response.status} ${response.statusText}).`);
435
+ /**
436
+ * Resolves a preset from the registry via API endpoint.
437
+ * Returns the entry metadata and the absolute bundle URL.
438
+ *
439
+ * @param baseUrl - Registry base URL
440
+ * @param slug - Preset slug
441
+ * @param platform - Target platform
442
+ * @param version - Version to resolve (defaults to "latest")
443
+ */
444
+ async function resolvePreset(baseUrl, slug, platform, version = LATEST_VERSION) {
445
+ const apiUrl = new URL(API_ENDPOINTS.presets.entry(slug, platform, version), baseUrl);
446
+ const response = await fetch(apiUrl);
447
+ if (!response.ok) {
448
+ if (response.status === 404) {
449
+ const versionInfo = version === LATEST_VERSION ? "" : ` version "${version}"`;
450
+ throw new Error(`Preset "${slug}"${versionInfo} for platform "${platform}" was not found in the registry.`);
451
+ }
452
+ throw new Error(`Failed to resolve preset (${response.status} ${response.statusText}).`);
453
+ }
398
454
  try {
399
- return await response.json();
455
+ const entry = await response.json();
456
+ const resolvedBundleUrl = new URL(entry.bundleUrl, baseUrl).toString();
457
+ return {
458
+ entry,
459
+ bundleUrl: resolvedBundleUrl
460
+ };
400
461
  } catch (error) {
401
- throw new Error(`Unable to parse registry index JSON: ${error.message}`);
462
+ throw new Error(`Unable to parse preset response: ${error.message}`);
402
463
  }
403
464
  }
404
- async function fetchRegistryBundle(baseUrl, bundlePath) {
405
- const bundleUrl = new URL(bundlePath, baseUrl);
465
+ /**
466
+ * Fetches a bundle from an absolute URL or resolves it relative to the registry.
467
+ */
468
+ async function fetchBundle(bundleUrl) {
406
469
  const response = await fetch(bundleUrl);
407
470
  if (!response.ok) throw new Error(`Failed to download bundle (${response.status} ${response.statusText}).`);
408
471
  try {
@@ -411,35 +474,6 @@ async function fetchRegistryBundle(baseUrl, bundlePath) {
411
474
  throw new Error(`Unable to parse bundle JSON: ${error.message}`);
412
475
  }
413
476
  }
414
- function resolveRegistryEntry(index, input, explicitPlatform) {
415
- const map = new Map();
416
- for (const [key, value] of Object.entries(index)) map.set(key.toLowerCase(), value);
417
- const normalizedInput = input.toLowerCase();
418
- const direct = map.get(normalizedInput);
419
- if (direct) return direct;
420
- let slugHint = normalizedInput;
421
- let platform = explicitPlatform;
422
- if (!platform) {
423
- const parts = normalizedInput.split(".");
424
- const maybePlatform = parts.at(-1);
425
- if (maybePlatform && isSupportedPlatform(maybePlatform)) {
426
- platform = maybePlatform;
427
- slugHint = parts.slice(0, -1).join(".");
428
- }
429
- }
430
- const matches = Object.values(index).filter((entry) => entry.slug.toLowerCase() === slugHint);
431
- if (platform) {
432
- const match = matches.find((entry) => entry.platform === platform);
433
- if (match) return match;
434
- throw new Error(`Preset "${input}" is not available for platform "${platform}".`);
435
- }
436
- if (matches.length === 1) return matches[0];
437
- if (matches.length > 1) {
438
- const platforms = matches.map((entry) => entry.platform).join(", ");
439
- throw new Error(`Preset "${input}" is available for multiple platforms (${platforms}). Use --platform to pick one or specify <slug>.<platform>.`);
440
- }
441
- throw new Error(`Preset "${input}" was not found in the active registry.`);
442
- }
443
477
 
444
478
  //#endregion
445
479
  //#region src/utils/diff.ts
@@ -472,4 +506,4 @@ function maybeStripPrefix(pathInput, prefix) {
472
506
  }
473
507
 
474
508
  //#endregion
475
- export { COMMON_LICENSES, CONFIG_DIR_NAME, PLATFORMS, PLATFORM_IDS, PRESET_CONFIG_FILENAME, PRESET_SCHEMA_URL, authorSchema, buildRegistryData, bundledFileSchema, cleanInstallMessage, collectBundledFiles, createDiffPreview, decodeBundledFile, decodeUtf8, descriptionSchema, encodeItemName, encodeUtf8, fetchRegistryBundle, fetchRegistryIndex, generateDateVersion, isAbsoluteUrl, isLikelyText, isSupportedPlatform, licenseSchema, maybeStripPrefix, normalizeBundlePath, normalizeBundlePublicBase, normalizePathFragment, normalizePlatformInput, platformIdSchema, platformPresetConfigSchema, presetConfigSchema, registryBundleSchema, registryEntrySchema, registryIndexSchema, resolveRegistryEntry, slugSchema, titleSchema, toPosixPath, toUint8Array, toUtf8String, validateDescription, validateLicense, validatePresetConfig, validateSlug, validateTitle, verifyBundledFileChecksum };
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 };
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@agentrules/core",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "author": "Brian Cheung <bcheung.dev@gmail.com> (https://github.com/bcheung)",
5
5
  "license": "MIT",
6
- "homepage": "https://docs.agentrules.directory",
6
+ "homepage": "https://agentrules.directory",
7
7
  "repository": {
8
8
  "type": "git",
9
9
  "url": "https://github.com/agentrules-sh/agentrules",
@@ -41,14 +41,15 @@
41
41
  "build": "tsdown",
42
42
  "dev": "tsdown --watch",
43
43
  "typecheck": "tsc --noEmit",
44
- "test": "bun test"
44
+ "test": "bun test",
45
+ "clean": "rm -rf node_modules dist .turbo"
45
46
  },
46
47
  "dependencies": {
47
48
  "diff": "^8.0.2",
48
- "zod": "^4.1.12"
49
+ "zod": "catalog:"
49
50
  },
50
51
  "devDependencies": {
51
52
  "tsdown": "^0.9.0",
52
- "typescript": "5.7.2"
53
+ "typescript": "catalog:"
53
54
  }
54
55
  }