@agentrules/core 0.0.2 → 0.0.4

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 ADDED
@@ -0,0 +1,140 @@
1
+ # @agentrules/core
2
+
3
+ Shared types and utilities for the AGENT_RULES ecosystem.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @agentrules/core
9
+ ```
10
+
11
+ ## Features
12
+
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
18
+
19
+ ## Usage
20
+
21
+ ### Building Registry Data
22
+
23
+ ```ts
24
+ import { buildRegistryData } from "@agentrules/core";
25
+
26
+ const result = buildRegistryData({
27
+ bundleBase: "/r",
28
+ presets: [
29
+ {
30
+ slug: "my-preset",
31
+ config: {
32
+ name: "my-preset",
33
+ title: "My Preset",
34
+ version: "1.0.0",
35
+ description: "A helpful preset",
36
+ platforms: {
37
+ opencode: { path: ".opencode" },
38
+ },
39
+ },
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
+ },
48
+ ],
49
+ },
50
+ ],
51
+ });
52
+
53
+ // result.entries → array for registry.json
54
+ // result.index → object for registry.index.json
55
+ // result.bundles → per-platform bundle payloads
56
+ ```
57
+
58
+ ### Validating Preset Config
59
+
60
+ ```ts
61
+ import { validatePresetConfig, presetConfigSchema } from "@agentrules/core";
62
+
63
+ // Quick validation (throws on error)
64
+ const config = validatePresetConfig(jsonData, "my-preset");
65
+
66
+ // Zod schema for custom handling
67
+ const result = presetConfigSchema.safeParse(jsonData);
68
+ if (!result.success) {
69
+ console.error(result.error.issues);
70
+ }
71
+ ```
72
+
73
+ ### Fetching from Registry
74
+
75
+ ```ts
76
+ import {
77
+ fetchRegistryIndex,
78
+ fetchRegistryBundle,
79
+ resolveRegistryEntry,
80
+ } from "@agentrules/core";
81
+
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
87
+ );
88
+ ```
89
+
90
+ ### Working with Bundles
91
+
92
+ ```ts
93
+ import {
94
+ decodeBundledFile,
95
+ verifyBundledFileChecksum,
96
+ isLikelyText,
97
+ } from "@agentrules/core";
98
+
99
+ for (const file of bundle.files) {
100
+ const data = decodeBundledFile(file);
101
+ await verifyBundledFileChecksum(file, data);
102
+
103
+ if (isLikelyText(data)) {
104
+ console.log(`Text file: ${file.path}`);
105
+ }
106
+ }
107
+ ```
108
+
109
+ ## Preset Config Format
110
+
111
+ Presets use `agentrules.json`:
112
+
113
+ ```json
114
+ {
115
+ "$schema": "https://agentrules.directory/schema/agentrules.json",
116
+ "name": "my-preset",
117
+ "title": "My Preset",
118
+ "version": "1.0.0",
119
+ "description": "Description here",
120
+ "author": { "name": "Your Name" },
121
+ "license": "MIT",
122
+ "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
+ }
130
+ }
131
+ ```
132
+
133
+ ## Development
134
+
135
+ ```bash
136
+ bun install
137
+ bun run build # build with tsdown
138
+ bun run test # run tests
139
+ bun run typecheck # type checking
140
+ ```
package/dist/index.d.ts CHANGED
@@ -1,31 +1,77 @@
1
1
  import { z } from "zod";
2
2
 
3
- //#region src/types.d.ts
4
- type PlatformId = "opencode" | "codex" | "claude" | "cursor";
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
+ //#region src/types/platform.d.ts
18
+ /**
19
+ * Single source of truth for platform IDs.
20
+ * Add new platforms here - types and config will follow.
21
+ */
22
+ declare const PLATFORM_ID_TUPLE: readonly ["opencode", "codex", "claude", "cursor"];
23
+ /** Union type of supported platform IDs, derived from PLATFORM_ID_TUPLE */
24
+ type PlatformId = (typeof PLATFORM_ID_TUPLE)[number];
25
+ /** List of supported platform IDs as a readonly tuple */
26
+ declare const PLATFORM_IDS: readonly ["opencode", "codex", "claude", "cursor"];
27
+ type PlatformConfig = {
28
+ /** Directory name for project installs (e.g., ".opencode") */
29
+ projectDir: string;
30
+ /** Path for global installs (e.g., "~/.config/opencode") */
31
+ globalDir: string;
32
+ };
33
+ /**
34
+ * Platform-specific configuration.
35
+ * Single source of truth for all platform paths.
36
+ */
37
+ declare const PLATFORMS: Record<PlatformId, PlatformConfig>;
38
+ /**
39
+ * Convention: preset files under this directory map to the platform config directory.
40
+ * e.g., `config/agent.md` → `.opencode/agent.md` (project) or `agent.md` (global)
41
+ */
42
+ declare const CONFIG_DIR_NAME = "config";
43
+ declare function isSupportedPlatform(value: string): value is PlatformId;
44
+ declare function normalizePlatformInput(value: string): PlatformId;
45
+
46
+ //#endregion
47
+ //#region src/types/definitions.d.ts
5
48
  type AuthorInfo = {
6
49
  name: string;
7
50
  email?: string;
8
51
  url?: string;
9
52
  };
10
- type PlatformSpecificConfig = {
53
+ type PlatformPresetConfig = {
54
+ /** Path to platform config files. Defaults to platform's projectDir (e.g., ".opencode") */
55
+ path?: string;
11
56
  features?: string[];
12
57
  installMessage?: string;
13
58
  };
14
59
  type PresetConfig = {
60
+ $schema?: string;
61
+ name: string;
15
62
  title: string;
16
- version: string;
63
+ version?: string;
17
64
  description: string;
18
65
  tags?: string[];
19
66
  author?: AuthorInfo;
20
- license?: string;
21
- primary?: PlatformId;
22
- platforms: Partial<Record<PlatformId, PlatformSpecificConfig>>;
67
+ license: string;
68
+ platforms: Partial<Record<PlatformId, PlatformPresetConfig>>;
23
69
  };
24
70
  type BundledFile = {
25
71
  path: string;
72
+ /** File size in bytes */
26
73
  size: number;
27
74
  checksum: string;
28
- encoding: "utf-8" | "base64";
29
75
  contents: string;
30
76
  };
31
77
  type RegistryBundle = {
@@ -36,7 +82,9 @@ type RegistryBundle = {
36
82
  description: string;
37
83
  tags: string[];
38
84
  author?: AuthorInfo;
39
- license?: string;
85
+ license: string;
86
+ licenseContent?: string;
87
+ readmeContent?: string;
40
88
  features?: string[];
41
89
  installMessage?: string;
42
90
  files: BundledFile[];
@@ -50,78 +98,42 @@ type RegistryEntry = {
50
98
  description: string;
51
99
  tags: string[];
52
100
  author?: AuthorInfo;
53
- license?: string;
101
+ license: string;
54
102
  features?: string[];
55
103
  installMessage?: string;
56
104
  bundlePath: string;
57
105
  fileCount: number;
58
- totalBytes: number;
59
- isPrimary?: boolean;
106
+ /** Total size of all files in bytes */
107
+ totalSize: number;
108
+ /** Whether the preset has a README.md */
109
+ hasReadmeContent?: boolean;
110
+ /** Whether the preset has a LICENSE.md */
111
+ hasLicenseContent?: boolean;
60
112
  };
61
113
  type RegistryData = {
62
114
  $schema: string;
63
115
  items: RegistryEntry[];
64
116
  };
65
117
  type RegistryIndex = Record<string, RegistryEntry>;
66
- type RegistryIndexItem = RegistryEntry; //#endregion
67
- //#region src/build-utils.d.ts
68
- declare function normalizeBundlePublicBase(value: string): string;
69
- declare function isAbsoluteUrl(value: string): boolean;
70
- declare function cleanInstallMessage(value: unknown): string | undefined;
71
- declare function encodeItemName(slug: string, platform: PlatformId): string;
72
- declare function validatePresetConfig(config: unknown, slug: string): PresetConfig;
73
- declare function collectBundledFiles(files: Record<string, string>): BundledFile[];
74
-
75
- //#endregion
76
- //#region src/bundle.d.ts
77
- declare function decodeBundledFile(file: BundledFile): Uint8Array;
78
- declare function verifyBundledFileChecksum(file: BundledFile, payload: ArrayBuffer | ArrayBufferView): Promise<void>;
79
- declare function isLikelyText(payload: ArrayBuffer | ArrayBufferView): boolean;
80
- declare function toUtf8String(payload: ArrayBuffer | ArrayBufferView): string;
81
-
82
- //#endregion
83
- //#region src/diff.d.ts
84
- type DiffPreviewOptions = {
85
- context?: number;
86
- maxLines?: number;
118
+ type RegistryIndexItem = RegistryEntry;
119
+ type RegistryFileInput = {
120
+ path: string;
121
+ contents: ArrayBuffer | ArrayBufferView | string;
87
122
  };
88
- declare function createDiffPreview(path: string, currentText: string, incomingText: string, options?: DiffPreviewOptions): string;
89
-
90
- //#endregion
91
- //#region src/encoding.d.ts
92
- declare function toPosixPath(pathValue: string): string;
93
- declare function encodeUtf8(value: string): Uint8Array<ArrayBuffer>;
94
- declare function decodeUtf8(payload: ArrayBuffer | ArrayBufferView): string;
95
- declare function toUint8Array(payload: ArrayBuffer | ArrayBufferView): Uint8Array<ArrayBufferLike>;
96
-
97
- //#endregion
98
- //#region src/paths.d.ts
99
- declare function normalizeBundlePath(value: string): string;
100
- declare function normalizePathFragment(value?: string): string | undefined;
101
- declare function maybeStripPrefix(pathInput: string, prefix?: string): string;
102
-
103
- //#endregion
104
- //#region src/platform.d.ts
105
- declare const PLATFORM_IDS: ["opencode", "codex", "claude", "cursor"];
106
- declare function isSupportedPlatform(value: string): value is PlatformId;
107
- declare function normalizePlatformInput(value: string): PlatformId;
108
-
109
- //#endregion
110
- //#region src/preset.d.ts
111
- declare function definePreset(config: PresetConfig): PresetConfig;
112
-
113
- //#endregion
114
- //#region src/registry.d.ts
115
- type FetchRegistryBundleResult = {
116
- bundle: RegistryBundle;
117
- etag: string | null;
123
+ type RegistryPlatformInput = {
124
+ platform: PlatformId;
125
+ files: RegistryFileInput[];
126
+ /** Install message from INSTALL.txt file */
127
+ installMessage?: string;
118
128
  };
119
- declare function fetchRegistryIndex(baseUrl: string): Promise<RegistryIndex>;
120
- declare function fetchRegistryBundle(baseUrl: string, bundlePath: string): Promise<FetchRegistryBundleResult>;
121
- declare function resolveRegistryEntry(index: RegistryIndex, input: string, explicitPlatform?: PlatformId): RegistryEntry;
122
-
123
- //#endregion
124
- //#region src/schema.d.ts
129
+ type RegistryPresetInput = {
130
+ slug: string;
131
+ config: PresetConfig;
132
+ platforms: RegistryPlatformInput[];
133
+ readmeContent?: string;
134
+ licenseContent?: string;
135
+ }; //#endregion
136
+ //#region src/types/schema.d.ts
125
137
  declare const platformIdSchema: z.ZodEnum<{
126
138
  opencode: "opencode";
127
139
  codex: "codex";
@@ -133,9 +145,29 @@ declare const authorSchema: z.ZodObject<{
133
145
  email: z.ZodOptional<z.ZodEmail>;
134
146
  url: z.ZodOptional<z.ZodURL>;
135
147
  }, z.core.$strict>;
136
- declare const platformPresetSchema: z.ZodObject<{
148
+ declare const titleSchema: z.ZodString;
149
+ declare const descriptionSchema: z.ZodString;
150
+ /** Validate a title string and return error message if invalid, undefined if valid */
151
+ declare function validateTitle(value: string): string | undefined;
152
+ /** Validate a description string and return error message if invalid, undefined if valid */
153
+ declare function validateDescription(value: string): string | undefined;
154
+ declare const slugSchema: z.ZodString;
155
+ /** Validate a slug string and return error message if invalid, undefined if valid */
156
+ declare function validateSlug(value: string): string | undefined;
157
+ declare const COMMON_LICENSES: readonly ["MIT", "Apache-2.0", "GPL-3.0-only", "BSD-3-Clause", "ISC", "Unlicense"];
158
+ type CommonLicense = (typeof COMMON_LICENSES)[number];
159
+ declare const licenseSchema: z.ZodString;
160
+ /** Validate a license string and return error message if invalid, undefined if valid */
161
+ 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
+ declare const presetConfigSchema: z.ZodObject<{
167
+ $schema: z.ZodOptional<z.ZodString>;
168
+ name: z.ZodString;
137
169
  title: z.ZodString;
138
- version: z.ZodString;
170
+ version: z.ZodOptional<z.ZodString>;
139
171
  description: z.ZodString;
140
172
  tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
141
173
  author: z.ZodOptional<z.ZodObject<{
@@ -143,15 +175,18 @@ declare const platformPresetSchema: z.ZodObject<{
143
175
  email: z.ZodOptional<z.ZodEmail>;
144
176
  url: z.ZodOptional<z.ZodURL>;
145
177
  }, z.core.$strict>>;
146
- license: z.ZodOptional<z.ZodString>;
147
- features: z.ZodOptional<z.ZodArray<z.ZodString>>;
148
- installMessage: z.ZodOptional<z.ZodString>;
178
+ 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>;
149
185
  }, z.core.$strict>;
150
186
  declare const bundledFileSchema: z.ZodObject<{
151
187
  path: z.ZodString;
152
188
  size: z.ZodNumber;
153
189
  checksum: z.ZodString;
154
- encoding: z.ZodUnion<readonly [z.ZodLiteral<"utf-8">, z.ZodLiteral<"base64">]>;
155
190
  contents: z.ZodString;
156
191
  }, z.core.$strip>;
157
192
  declare const registryBundleSchema: z.ZodObject<{
@@ -171,30 +206,30 @@ declare const registryBundleSchema: z.ZodObject<{
171
206
  email: z.ZodOptional<z.ZodEmail>;
172
207
  url: z.ZodOptional<z.ZodURL>;
173
208
  }, z.core.$strict>>;
174
- license: z.ZodOptional<z.ZodString>;
209
+ license: z.ZodString;
210
+ licenseContent: z.ZodOptional<z.ZodString>;
211
+ readmeContent: z.ZodOptional<z.ZodString>;
175
212
  features: z.ZodOptional<z.ZodArray<z.ZodString>>;
176
213
  installMessage: z.ZodOptional<z.ZodString>;
177
214
  files: z.ZodArray<z.ZodObject<{
178
215
  path: z.ZodString;
179
216
  size: z.ZodNumber;
180
217
  checksum: z.ZodString;
181
- encoding: z.ZodUnion<readonly [z.ZodLiteral<"utf-8">, z.ZodLiteral<"base64">]>;
182
218
  contents: z.ZodString;
183
219
  }, z.core.$strip>>;
184
220
  }, z.core.$strip>;
185
221
  declare const registryEntrySchema: z.ZodObject<{
222
+ features: z.ZodOptional<z.ZodArray<z.ZodString>>;
186
223
  title: z.ZodString;
187
- version: z.ZodString;
188
224
  description: z.ZodString;
225
+ license: z.ZodString;
226
+ version: z.ZodString;
189
227
  tags: z.ZodArray<z.ZodString>;
190
228
  author: z.ZodOptional<z.ZodObject<{
191
229
  name: z.ZodString;
192
230
  email: z.ZodOptional<z.ZodEmail>;
193
231
  url: z.ZodOptional<z.ZodURL>;
194
232
  }, z.core.$strict>>;
195
- license: z.ZodOptional<z.ZodString>;
196
- features: z.ZodOptional<z.ZodArray<z.ZodString>>;
197
- installMessage: z.ZodOptional<z.ZodString>;
198
233
  platform: z.ZodEnum<{
199
234
  opencode: "opencode";
200
235
  codex: "codex";
@@ -205,21 +240,22 @@ declare const registryEntrySchema: z.ZodObject<{
205
240
  name: z.ZodString;
206
241
  bundlePath: z.ZodString;
207
242
  fileCount: z.ZodNumber;
208
- totalBytes: z.ZodNumber;
243
+ totalSize: z.ZodNumber;
244
+ hasReadmeContent: z.ZodOptional<z.ZodBoolean>;
245
+ hasLicenseContent: z.ZodOptional<z.ZodBoolean>;
209
246
  }, z.core.$strip>;
210
247
  declare const registryIndexSchema: z.ZodRecord<z.ZodString, z.ZodObject<{
248
+ features: z.ZodOptional<z.ZodArray<z.ZodString>>;
211
249
  title: z.ZodString;
212
- version: z.ZodString;
213
250
  description: z.ZodString;
251
+ license: z.ZodString;
252
+ version: z.ZodString;
214
253
  tags: z.ZodArray<z.ZodString>;
215
254
  author: z.ZodOptional<z.ZodObject<{
216
255
  name: z.ZodString;
217
256
  email: z.ZodOptional<z.ZodEmail>;
218
257
  url: z.ZodOptional<z.ZodURL>;
219
258
  }, z.core.$strict>>;
220
- license: z.ZodOptional<z.ZodString>;
221
- features: z.ZodOptional<z.ZodArray<z.ZodString>>;
222
- installMessage: z.ZodOptional<z.ZodString>;
223
259
  platform: z.ZodEnum<{
224
260
  opencode: "opencode";
225
261
  codex: "codex";
@@ -230,8 +266,73 @@ declare const registryIndexSchema: z.ZodRecord<z.ZodString, z.ZodObject<{
230
266
  name: z.ZodString;
231
267
  bundlePath: z.ZodString;
232
268
  fileCount: z.ZodNumber;
233
- totalBytes: z.ZodNumber;
269
+ totalSize: z.ZodNumber;
270
+ hasReadmeContent: z.ZodOptional<z.ZodBoolean>;
271
+ hasLicenseContent: z.ZodOptional<z.ZodBoolean>;
234
272
  }, z.core.$strip>>;
235
273
 
236
274
  //#endregion
237
- export { AuthorInfo, BundledFile, DiffPreviewOptions, FetchRegistryBundleResult, PLATFORM_IDS, PlatformId, PlatformSpecificConfig, PresetConfig, RegistryBundle, RegistryData, RegistryEntry, RegistryIndex, RegistryIndexItem, authorSchema, bundledFileSchema, cleanInstallMessage, collectBundledFiles, createDiffPreview, decodeBundledFile, decodeUtf8, definePreset, encodeItemName, encodeUtf8, fetchRegistryBundle, fetchRegistryIndex, isAbsoluteUrl, isLikelyText, isSupportedPlatform, maybeStripPrefix, normalizeBundlePath, normalizeBundlePublicBase, normalizePathFragment, normalizePlatformInput, platformIdSchema, platformPresetSchema, registryBundleSchema, registryEntrySchema, registryIndexSchema, resolveRegistryEntry, toPosixPath, toUint8Array, toUtf8String, validatePresetConfig, verifyBundledFileChecksum };
275
+ //#region src/builder/registry.d.ts
276
+ type BuildRegistryDataOptions = {
277
+ presets: RegistryPresetInput[];
278
+ bundleBase?: string;
279
+ /** Override the auto-generated version. If not provided, uses current UTC date. */
280
+ version?: string;
281
+ };
282
+ type BuildRegistryDataResult = {
283
+ entries: RegistryEntry[];
284
+ index: RegistryIndex;
285
+ bundles: RegistryBundle[];
286
+ };
287
+ declare function buildRegistryData(options: BuildRegistryDataOptions): BuildRegistryDataResult;
288
+
289
+ //#endregion
290
+ //#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
+ declare function cleanInstallMessage(value: unknown): string | undefined;
299
+ declare function encodeItemName(slug: string, platform: PlatformId): string;
300
+ declare function validatePresetConfig(config: unknown, slug: string): PresetConfig;
301
+ declare function collectBundledFiles(files: Record<string, string>): BundledFile[];
302
+
303
+ //#endregion
304
+ //#region src/client/bundle.d.ts
305
+ declare function decodeBundledFile(file: BundledFile): Uint8Array;
306
+ declare function verifyBundledFileChecksum(file: BundledFile, payload: ArrayBuffer | ArrayBufferView): Promise<void>;
307
+ declare function isLikelyText(payload: ArrayBuffer | ArrayBufferView): boolean;
308
+ declare function toUtf8String(payload: ArrayBuffer | ArrayBufferView): string;
309
+
310
+ //#endregion
311
+ //#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;
315
+
316
+ //#endregion
317
+ //#region src/utils/diff.d.ts
318
+ type DiffPreviewOptions = {
319
+ context?: number;
320
+ maxLines?: number;
321
+ };
322
+ declare function createDiffPreview(path: string, currentText: string, incomingText: string, options?: DiffPreviewOptions): string;
323
+
324
+ //#endregion
325
+ //#region src/utils/encoding.d.ts
326
+ declare function toPosixPath(pathValue: string): string;
327
+ declare function encodeUtf8(value: string): Uint8Array<ArrayBuffer>;
328
+ declare function decodeUtf8(payload: ArrayBuffer | ArrayBufferView): string;
329
+ declare function toUint8Array(payload: ArrayBuffer | ArrayBufferView): Uint8Array<ArrayBufferLike>;
330
+
331
+ //#endregion
332
+ //#region src/utils/paths.d.ts
333
+ declare function normalizeBundlePath(value: string): string;
334
+ declare function normalizePathFragment(value?: string): string | undefined;
335
+ declare function maybeStripPrefix(pathInput: string, prefix?: string): string;
336
+
337
+ //#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 };
package/dist/index.js CHANGED
@@ -1,7 +1,180 @@
1
- import { createTwoFilesPatch } from "diff";
1
+ import { createHash } from "crypto";
2
2
  import { z } from "zod";
3
+ import { createTwoFilesPatch } from "diff";
3
4
 
4
- //#region src/encoding.ts
5
+ //#region src/types/constants.ts
6
+ /**
7
+ * Shared constants for agentrules presets and registry.
8
+ */
9
+ /** Filename for preset configuration */
10
+ const PRESET_CONFIG_FILENAME = "agentrules.json";
11
+ /** JSON Schema URL for preset configuration */
12
+ const PRESET_SCHEMA_URL = "https://agentrules.directory/schema/agentrules.json";
13
+
14
+ //#endregion
15
+ //#region src/types/platform.ts
16
+ /**
17
+ * Single source of truth for platform IDs.
18
+ * Add new platforms here - types and config will follow.
19
+ */
20
+ const PLATFORM_ID_TUPLE = [
21
+ "opencode",
22
+ "codex",
23
+ "claude",
24
+ "cursor"
25
+ ];
26
+ /** List of supported platform IDs as a readonly tuple */
27
+ const PLATFORM_IDS = PLATFORM_ID_TUPLE;
28
+ /**
29
+ * Platform-specific configuration.
30
+ * Single source of truth for all platform paths.
31
+ */
32
+ const PLATFORMS = {
33
+ opencode: {
34
+ projectDir: ".opencode",
35
+ globalDir: "~/.config/opencode"
36
+ },
37
+ codex: {
38
+ projectDir: ".codex",
39
+ globalDir: "~/.codex"
40
+ },
41
+ claude: {
42
+ projectDir: ".claude",
43
+ globalDir: "~/.claude"
44
+ },
45
+ cursor: {
46
+ projectDir: ".cursor",
47
+ globalDir: "~/.cursor"
48
+ }
49
+ };
50
+ /**
51
+ * Convention: preset files under this directory map to the platform config directory.
52
+ * e.g., `config/agent.md` → `.opencode/agent.md` (project) or `agent.md` (global)
53
+ */
54
+ const CONFIG_DIR_NAME = "config";
55
+ function isSupportedPlatform(value) {
56
+ return PLATFORM_ID_TUPLE.includes(value);
57
+ }
58
+ function normalizePlatformInput(value) {
59
+ const normalized = value.toLowerCase();
60
+ if (isSupportedPlatform(normalized)) return normalized;
61
+ throw new Error(`Unknown platform "${value}". Supported platforms: ${PLATFORM_IDS.join(", ")}.`);
62
+ }
63
+
64
+ //#endregion
65
+ //#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+)?$/;
67
+ 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
+ const titleSchema = z.string().trim().min(1).max(120);
74
+ const descriptionSchema = z.string().trim().min(1).max(500);
75
+ /** Validate a title string and return error message if invalid, undefined if valid */
76
+ function validateTitle(value) {
77
+ const trimmed = value.trim();
78
+ if (!trimmed) return "Title is required";
79
+ if (trimmed.length > 120) return "Title must be 120 characters or less";
80
+ return;
81
+ }
82
+ /** Validate a description string and return error message if invalid, undefined if valid */
83
+ function validateDescription(value) {
84
+ const trimmed = value.trim();
85
+ if (!trimmed) return "Description is required";
86
+ if (trimmed.length > 500) return "Description must be 500 characters or less";
87
+ return;
88
+ }
89
+ const versionSchema = z.string().trim().regex(DATE_VERSION_REGEX, "Version must be date-based (YYYY.MM.DD or YYYY.MM.DD-N)");
90
+ const tagSchema = z.string().trim().min(1).max(48);
91
+ const tagsSchema = z.array(tagSchema).max(10);
92
+ const featureSchema = z.string().trim().min(1).max(160);
93
+ const featuresSchema = z.array(featureSchema).max(10);
94
+ const installMessageSchema = z.string().trim().max(4e3);
95
+ const contentSchema = z.string();
96
+ const SLUG_REGEX = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
97
+ const SLUG_ERROR = "Must be lowercase alphanumeric with hyphens (e.g., my-preset)";
98
+ const slugSchema = z.string().trim().min(1).max(64).regex(SLUG_REGEX, SLUG_ERROR);
99
+ /** Validate a slug string and return error message if invalid, undefined if valid */
100
+ function validateSlug(value) {
101
+ const trimmed = value.trim();
102
+ if (!trimmed) return "Name is required";
103
+ if (trimmed.length > 64) return "Name must be 64 characters or less";
104
+ if (!SLUG_REGEX.test(trimmed)) return SLUG_ERROR;
105
+ return;
106
+ }
107
+ const COMMON_LICENSES = [
108
+ "MIT",
109
+ "Apache-2.0",
110
+ "GPL-3.0-only",
111
+ "BSD-3-Clause",
112
+ "ISC",
113
+ "Unlicense"
114
+ ];
115
+ const licenseSchema = z.string().trim().min(1).max(128);
116
+ /** Validate a license string and return error message if invalid, undefined if valid */
117
+ function validateLicense(value) {
118
+ const trimmed = value.trim();
119
+ if (!trimmed) return "License is required";
120
+ if (trimmed.length > 128) return "License must be 128 characters or less";
121
+ return;
122
+ }
123
+ 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
+ const presetConfigSchema = z.object({
130
+ $schema: z.string().optional(),
131
+ name: slugSchema,
132
+ title: titleSchema,
133
+ version: versionSchema.optional(),
134
+ description: descriptionSchema,
135
+ tags: tagsSchema.optional(),
136
+ author: authorSchema.optional(),
137
+ license: licenseSchema,
138
+ platforms: platformsObjectSchema
139
+ }).strict();
140
+ const bundledFileSchema = z.object({
141
+ path: z.string().min(1),
142
+ size: z.number().int().nonnegative(),
143
+ checksum: z.string().length(64),
144
+ contents: z.string()
145
+ });
146
+ const registryBundleSchema = z.object({
147
+ slug: z.string().trim().min(1),
148
+ platform: platformIdSchema,
149
+ title: titleSchema,
150
+ version: versionSchema,
151
+ description: descriptionSchema,
152
+ tags: tagsSchema,
153
+ author: authorSchema.optional(),
154
+ license: licenseSchema,
155
+ licenseContent: contentSchema.optional(),
156
+ readmeContent: contentSchema.optional(),
157
+ features: featuresSchema.optional(),
158
+ installMessage: installMessageSchema.optional(),
159
+ files: z.array(bundledFileSchema).min(1)
160
+ });
161
+ const registryEntrySchema = registryBundleSchema.omit({
162
+ files: true,
163
+ readmeContent: true,
164
+ licenseContent: true,
165
+ installMessage: true
166
+ }).extend({
167
+ name: z.string().trim().min(1),
168
+ bundlePath: z.string().trim().min(1),
169
+ fileCount: z.number().int().nonnegative(),
170
+ totalSize: z.number().int().nonnegative(),
171
+ hasReadmeContent: z.boolean().optional(),
172
+ hasLicenseContent: z.boolean().optional()
173
+ });
174
+ const registryIndexSchema = z.record(z.string(), registryEntrySchema);
175
+
176
+ //#endregion
177
+ //#region src/utils/encoding.ts
5
178
  function toPosixPath(pathValue) {
6
179
  return pathValue.split("\\").join("/");
7
180
  }
@@ -21,7 +194,17 @@ function toUint8Array(payload) {
21
194
  }
22
195
 
23
196
  //#endregion
24
- //#region src/build-utils.ts
197
+ //#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
+ }
25
208
  function normalizeBundlePublicBase(value) {
26
209
  const trimmed = value.trim();
27
210
  if (!trimmed) throw new Error("--bundle-base must be a non-empty string");
@@ -43,11 +226,13 @@ function encodeItemName(slug, platform) {
43
226
  return `${slug}.${platform}`;
44
227
  }
45
228
  function validatePresetConfig(config, slug) {
46
- if (!config || typeof config !== "object") throw new Error(`Invalid preset config export for ${slug}`);
229
+ if (!config || typeof config !== "object") throw new Error(`Invalid preset config for ${slug}`);
47
230
  const preset = config;
231
+ if (!preset.name || typeof preset.name !== "string") throw new Error(`Preset ${slug} is missing a name`);
48
232
  if (!preset.title || typeof preset.title !== "string") throw new Error(`Preset ${slug} is missing a title`);
49
- if (!preset.version || typeof preset.version !== "string") throw new Error(`Preset ${slug} is missing a version`);
233
+ if (preset.version !== void 0 && typeof preset.version !== "string") throw new Error(`Preset ${slug} has invalid version (must be string or omitted)`);
50
234
  if (!preset.description || typeof preset.description !== "string") throw new Error(`Preset ${slug} is missing a description`);
235
+ if (!preset.license || typeof preset.license !== "string") throw new Error(`Preset ${slug} is missing a license (SPDX identifier required)`);
51
236
  if (!preset.platforms || typeof preset.platforms !== "object") throw new Error(`Preset ${slug} is missing platforms map`);
52
237
  return preset;
53
238
  }
@@ -59,20 +244,125 @@ function collectBundledFiles(files) {
59
244
  path: normalizedPath,
60
245
  size: payload.length,
61
246
  checksum: "",
62
- encoding: "utf-8",
63
247
  contents
64
248
  };
65
249
  }).sort((a, b) => a.path.localeCompare(b.path));
66
250
  }
67
251
 
68
252
  //#endregion
69
- //#region src/bundle.ts
70
- const BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
71
- const BASE64_LOOKUP = Object.fromEntries(Array.from(BASE64_ALPHABET).map((char, index) => [char, index]));
253
+ //#region src/builder/registry.ts
254
+ const NAME_PATTERN = /^[a-z0-9-]+$/;
255
+ function buildRegistryData(options) {
256
+ const bundleBase = normalizeBundlePublicBase(options.bundleBase ?? "/r");
257
+ const buildVersion = options.version ?? generateDateVersion();
258
+ const entries = [];
259
+ const bundles = [];
260
+ for (const presetInput of options.presets) {
261
+ if (!NAME_PATTERN.test(presetInput.slug)) throw new Error(`Invalid slug "${presetInput.slug}". Slugs must be lowercase kebab-case.`);
262
+ 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
+ }
311
+ }
312
+ sortBySlugAndPlatform(entries);
313
+ sortBySlugAndPlatform(bundles);
314
+ const index = entries.reduce((acc, entry) => {
315
+ acc[entry.name] = entry;
316
+ return acc;
317
+ }, {});
318
+ return {
319
+ entries,
320
+ index,
321
+ bundles
322
+ };
323
+ }
324
+ function createBundledFilesFromInputs(files) {
325
+ return files.map((file) => {
326
+ const payload = normalizeFilePayload(file.contents);
327
+ const contents = encodeFilePayload(payload, file.path);
328
+ const checksum = createHash("sha256").update(payload).digest("hex");
329
+ return {
330
+ path: toPosixPath(file.path),
331
+ size: payload.length,
332
+ checksum,
333
+ contents
334
+ };
335
+ }).sort((a, b) => a.path.localeCompare(b.path));
336
+ }
337
+ 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);
342
+ }
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;
347
+ }
348
+ function getBundlePublicPath(base, slug, platform, version) {
349
+ const prefix = base === "/" ? "" : base;
350
+ return `${prefix}/${slug}/${platform}.${version}.json`;
351
+ }
352
+ function ensureKnownPlatform(platform, slug) {
353
+ if (!isSupportedPlatform(platform)) throw new Error(`Unknown platform "${platform}" in ${slug}. Supported: ${PLATFORM_IDS.join(", ")}`);
354
+ }
355
+ function sortBySlugAndPlatform(items) {
356
+ items.sort((a, b) => {
357
+ if (a.slug === b.slug) return a.platform.localeCompare(b.platform);
358
+ return a.slug.localeCompare(b.slug);
359
+ });
360
+ }
361
+
362
+ //#endregion
363
+ //#region src/client/bundle.ts
72
364
  function decodeBundledFile(file) {
73
- if (file.encoding === "utf-8") return encodeUtf8(file.contents);
74
- if (file.encoding === "base64") return decodeBase64(file.contents);
75
- throw new Error(`Unsupported encoding "${file.encoding}" for ${file.path}.`);
365
+ return encodeUtf8(file.contents);
76
366
  }
77
367
  async function verifyBundledFileChecksum(file, payload) {
78
368
  const bytes = toUint8Array(payload);
@@ -92,39 +382,6 @@ function isLikelyText(payload) {
92
382
  function toUtf8String(payload) {
93
383
  return decodeUtf8(payload);
94
384
  }
95
- function decodeBase64(input) {
96
- const sanitized = input.replace(/[^A-Za-z0-9+/=]/g, "");
97
- if (sanitized.length % 4 !== 0) throw new Error("Invalid base64 payload length.");
98
- let outputLength = sanitized.length / 4 * 3;
99
- if (sanitized.endsWith("==")) outputLength -= 2;
100
- else if (sanitized.endsWith("=")) outputLength -= 1;
101
- const bytes = new Uint8Array(outputLength);
102
- let byteIndex = 0;
103
- for (let i = 0; i < sanitized.length; i += 4) {
104
- const chunk = sanitized.slice(i, i + 4);
105
- const enc1 = decodeBase64Char(chunk[0]);
106
- const enc2 = decodeBase64Char(chunk[1]);
107
- const enc3 = chunk[2] === "=" ? 0 : decodeBase64Char(chunk[2]);
108
- const enc4 = chunk[3] === "=" ? 0 : decodeBase64Char(chunk[3]);
109
- const combined = enc1 * 262144 + enc2 * 4096 + enc3 * 64 + enc4;
110
- bytes[byteIndex] = Math.floor(combined / 65536) % 256;
111
- byteIndex += 1;
112
- if (chunk[2] !== "=") {
113
- bytes[byteIndex] = Math.floor(combined / 256) % 256;
114
- byteIndex += 1;
115
- }
116
- if (chunk[3] !== "=") {
117
- bytes[byteIndex] = combined % 256;
118
- byteIndex += 1;
119
- }
120
- }
121
- return bytes;
122
- }
123
- function decodeBase64Char(char) {
124
- const value = BASE64_LOOKUP[char];
125
- if (value === void 0) throw new Error(`Invalid base64 character "${char}".`);
126
- return value;
127
- }
128
385
  async function sha256Hex(payload) {
129
386
  const crypto = globalThis.crypto;
130
387
  if (!crypto?.subtle) throw new Error("SHA-256 hashing requires Web Crypto API support.");
@@ -133,60 +390,7 @@ async function sha256Hex(payload) {
133
390
  }
134
391
 
135
392
  //#endregion
136
- //#region src/diff.ts
137
- const DEFAULT_CONTEXT = 2;
138
- const DEFAULT_MAX_LINES = 40;
139
- function createDiffPreview(path, currentText, incomingText, options = {}) {
140
- const patch = createTwoFilesPatch(`${path} (current)`, `${path} (incoming)`, currentText, incomingText, void 0, void 0, { context: options.context ?? DEFAULT_CONTEXT });
141
- const lines = patch.trim().split("\n");
142
- const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
143
- const limited = lines.slice(0, maxLines);
144
- if (lines.length > maxLines) limited.push("...");
145
- return limited.join("\n");
146
- }
147
-
148
- //#endregion
149
- //#region src/paths.ts
150
- function normalizeBundlePath(value) {
151
- return value.replace(/\\/g, "/").replace(/^\.\/+/, "").replace(/^\/+/, "");
152
- }
153
- function normalizePathFragment(value) {
154
- if (!value) return;
155
- const normalized = value.replace(/\\/g, "/").replace(/^\/+/, "");
156
- return normalized.replace(/\/+$/, "");
157
- }
158
- function maybeStripPrefix(pathInput, prefix) {
159
- if (!prefix) return pathInput;
160
- if (pathInput === prefix) return "";
161
- if (pathInput.startsWith(`${prefix}/`)) return pathInput.slice(prefix.length + 1);
162
- return pathInput;
163
- }
164
-
165
- //#endregion
166
- //#region src/platform.ts
167
- const PLATFORM_IDS = [
168
- "opencode",
169
- "codex",
170
- "claude",
171
- "cursor"
172
- ];
173
- function isSupportedPlatform(value) {
174
- return PLATFORM_IDS.includes(value);
175
- }
176
- function normalizePlatformInput(value) {
177
- const normalized = value.toLowerCase();
178
- if (isSupportedPlatform(normalized)) return normalized;
179
- throw new Error(`Unknown platform "${value}". Supported platforms: ${PLATFORM_IDS.join(", ")}.`);
180
- }
181
-
182
- //#endregion
183
- //#region src/preset.ts
184
- function definePreset(config) {
185
- return config;
186
- }
187
-
188
- //#endregion
189
- //#region src/registry.ts
393
+ //#region src/client/registry.ts
190
394
  async function fetchRegistryIndex(baseUrl) {
191
395
  const indexUrl = new URL("registry.index.json", baseUrl);
192
396
  const response = await fetch(indexUrl);
@@ -202,11 +406,7 @@ async function fetchRegistryBundle(baseUrl, bundlePath) {
202
406
  const response = await fetch(bundleUrl);
203
407
  if (!response.ok) throw new Error(`Failed to download bundle (${response.status} ${response.statusText}).`);
204
408
  try {
205
- const bundle = await response.json();
206
- return {
207
- bundle,
208
- etag: response.headers.get("etag")
209
- };
409
+ return await response.json();
210
410
  } catch (error) {
211
411
  throw new Error(`Unable to parse bundle JSON: ${error.message}`);
212
412
  }
@@ -222,7 +422,7 @@ function resolveRegistryEntry(index, input, explicitPlatform) {
222
422
  if (!platform) {
223
423
  const parts = normalizedInput.split(".");
224
424
  const maybePlatform = parts.at(-1);
225
- if (maybePlatform && PLATFORM_IDS.includes(maybePlatform)) {
425
+ if (maybePlatform && isSupportedPlatform(maybePlatform)) {
226
426
  platform = maybePlatform;
227
427
  slugHint = parts.slice(0, -1).join(".");
228
428
  }
@@ -242,65 +442,34 @@ function resolveRegistryEntry(index, input, explicitPlatform) {
242
442
  }
243
443
 
244
444
  //#endregion
245
- //#region src/schema.ts
246
- const SEMVER_REGEX = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-(?:0|[1-9A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9A-Za-z-][0-9A-Za-z-]*))*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
247
- const platformIdSchema = z.enum([
248
- "opencode",
249
- "codex",
250
- "claude",
251
- "cursor"
252
- ]);
253
- const authorSchema = z.object({
254
- name: z.string().trim().min(1),
255
- email: z.email().trim().optional(),
256
- url: z.url().trim().optional()
257
- }).strict();
258
- const titleSchema = z.string().trim().min(1).max(120);
259
- const descriptionSchema = z.string().trim().min(1).max(500);
260
- const versionSchema = z.string().trim().regex(SEMVER_REGEX, "Version must follow semantic versioning");
261
- const tagSchema = z.string().trim().min(1).max(48);
262
- const tagsSchema = z.array(tagSchema).max(10);
263
- const featureSchema = z.string().trim().min(1).max(160);
264
- const featuresSchema = z.array(featureSchema).max(10);
265
- const installMessageSchema = z.string().trim().max(4e3);
266
- const licenseSchema = z.string().trim().max(80);
267
- const platformPresetSchema = z.object({
268
- title: titleSchema,
269
- version: versionSchema,
270
- description: descriptionSchema,
271
- tags: tagsSchema.optional(),
272
- author: authorSchema.optional(),
273
- license: licenseSchema.optional(),
274
- features: featuresSchema.optional(),
275
- installMessage: installMessageSchema.optional()
276
- }).strict();
277
- const bundledFileSchema = z.object({
278
- path: z.string().min(1),
279
- size: z.number().int().nonnegative(),
280
- checksum: z.string().length(64),
281
- encoding: z.union([z.literal("utf-8"), z.literal("base64")]),
282
- contents: z.string()
283
- });
284
- const registryBundleSchema = z.object({
285
- slug: z.string().trim().min(1),
286
- platform: platformIdSchema,
287
- title: titleSchema,
288
- version: versionSchema,
289
- description: descriptionSchema,
290
- tags: tagsSchema,
291
- author: authorSchema.optional(),
292
- license: licenseSchema.optional(),
293
- features: featuresSchema.optional(),
294
- installMessage: installMessageSchema.optional(),
295
- files: z.array(bundledFileSchema).min(1)
296
- });
297
- const registryEntrySchema = registryBundleSchema.omit({ files: true }).extend({
298
- name: z.string().trim().min(1),
299
- bundlePath: z.string().trim().min(1),
300
- fileCount: z.number().int().nonnegative(),
301
- totalBytes: z.number().int().nonnegative()
302
- });
303
- const registryIndexSchema = z.record(z.string(), registryEntrySchema);
445
+ //#region src/utils/diff.ts
446
+ const DEFAULT_CONTEXT = 2;
447
+ const DEFAULT_MAX_LINES = 40;
448
+ function createDiffPreview(path, currentText, incomingText, options = {}) {
449
+ const patch = createTwoFilesPatch(`${path} (current)`, `${path} (incoming)`, currentText, incomingText, void 0, void 0, { context: options.context ?? DEFAULT_CONTEXT });
450
+ const lines = patch.trim().split("\n");
451
+ const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
452
+ const limited = lines.slice(0, maxLines);
453
+ if (lines.length > maxLines) limited.push("...");
454
+ return limited.join("\n");
455
+ }
456
+
457
+ //#endregion
458
+ //#region src/utils/paths.ts
459
+ function normalizeBundlePath(value) {
460
+ return value.replace(/\\/g, "/").replace(/^\.\/+/, "").replace(/^\/+/, "");
461
+ }
462
+ function normalizePathFragment(value) {
463
+ if (!value) return;
464
+ const normalized = value.replace(/\\/g, "/").replace(/^\/+/, "");
465
+ return normalized.replace(/\/+$/, "");
466
+ }
467
+ function maybeStripPrefix(pathInput, prefix) {
468
+ if (!prefix) return pathInput;
469
+ if (pathInput === prefix) return "";
470
+ if (pathInput.startsWith(`${prefix}/`)) return pathInput.slice(prefix.length + 1);
471
+ return pathInput;
472
+ }
304
473
 
305
474
  //#endregion
306
- export { PLATFORM_IDS, authorSchema, bundledFileSchema, cleanInstallMessage, collectBundledFiles, createDiffPreview, decodeBundledFile, decodeUtf8, definePreset, encodeItemName, encodeUtf8, fetchRegistryBundle, fetchRegistryIndex, isAbsoluteUrl, isLikelyText, isSupportedPlatform, maybeStripPrefix, normalizeBundlePath, normalizeBundlePublicBase, normalizePathFragment, normalizePlatformInput, platformIdSchema, platformPresetSchema, registryBundleSchema, registryEntrySchema, registryIndexSchema, resolveRegistryEntry, toPosixPath, toUint8Array, toUtf8String, validatePresetConfig, verifyBundledFileChecksum };
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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentrules/core",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "author": "Brian Cheung <bcheung.dev@gmail.com> (https://github.com/bcheung)",
5
5
  "license": "MIT",
6
6
  "homepage": "https://docs.agentrules.directory",
@@ -51,4 +51,4 @@
51
51
  "tsdown": "^0.9.0",
52
52
  "typescript": "5.7.2"
53
53
  }
54
- }
54
+ }