@agentrules/core 0.0.3 → 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 +25 -34
- package/dist/index.d.ts +189 -101
- package/dist/index.js +264 -162
- package/package.json +7 -6
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:
|
|
33
|
+
version: 1,
|
|
35
34
|
description: "A helpful preset",
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
license: "MIT",
|
|
36
|
+
platform: "opencode",
|
|
37
|
+
path: ".opencode",
|
|
39
38
|
},
|
|
40
|
-
|
|
41
|
-
{
|
|
42
|
-
|
|
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 →
|
|
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
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
"
|
|
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":
|
|
108
|
+
"version": 1,
|
|
119
109
|
"description": "Description here",
|
|
120
|
-
"author": { "name": "Your Name" },
|
|
121
110
|
"license": "MIT",
|
|
122
111
|
"tags": ["starter", "typescript"],
|
|
123
|
-
"
|
|
124
|
-
|
|
125
|
-
|
|
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
|
@@ -31,26 +31,18 @@ declare function isSupportedPlatform(value: string): value is PlatformId;
|
|
|
31
31
|
declare function normalizePlatformInput(value: string): PlatformId;
|
|
32
32
|
//#endregion
|
|
33
33
|
//#region src/types/definitions.d.ts
|
|
34
|
-
type AuthorInfo = {
|
|
35
|
-
name: string;
|
|
36
|
-
email?: string;
|
|
37
|
-
url?: string;
|
|
38
|
-
};
|
|
39
|
-
type PlatformPresetConfig = {
|
|
40
|
-
path: string;
|
|
41
|
-
features?: string[];
|
|
42
|
-
installMessage?: string;
|
|
43
|
-
};
|
|
44
34
|
type PresetConfig = {
|
|
45
35
|
$schema?: string;
|
|
46
36
|
name: string;
|
|
47
37
|
title: string;
|
|
48
|
-
version
|
|
38
|
+
version?: number;
|
|
49
39
|
description: string;
|
|
50
40
|
tags?: string[];
|
|
51
|
-
|
|
52
|
-
license
|
|
53
|
-
|
|
41
|
+
features?: string[];
|
|
42
|
+
license: string;
|
|
43
|
+
platform: PlatformId;
|
|
44
|
+
/** Path to config files. Defaults to platform's projectDir (e.g., ".claude") */
|
|
45
|
+
path?: string;
|
|
54
46
|
};
|
|
55
47
|
type BundledFile = {
|
|
56
48
|
path: string;
|
|
@@ -59,19 +51,32 @@ type BundledFile = {
|
|
|
59
51
|
checksum: string;
|
|
60
52
|
contents: string;
|
|
61
53
|
};
|
|
62
|
-
|
|
54
|
+
/**
|
|
55
|
+
* What clients send to publish a preset.
|
|
56
|
+
* Version is optional major version. Registry assigns full MAJOR.MINOR.
|
|
57
|
+
*/
|
|
58
|
+
type PublishInput = {
|
|
63
59
|
slug: string;
|
|
64
60
|
platform: PlatformId;
|
|
65
61
|
title: string;
|
|
66
|
-
version: string;
|
|
67
62
|
description: string;
|
|
68
63
|
tags: string[];
|
|
69
|
-
|
|
70
|
-
|
|
64
|
+
license: string;
|
|
65
|
+
licenseContent?: string;
|
|
66
|
+
readmeContent?: string;
|
|
71
67
|
features?: string[];
|
|
72
68
|
installMessage?: string;
|
|
73
|
-
readme?: string;
|
|
74
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;
|
|
75
80
|
};
|
|
76
81
|
type RegistryEntry = {
|
|
77
82
|
name: string;
|
|
@@ -81,36 +86,25 @@ type RegistryEntry = {
|
|
|
81
86
|
version: string;
|
|
82
87
|
description: string;
|
|
83
88
|
tags: string[];
|
|
84
|
-
|
|
85
|
-
license?: string;
|
|
89
|
+
license: string;
|
|
86
90
|
features?: string[];
|
|
87
|
-
|
|
88
|
-
bundlePath: string;
|
|
91
|
+
bundleUrl: string;
|
|
89
92
|
fileCount: number;
|
|
90
|
-
/** Total size of all files in bytes */
|
|
91
93
|
totalSize: number;
|
|
92
|
-
/** Whether the preset has a README */
|
|
93
|
-
hasReadme?: boolean;
|
|
94
|
-
};
|
|
95
|
-
type RegistryData = {
|
|
96
|
-
$schema: string;
|
|
97
|
-
items: RegistryEntry[];
|
|
98
94
|
};
|
|
99
95
|
type RegistryIndex = Record<string, RegistryEntry>;
|
|
100
|
-
type RegistryIndexItem = RegistryEntry;
|
|
101
96
|
type RegistryFileInput = {
|
|
102
97
|
path: string;
|
|
103
98
|
contents: ArrayBuffer | ArrayBufferView | string;
|
|
104
99
|
};
|
|
105
|
-
type RegistryPlatformInput = {
|
|
106
|
-
platform: PlatformId;
|
|
107
|
-
files: RegistryFileInput[];
|
|
108
|
-
};
|
|
109
100
|
type RegistryPresetInput = {
|
|
110
101
|
slug: string;
|
|
111
102
|
config: PresetConfig;
|
|
112
|
-
|
|
113
|
-
|
|
103
|
+
files: RegistryFileInput[];
|
|
104
|
+
/** Install message from INSTALL.txt file */
|
|
105
|
+
installMessage?: string;
|
|
106
|
+
readmeContent?: string;
|
|
107
|
+
licenseContent?: string;
|
|
114
108
|
}; //#endregion
|
|
115
109
|
//#region src/types/schema.d.ts
|
|
116
110
|
declare const platformIdSchema: z.ZodEnum<{
|
|
@@ -119,36 +113,36 @@ declare const platformIdSchema: z.ZodEnum<{
|
|
|
119
113
|
claude: "claude";
|
|
120
114
|
cursor: "cursor";
|
|
121
115
|
}>;
|
|
122
|
-
declare const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
declare
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
116
|
+
declare const titleSchema: z.ZodString;
|
|
117
|
+
declare const descriptionSchema: z.ZodString;
|
|
118
|
+
/** Validate a title string and return error message if invalid, undefined if valid */
|
|
119
|
+
declare function validateTitle(value: string): string | undefined;
|
|
120
|
+
/** Validate a description string and return error message if invalid, undefined if valid */
|
|
121
|
+
declare function validateDescription(value: string): string | undefined;
|
|
122
|
+
declare const slugSchema: z.ZodString;
|
|
123
|
+
/** Validate a slug string and return error message if invalid, undefined if valid */
|
|
124
|
+
declare function validateSlug(value: string): string | undefined;
|
|
125
|
+
declare const COMMON_LICENSES: readonly ["MIT", "Apache-2.0", "GPL-3.0-only", "BSD-3-Clause", "ISC", "Unlicense"];
|
|
126
|
+
type CommonLicense = (typeof COMMON_LICENSES)[number];
|
|
127
|
+
declare const licenseSchema: z.ZodString;
|
|
128
|
+
/** Validate a license string and return error message if invalid, undefined if valid */
|
|
129
|
+
declare function validateLicense(value: string): string | undefined;
|
|
132
130
|
declare const presetConfigSchema: z.ZodObject<{
|
|
133
131
|
$schema: z.ZodOptional<z.ZodString>;
|
|
134
132
|
name: z.ZodString;
|
|
135
133
|
title: z.ZodString;
|
|
136
|
-
version: z.
|
|
134
|
+
version: z.ZodOptional<z.ZodNumber>;
|
|
137
135
|
description: z.ZodString;
|
|
138
136
|
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
features: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
149
|
-
installMessage: z.ZodOptional<z.ZodString>;
|
|
150
|
-
}, z.core.$strict>>;
|
|
151
|
-
}, z.core.$strip>;
|
|
137
|
+
features: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
138
|
+
license: z.ZodString;
|
|
139
|
+
platform: z.ZodEnum<{
|
|
140
|
+
opencode: "opencode";
|
|
141
|
+
codex: "codex";
|
|
142
|
+
claude: "claude";
|
|
143
|
+
cursor: "cursor";
|
|
144
|
+
}>;
|
|
145
|
+
path: z.ZodOptional<z.ZodString>;
|
|
152
146
|
}, z.core.$strict>;
|
|
153
147
|
declare const bundledFileSchema: z.ZodObject<{
|
|
154
148
|
path: z.ZodString;
|
|
@@ -156,7 +150,11 @@ declare const bundledFileSchema: z.ZodObject<{
|
|
|
156
150
|
checksum: z.ZodString;
|
|
157
151
|
contents: z.ZodString;
|
|
158
152
|
}, z.core.$strip>;
|
|
159
|
-
|
|
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<{
|
|
160
158
|
slug: z.ZodString;
|
|
161
159
|
platform: z.ZodEnum<{
|
|
162
160
|
opencode: "opencode";
|
|
@@ -165,15 +163,11 @@ declare const registryBundleSchema: z.ZodObject<{
|
|
|
165
163
|
cursor: "cursor";
|
|
166
164
|
}>;
|
|
167
165
|
title: z.ZodString;
|
|
168
|
-
version: z.ZodString;
|
|
169
166
|
description: z.ZodString;
|
|
170
167
|
tags: z.ZodArray<z.ZodString>;
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
url: z.ZodOptional<z.ZodURL>;
|
|
175
|
-
}, z.core.$strict>>;
|
|
176
|
-
license: z.ZodOptional<z.ZodString>;
|
|
168
|
+
license: z.ZodString;
|
|
169
|
+
licenseContent: z.ZodOptional<z.ZodString>;
|
|
170
|
+
readmeContent: z.ZodOptional<z.ZodString>;
|
|
177
171
|
features: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
178
172
|
installMessage: z.ZodOptional<z.ZodString>;
|
|
179
173
|
files: z.ZodArray<z.ZodObject<{
|
|
@@ -182,62 +176,105 @@ declare const registryBundleSchema: z.ZodObject<{
|
|
|
182
176
|
checksum: z.ZodString;
|
|
183
177
|
contents: z.ZodString;
|
|
184
178
|
}, z.core.$strip>>;
|
|
179
|
+
version: z.ZodOptional<z.ZodNumber>;
|
|
185
180
|
}, z.core.$strip>;
|
|
186
|
-
|
|
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<{
|
|
186
|
+
title: z.ZodString;
|
|
187
|
+
description: z.ZodString;
|
|
188
|
+
license: z.ZodString;
|
|
189
|
+
platform: z.ZodEnum<{
|
|
190
|
+
opencode: "opencode";
|
|
191
|
+
codex: "codex";
|
|
192
|
+
claude: "claude";
|
|
193
|
+
cursor: "cursor";
|
|
194
|
+
}>;
|
|
195
|
+
tags: z.ZodArray<z.ZodString>;
|
|
187
196
|
features: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
197
|
+
slug: z.ZodString;
|
|
198
|
+
licenseContent: z.ZodOptional<z.ZodString>;
|
|
199
|
+
readmeContent: z.ZodOptional<z.ZodString>;
|
|
188
200
|
installMessage: z.ZodOptional<z.ZodString>;
|
|
189
|
-
|
|
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>>;
|
|
190
207
|
version: z.ZodString;
|
|
208
|
+
}, z.core.$strip>;
|
|
209
|
+
declare const registryEntrySchema: z.ZodObject<{
|
|
210
|
+
title: z.ZodString;
|
|
191
211
|
description: z.ZodString;
|
|
192
|
-
|
|
193
|
-
author: z.ZodOptional<z.ZodObject<{
|
|
194
|
-
name: z.ZodString;
|
|
195
|
-
email: z.ZodOptional<z.ZodEmail>;
|
|
196
|
-
url: z.ZodOptional<z.ZodURL>;
|
|
197
|
-
}, z.core.$strict>>;
|
|
198
|
-
license: z.ZodOptional<z.ZodString>;
|
|
212
|
+
license: z.ZodString;
|
|
199
213
|
platform: z.ZodEnum<{
|
|
200
214
|
opencode: "opencode";
|
|
201
215
|
codex: "codex";
|
|
202
216
|
claude: "claude";
|
|
203
217
|
cursor: "cursor";
|
|
204
218
|
}>;
|
|
219
|
+
version: z.ZodString;
|
|
220
|
+
tags: z.ZodArray<z.ZodString>;
|
|
221
|
+
features: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
205
222
|
slug: z.ZodString;
|
|
206
223
|
name: z.ZodString;
|
|
207
|
-
|
|
224
|
+
bundleUrl: z.ZodString;
|
|
208
225
|
fileCount: z.ZodNumber;
|
|
209
226
|
totalSize: z.ZodNumber;
|
|
210
227
|
}, z.core.$strip>;
|
|
211
228
|
declare const registryIndexSchema: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
212
|
-
features: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
213
|
-
installMessage: z.ZodOptional<z.ZodString>;
|
|
214
229
|
title: z.ZodString;
|
|
215
|
-
version: z.ZodString;
|
|
216
230
|
description: z.ZodString;
|
|
217
|
-
|
|
218
|
-
author: z.ZodOptional<z.ZodObject<{
|
|
219
|
-
name: z.ZodString;
|
|
220
|
-
email: z.ZodOptional<z.ZodEmail>;
|
|
221
|
-
url: z.ZodOptional<z.ZodURL>;
|
|
222
|
-
}, z.core.$strict>>;
|
|
223
|
-
license: z.ZodOptional<z.ZodString>;
|
|
231
|
+
license: z.ZodString;
|
|
224
232
|
platform: z.ZodEnum<{
|
|
225
233
|
opencode: "opencode";
|
|
226
234
|
codex: "codex";
|
|
227
235
|
claude: "claude";
|
|
228
236
|
cursor: "cursor";
|
|
229
237
|
}>;
|
|
238
|
+
version: z.ZodString;
|
|
239
|
+
tags: z.ZodArray<z.ZodString>;
|
|
240
|
+
features: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
230
241
|
slug: z.ZodString;
|
|
231
242
|
name: z.ZodString;
|
|
232
|
-
|
|
243
|
+
bundleUrl: z.ZodString;
|
|
233
244
|
fileCount: z.ZodNumber;
|
|
234
245
|
totalSize: z.ZodNumber;
|
|
235
246
|
}, z.core.$strip>>;
|
|
236
247
|
|
|
237
248
|
//#endregion
|
|
238
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
|
+
*/
|
|
239
271
|
type BuildRegistryDataOptions = {
|
|
240
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
|
+
*/
|
|
241
278
|
bundleBase?: string;
|
|
242
279
|
};
|
|
243
280
|
type BuildRegistryDataResult = {
|
|
@@ -245,16 +282,18 @@ type BuildRegistryDataResult = {
|
|
|
245
282
|
index: RegistryIndex;
|
|
246
283
|
bundles: RegistryBundle[];
|
|
247
284
|
};
|
|
248
|
-
|
|
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>;
|
|
249
291
|
|
|
250
292
|
//#endregion
|
|
251
293
|
//#region src/builder/utils.d.ts
|
|
252
|
-
declare function normalizeBundlePublicBase(value: string): string;
|
|
253
|
-
declare function isAbsoluteUrl(value: string): boolean;
|
|
254
294
|
declare function cleanInstallMessage(value: unknown): string | undefined;
|
|
255
295
|
declare function encodeItemName(slug: string, platform: PlatformId): string;
|
|
256
296
|
declare function validatePresetConfig(config: unknown, slug: string): PresetConfig;
|
|
257
|
-
declare function collectBundledFiles(files: Record<string, string>): BundledFile[];
|
|
258
297
|
|
|
259
298
|
//#endregion
|
|
260
299
|
//#region src/client/bundle.d.ts
|
|
@@ -265,13 +304,62 @@ declare function toUtf8String(payload: ArrayBuffer | ArrayBufferView): string;
|
|
|
265
304
|
|
|
266
305
|
//#endregion
|
|
267
306
|
//#region src/client/registry.d.ts
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
+
};
|
|
271
362
|
};
|
|
272
|
-
declare function fetchRegistryIndex(baseUrl: string): Promise<RegistryIndex>;
|
|
273
|
-
declare function fetchRegistryBundle(baseUrl: string, bundlePath: string): Promise<FetchRegistryBundleResult>;
|
|
274
|
-
declare function resolveRegistryEntry(index: RegistryIndex, input: string, explicitPlatform?: PlatformId): RegistryEntry;
|
|
275
363
|
|
|
276
364
|
//#endregion
|
|
277
365
|
//#region src/utils/diff.d.ts
|
|
@@ -295,4 +383,4 @@ declare function normalizePathFragment(value?: string): string | undefined;
|
|
|
295
383
|
declare function maybeStripPrefix(pathInput: string, prefix?: string): string;
|
|
296
384
|
|
|
297
385
|
//#endregion
|
|
298
|
-
export {
|
|
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,7 +1,35 @@
|
|
|
1
|
-
import { createHash } from "crypto";
|
|
2
1
|
import { z } from "zod";
|
|
3
2
|
import { createTwoFilesPatch } from "diff";
|
|
4
3
|
|
|
4
|
+
//#region src/constants.ts
|
|
5
|
+
/**
|
|
6
|
+
* Shared constants for agentrules presets and registry.
|
|
7
|
+
*/
|
|
8
|
+
/** Filename for preset configuration */
|
|
9
|
+
const PRESET_CONFIG_FILENAME = "agentrules.json";
|
|
10
|
+
/** JSON Schema URL for preset configuration */
|
|
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
|
+
};
|
|
31
|
+
|
|
32
|
+
//#endregion
|
|
5
33
|
//#region src/types/platform.ts
|
|
6
34
|
/**
|
|
7
35
|
* Single source of truth for platform IDs.
|
|
@@ -53,40 +81,71 @@ function normalizePlatformInput(value) {
|
|
|
53
81
|
|
|
54
82
|
//#endregion
|
|
55
83
|
//#region src/types/schema.ts
|
|
56
|
-
const
|
|
84
|
+
const VERSION_REGEX = /^[1-9]\d*\.\d+$/;
|
|
57
85
|
const platformIdSchema = z.enum(PLATFORM_IDS);
|
|
58
|
-
const authorSchema = z.object({
|
|
59
|
-
name: z.string().trim().min(1),
|
|
60
|
-
email: z.email().trim().optional(),
|
|
61
|
-
url: z.url().trim().optional()
|
|
62
|
-
}).strict();
|
|
63
86
|
const titleSchema = z.string().trim().min(1).max(120);
|
|
64
87
|
const descriptionSchema = z.string().trim().min(1).max(500);
|
|
65
|
-
|
|
88
|
+
/** Validate a title string and return error message if invalid, undefined if valid */
|
|
89
|
+
function validateTitle(value) {
|
|
90
|
+
const trimmed = value.trim();
|
|
91
|
+
if (!trimmed) return "Title is required";
|
|
92
|
+
if (trimmed.length > 120) return "Title must be 120 characters or less";
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
/** Validate a description string and return error message if invalid, undefined if valid */
|
|
96
|
+
function validateDescription(value) {
|
|
97
|
+
const trimmed = value.trim();
|
|
98
|
+
if (!trimmed) return "Description is required";
|
|
99
|
+
if (trimmed.length > 500) return "Description must be 500 characters or less";
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
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");
|
|
66
104
|
const tagSchema = z.string().trim().min(1).max(48);
|
|
67
105
|
const tagsSchema = z.array(tagSchema).max(10);
|
|
68
106
|
const featureSchema = z.string().trim().min(1).max(160);
|
|
69
107
|
const featuresSchema = z.array(featureSchema).max(10);
|
|
70
108
|
const installMessageSchema = z.string().trim().max(4e3);
|
|
71
|
-
const
|
|
72
|
-
const
|
|
109
|
+
const contentSchema = z.string();
|
|
110
|
+
const SLUG_REGEX = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
111
|
+
const SLUG_ERROR = "Must be lowercase alphanumeric with hyphens (e.g., my-preset)";
|
|
112
|
+
const slugSchema = z.string().trim().min(1).max(64).regex(SLUG_REGEX, SLUG_ERROR);
|
|
113
|
+
/** Validate a slug string and return error message if invalid, undefined if valid */
|
|
114
|
+
function validateSlug(value) {
|
|
115
|
+
const trimmed = value.trim();
|
|
116
|
+
if (!trimmed) return "Name is required";
|
|
117
|
+
if (trimmed.length > 64) return "Name must be 64 characters or less";
|
|
118
|
+
if (!SLUG_REGEX.test(trimmed)) return SLUG_ERROR;
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const COMMON_LICENSES = [
|
|
122
|
+
"MIT",
|
|
123
|
+
"Apache-2.0",
|
|
124
|
+
"GPL-3.0-only",
|
|
125
|
+
"BSD-3-Clause",
|
|
126
|
+
"ISC",
|
|
127
|
+
"Unlicense"
|
|
128
|
+
];
|
|
129
|
+
const licenseSchema = z.string().trim().min(1).max(128);
|
|
130
|
+
/** Validate a license string and return error message if invalid, undefined if valid */
|
|
131
|
+
function validateLicense(value) {
|
|
132
|
+
const trimmed = value.trim();
|
|
133
|
+
if (!trimmed) return "License is required";
|
|
134
|
+
if (trimmed.length > 128) return "License must be 128 characters or less";
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
73
137
|
const pathSchema = z.string().trim().min(1);
|
|
74
|
-
const platformPresetConfigSchema = z.object({
|
|
75
|
-
path: pathSchema,
|
|
76
|
-
features: featuresSchema.optional(),
|
|
77
|
-
installMessage: installMessageSchema.optional()
|
|
78
|
-
}).strict();
|
|
79
|
-
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" });
|
|
80
138
|
const presetConfigSchema = z.object({
|
|
81
139
|
$schema: z.string().optional(),
|
|
82
140
|
name: slugSchema,
|
|
83
141
|
title: titleSchema,
|
|
84
|
-
version:
|
|
142
|
+
version: majorVersionSchema.optional(),
|
|
85
143
|
description: descriptionSchema,
|
|
86
144
|
tags: tagsSchema.optional(),
|
|
87
|
-
|
|
88
|
-
license: licenseSchema
|
|
89
|
-
|
|
145
|
+
features: featuresSchema.optional(),
|
|
146
|
+
license: licenseSchema,
|
|
147
|
+
platform: platformIdSchema,
|
|
148
|
+
path: pathSchema.optional()
|
|
90
149
|
}).strict();
|
|
91
150
|
const bundledFileSchema = z.object({
|
|
92
151
|
path: z.string().min(1),
|
|
@@ -94,22 +153,37 @@ const bundledFileSchema = z.object({
|
|
|
94
153
|
checksum: z.string().length(64),
|
|
95
154
|
contents: z.string()
|
|
96
155
|
});
|
|
97
|
-
|
|
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({
|
|
98
161
|
slug: z.string().trim().min(1),
|
|
99
162
|
platform: platformIdSchema,
|
|
100
163
|
title: titleSchema,
|
|
101
|
-
version: versionSchema,
|
|
102
164
|
description: descriptionSchema,
|
|
103
165
|
tags: tagsSchema,
|
|
104
|
-
|
|
105
|
-
|
|
166
|
+
license: licenseSchema,
|
|
167
|
+
licenseContent: contentSchema.optional(),
|
|
168
|
+
readmeContent: contentSchema.optional(),
|
|
106
169
|
features: featuresSchema.optional(),
|
|
107
170
|
installMessage: installMessageSchema.optional(),
|
|
108
|
-
files: z.array(bundledFileSchema).min(1)
|
|
171
|
+
files: z.array(bundledFileSchema).min(1),
|
|
172
|
+
version: majorVersionSchema.optional()
|
|
109
173
|
});
|
|
110
|
-
|
|
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 });
|
|
179
|
+
const registryEntrySchema = registryBundleSchema.omit({
|
|
180
|
+
files: true,
|
|
181
|
+
readmeContent: true,
|
|
182
|
+
licenseContent: true,
|
|
183
|
+
installMessage: true
|
|
184
|
+
}).extend({
|
|
111
185
|
name: z.string().trim().min(1),
|
|
112
|
-
|
|
186
|
+
bundleUrl: z.string().trim().min(1),
|
|
113
187
|
fileCount: z.number().int().nonnegative(),
|
|
114
188
|
totalSize: z.number().int().nonnegative()
|
|
115
189
|
});
|
|
@@ -137,18 +211,6 @@ function toUint8Array(payload) {
|
|
|
137
211
|
|
|
138
212
|
//#endregion
|
|
139
213
|
//#region src/builder/utils.ts
|
|
140
|
-
function normalizeBundlePublicBase(value) {
|
|
141
|
-
const trimmed = value.trim();
|
|
142
|
-
if (!trimmed) throw new Error("--bundle-base must be a non-empty string");
|
|
143
|
-
if (isAbsoluteUrl(trimmed)) return trimmed.replace(/\/+$/, "");
|
|
144
|
-
let normalized = trimmed;
|
|
145
|
-
if (!normalized.startsWith("/")) normalized = `/${normalized}`;
|
|
146
|
-
normalized = normalized.replace(/\/+$/, "");
|
|
147
|
-
return normalized === "" ? "/" : normalized;
|
|
148
|
-
}
|
|
149
|
-
function isAbsoluteUrl(value) {
|
|
150
|
-
return /^[a-zA-Z][a-zA-Z\d+-.]*:/.test(value);
|
|
151
|
-
}
|
|
152
214
|
function cleanInstallMessage(value) {
|
|
153
215
|
if (typeof value !== "string") return;
|
|
154
216
|
const trimmed = value.trim();
|
|
@@ -162,80 +224,115 @@ function validatePresetConfig(config, slug) {
|
|
|
162
224
|
const preset = config;
|
|
163
225
|
if (!preset.name || typeof preset.name !== "string") throw new Error(`Preset ${slug} is missing a name`);
|
|
164
226
|
if (!preset.title || typeof preset.title !== "string") throw new Error(`Preset ${slug} is missing a title`);
|
|
165
|
-
if (
|
|
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)`);
|
|
166
228
|
if (!preset.description || typeof preset.description !== "string") throw new Error(`Preset ${slug} is missing a description`);
|
|
167
|
-
if (!preset.
|
|
229
|
+
if (!preset.license || typeof preset.license !== "string") throw new Error(`Preset ${slug} is missing a license (SPDX identifier required)`);
|
|
230
|
+
if (!preset.platform || typeof preset.platform !== "string") throw new Error(`Preset ${slug} is missing a platform`);
|
|
231
|
+
if (!PLATFORM_IDS.includes(preset.platform)) throw new Error(`Preset ${slug} has unknown platform: ${preset.platform}. Supported platforms: ${PLATFORM_IDS.join(", ")}`);
|
|
168
232
|
return preset;
|
|
169
233
|
}
|
|
170
|
-
function collectBundledFiles(files) {
|
|
171
|
-
return Object.entries(files).map(([path, contents]) => {
|
|
172
|
-
const normalizedPath = toPosixPath(path);
|
|
173
|
-
const payload = encodeUtf8(contents);
|
|
174
|
-
return {
|
|
175
|
-
path: normalizedPath,
|
|
176
|
-
size: payload.length,
|
|
177
|
-
checksum: "",
|
|
178
|
-
contents
|
|
179
|
-
};
|
|
180
|
-
}).sort((a, b) => a.path.localeCompare(b.path));
|
|
181
|
-
}
|
|
182
234
|
|
|
183
235
|
//#endregion
|
|
184
236
|
//#region src/builder/registry.ts
|
|
185
237
|
const NAME_PATTERN = /^[a-z0-9-]+$/;
|
|
186
|
-
|
|
187
|
-
|
|
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);
|
|
188
290
|
const entries = [];
|
|
189
291
|
const bundles = [];
|
|
190
292
|
for (const presetInput of options.presets) {
|
|
191
293
|
if (!NAME_PATTERN.test(presetInput.slug)) throw new Error(`Invalid slug "${presetInput.slug}". Slugs must be lowercase kebab-case.`);
|
|
192
294
|
const presetConfig = validatePresetConfig(presetInput.config, presetInput.slug);
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
files
|
|
235
|
-
};
|
|
236
|
-
entries.push(entry);
|
|
237
|
-
bundles.push(bundle);
|
|
238
|
-
}
|
|
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);
|
|
239
336
|
}
|
|
240
337
|
sortBySlugAndPlatform(entries);
|
|
241
338
|
sortBySlugAndPlatform(bundles);
|
|
@@ -249,33 +346,49 @@ function buildRegistryData(options) {
|
|
|
249
346
|
bundles
|
|
250
347
|
};
|
|
251
348
|
}
|
|
252
|
-
function createBundledFilesFromInputs(files) {
|
|
253
|
-
|
|
349
|
+
async function createBundledFilesFromInputs(files) {
|
|
350
|
+
const results = await Promise.all(files.map(async (file) => {
|
|
254
351
|
const payload = normalizeFilePayload(file.contents);
|
|
255
352
|
const contents = encodeFilePayload(payload, file.path);
|
|
256
|
-
const checksum =
|
|
353
|
+
const checksum = await sha256(payload);
|
|
257
354
|
return {
|
|
258
355
|
path: toPosixPath(file.path),
|
|
259
356
|
size: payload.length,
|
|
260
357
|
checksum,
|
|
261
358
|
contents
|
|
262
359
|
};
|
|
263
|
-
})
|
|
360
|
+
}));
|
|
361
|
+
return results.sort((a, b) => a.path.localeCompare(b.path));
|
|
264
362
|
}
|
|
265
363
|
function normalizeFilePayload(contents) {
|
|
266
|
-
if (typeof contents === "string") return
|
|
267
|
-
if (contents instanceof ArrayBuffer) return
|
|
268
|
-
if (ArrayBuffer.isView(contents)) return
|
|
269
|
-
return
|
|
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);
|
|
270
368
|
}
|
|
271
|
-
function encodeFilePayload(
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
|
|
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
|
+
}
|
|
275
376
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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}`;
|
|
279
392
|
}
|
|
280
393
|
function ensureKnownPlatform(platform, slug) {
|
|
281
394
|
if (!isSupportedPlatform(platform)) throw new Error(`Unknown platform "${platform}" in ${slug}. Supported: ${PLATFORM_IDS.join(", ")}`);
|
|
@@ -311,67 +424,56 @@ function toUtf8String(payload) {
|
|
|
311
424
|
return decodeUtf8(payload);
|
|
312
425
|
}
|
|
313
426
|
async function sha256Hex(payload) {
|
|
314
|
-
const crypto = globalThis.crypto;
|
|
315
|
-
if (!crypto?.subtle) throw new Error("SHA-256 hashing requires Web Crypto API support.");
|
|
316
|
-
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);
|
|
317
430
|
return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
318
431
|
}
|
|
319
432
|
|
|
320
433
|
//#endregion
|
|
321
434
|
//#region src/client/registry.ts
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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
|
+
}
|
|
326
454
|
try {
|
|
327
|
-
|
|
455
|
+
const entry = await response.json();
|
|
456
|
+
const resolvedBundleUrl = new URL(entry.bundleUrl, baseUrl).toString();
|
|
457
|
+
return {
|
|
458
|
+
entry,
|
|
459
|
+
bundleUrl: resolvedBundleUrl
|
|
460
|
+
};
|
|
328
461
|
} catch (error) {
|
|
329
|
-
throw new Error(`Unable to parse
|
|
462
|
+
throw new Error(`Unable to parse preset response: ${error.message}`);
|
|
330
463
|
}
|
|
331
464
|
}
|
|
332
|
-
|
|
333
|
-
|
|
465
|
+
/**
|
|
466
|
+
* Fetches a bundle from an absolute URL or resolves it relative to the registry.
|
|
467
|
+
*/
|
|
468
|
+
async function fetchBundle(bundleUrl) {
|
|
334
469
|
const response = await fetch(bundleUrl);
|
|
335
470
|
if (!response.ok) throw new Error(`Failed to download bundle (${response.status} ${response.statusText}).`);
|
|
336
471
|
try {
|
|
337
|
-
|
|
338
|
-
return {
|
|
339
|
-
bundle,
|
|
340
|
-
etag: response.headers.get("etag")
|
|
341
|
-
};
|
|
472
|
+
return await response.json();
|
|
342
473
|
} catch (error) {
|
|
343
474
|
throw new Error(`Unable to parse bundle JSON: ${error.message}`);
|
|
344
475
|
}
|
|
345
476
|
}
|
|
346
|
-
function resolveRegistryEntry(index, input, explicitPlatform) {
|
|
347
|
-
const map = new Map();
|
|
348
|
-
for (const [key, value] of Object.entries(index)) map.set(key.toLowerCase(), value);
|
|
349
|
-
const normalizedInput = input.toLowerCase();
|
|
350
|
-
const direct = map.get(normalizedInput);
|
|
351
|
-
if (direct) return direct;
|
|
352
|
-
let slugHint = normalizedInput;
|
|
353
|
-
let platform = explicitPlatform;
|
|
354
|
-
if (!platform) {
|
|
355
|
-
const parts = normalizedInput.split(".");
|
|
356
|
-
const maybePlatform = parts.at(-1);
|
|
357
|
-
if (maybePlatform && isSupportedPlatform(maybePlatform)) {
|
|
358
|
-
platform = maybePlatform;
|
|
359
|
-
slugHint = parts.slice(0, -1).join(".");
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
const matches = Object.values(index).filter((entry) => entry.slug.toLowerCase() === slugHint);
|
|
363
|
-
if (platform) {
|
|
364
|
-
const match = matches.find((entry) => entry.platform === platform);
|
|
365
|
-
if (match) return match;
|
|
366
|
-
throw new Error(`Preset "${input}" is not available for platform "${platform}".`);
|
|
367
|
-
}
|
|
368
|
-
if (matches.length === 1) return matches[0];
|
|
369
|
-
if (matches.length > 1) {
|
|
370
|
-
const platforms = matches.map((entry) => entry.platform).join(", ");
|
|
371
|
-
throw new Error(`Preset "${input}" is available for multiple platforms (${platforms}). Use --platform to pick one or specify <slug>.<platform>.`);
|
|
372
|
-
}
|
|
373
|
-
throw new Error(`Preset "${input}" was not found in the active registry.`);
|
|
374
|
-
}
|
|
375
477
|
|
|
376
478
|
//#endregion
|
|
377
479
|
//#region src/utils/diff.ts
|
|
@@ -404,4 +506,4 @@ function maybeStripPrefix(pathInput, prefix) {
|
|
|
404
506
|
}
|
|
405
507
|
|
|
406
508
|
//#endregion
|
|
407
|
-
export { CONFIG_DIR_NAME, PLATFORMS, PLATFORM_IDS,
|
|
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.
|
|
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://
|
|
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": "
|
|
49
|
+
"zod": "catalog:"
|
|
49
50
|
},
|
|
50
51
|
"devDependencies": {
|
|
51
52
|
"tsdown": "^0.9.0",
|
|
52
|
-
"typescript": "
|
|
53
|
+
"typescript": "catalog:"
|
|
53
54
|
}
|
|
54
|
-
}
|
|
55
|
+
}
|