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