@agentrules/core 0.0.11 → 0.1.0
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 +2 -2
- package/dist/index.d.ts +629 -174
- package/dist/index.js +461 -248
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,49 +1,6 @@
|
|
|
1
1
|
import { ZodError, z } from "zod";
|
|
2
2
|
import { createTwoFilesPatch } from "diff";
|
|
3
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
|
-
/** Directory name for preset metadata (README, LICENSE, etc.) */
|
|
11
|
-
const AGENT_RULES_DIR = ".agentrules";
|
|
12
|
-
/** JSON Schema URL for preset configuration */
|
|
13
|
-
const PRESET_SCHEMA_URL = "https://agentrules.directory/schema/agentrules.json";
|
|
14
|
-
/** API root path segment */
|
|
15
|
-
const API_PATH = "api";
|
|
16
|
-
/** Default version identifier for latest preset version */
|
|
17
|
-
const LATEST_VERSION = "latest";
|
|
18
|
-
/**
|
|
19
|
-
* API endpoint paths (relative to registry base URL).
|
|
20
|
-
*
|
|
21
|
-
* Note: Path parameters (slug, platform, version) are NOT URI-encoded.
|
|
22
|
-
* - Slugs may contain slashes (e.g., "username/my-preset") which should flow
|
|
23
|
-
* through as path segments for static registry compatibility
|
|
24
|
-
* - Platform and version are constrained values (enums, validated formats)
|
|
25
|
-
* that only contain URL-safe characters
|
|
26
|
-
*
|
|
27
|
-
* The client is responsible for validating these values before making requests.
|
|
28
|
-
*/
|
|
29
|
-
const API_ENDPOINTS = {
|
|
30
|
-
presets: {
|
|
31
|
-
base: `${API_PATH}/preset`,
|
|
32
|
-
get: (slug, platform, version = LATEST_VERSION) => `${API_PATH}/preset/${slug}/${platform}/${version}`,
|
|
33
|
-
unpublish: (slug, platform, version) => `${API_PATH}/preset/${slug}/${platform}/${version}`
|
|
34
|
-
},
|
|
35
|
-
auth: {
|
|
36
|
-
session: `${API_PATH}/auth/get-session`,
|
|
37
|
-
deviceCode: `${API_PATH}/auth/device/code`,
|
|
38
|
-
deviceToken: `${API_PATH}/auth/device/token`
|
|
39
|
-
},
|
|
40
|
-
rule: {
|
|
41
|
-
base: `${API_PATH}/rule`,
|
|
42
|
-
get: (slug) => `${API_PATH}/rule/${slug}`
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
//#endregion
|
|
47
4
|
//#region src/platform/types.ts
|
|
48
5
|
/**
|
|
49
6
|
* Platform and rule type definitions.
|
|
@@ -68,14 +25,14 @@ const PLATFORMS = {
|
|
|
68
25
|
globalDir: "~/.config/opencode",
|
|
69
26
|
types: {
|
|
70
27
|
instruction: {
|
|
71
|
-
description: "Project instructions
|
|
28
|
+
description: "Project instructions",
|
|
72
29
|
format: "markdown",
|
|
73
30
|
extension: "md",
|
|
74
31
|
projectPath: "AGENTS.md",
|
|
75
32
|
globalPath: "~/.config/opencode/AGENTS.md"
|
|
76
33
|
},
|
|
77
34
|
agent: {
|
|
78
|
-
description: "Specialized AI agent
|
|
35
|
+
description: "Specialized AI agent",
|
|
79
36
|
format: "markdown",
|
|
80
37
|
extension: "md",
|
|
81
38
|
projectPath: ".opencode/agent/{name}.md",
|
|
@@ -89,7 +46,7 @@ const PLATFORMS = {
|
|
|
89
46
|
globalPath: "~/.config/opencode/command/{name}.md"
|
|
90
47
|
},
|
|
91
48
|
tool: {
|
|
92
|
-
description: "Custom tool
|
|
49
|
+
description: "Custom tool",
|
|
93
50
|
format: "typescript",
|
|
94
51
|
extension: "ts",
|
|
95
52
|
projectPath: ".opencode/tool/{name}.ts",
|
|
@@ -103,7 +60,7 @@ const PLATFORMS = {
|
|
|
103
60
|
globalDir: "~/.claude",
|
|
104
61
|
types: {
|
|
105
62
|
instruction: {
|
|
106
|
-
description: "Project instructions
|
|
63
|
+
description: "Project instructions",
|
|
107
64
|
format: "markdown",
|
|
108
65
|
extension: "md",
|
|
109
66
|
projectPath: "CLAUDE.md",
|
|
@@ -117,7 +74,7 @@ const PLATFORMS = {
|
|
|
117
74
|
globalPath: "~/.claude/commands/{name}.md"
|
|
118
75
|
},
|
|
119
76
|
skill: {
|
|
120
|
-
description: "Custom skill
|
|
77
|
+
description: "Custom skill",
|
|
121
78
|
format: "markdown",
|
|
122
79
|
extension: "md",
|
|
123
80
|
projectPath: ".claude/skills/{name}/SKILL.md",
|
|
@@ -128,29 +85,45 @@ const PLATFORMS = {
|
|
|
128
85
|
cursor: {
|
|
129
86
|
label: "Cursor",
|
|
130
87
|
projectDir: ".cursor",
|
|
131
|
-
globalDir:
|
|
132
|
-
types: {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
88
|
+
globalDir: "~/.cursor",
|
|
89
|
+
types: {
|
|
90
|
+
instruction: {
|
|
91
|
+
description: "Project instructions",
|
|
92
|
+
format: "markdown",
|
|
93
|
+
extension: "md",
|
|
94
|
+
projectPath: "AGENTS.md",
|
|
95
|
+
globalPath: null
|
|
96
|
+
},
|
|
97
|
+
rule: {
|
|
98
|
+
description: "Custom rule",
|
|
99
|
+
format: "mdc",
|
|
100
|
+
extension: "mdc",
|
|
101
|
+
projectPath: ".cursor/rules/{name}.mdc",
|
|
102
|
+
globalPath: null
|
|
103
|
+
},
|
|
104
|
+
command: {
|
|
105
|
+
description: "Custom slash command",
|
|
106
|
+
format: "markdown",
|
|
107
|
+
extension: "md",
|
|
108
|
+
projectPath: ".cursor/commands/{name}.md",
|
|
109
|
+
globalPath: "~/.cursor/commands/{name}.md"
|
|
110
|
+
}
|
|
111
|
+
}
|
|
139
112
|
},
|
|
140
113
|
codex: {
|
|
141
114
|
label: "Codex",
|
|
142
|
-
projectDir: "",
|
|
115
|
+
projectDir: ".codex",
|
|
143
116
|
globalDir: "~/.codex",
|
|
144
117
|
types: {
|
|
145
118
|
instruction: {
|
|
146
|
-
description: "Project instructions
|
|
119
|
+
description: "Project instructions",
|
|
147
120
|
format: "markdown",
|
|
148
121
|
extension: "md",
|
|
149
122
|
projectPath: "AGENTS.md",
|
|
150
123
|
globalPath: "~/.codex/AGENTS.md"
|
|
151
124
|
},
|
|
152
125
|
command: {
|
|
153
|
-
description: "Custom prompt
|
|
126
|
+
description: "Custom prompt",
|
|
154
127
|
format: "markdown",
|
|
155
128
|
extension: "md",
|
|
156
129
|
projectPath: null,
|
|
@@ -163,8 +136,8 @@ const PLATFORMS = {
|
|
|
163
136
|
const PLATFORM_RULE_TYPES = {
|
|
164
137
|
opencode: [
|
|
165
138
|
"instruction",
|
|
166
|
-
"agent",
|
|
167
139
|
"command",
|
|
140
|
+
"agent",
|
|
168
141
|
"tool"
|
|
169
142
|
],
|
|
170
143
|
claude: [
|
|
@@ -172,8 +145,12 @@ const PLATFORM_RULE_TYPES = {
|
|
|
172
145
|
"command",
|
|
173
146
|
"skill"
|
|
174
147
|
],
|
|
175
|
-
|
|
176
|
-
|
|
148
|
+
codex: ["instruction", "command"],
|
|
149
|
+
cursor: [
|
|
150
|
+
"instruction",
|
|
151
|
+
"command",
|
|
152
|
+
"rule"
|
|
153
|
+
]
|
|
177
154
|
};
|
|
178
155
|
/** Get valid rule types for a specific platform */
|
|
179
156
|
function getValidRuleTypes(platform) {
|
|
@@ -247,25 +224,56 @@ function toUint8Array(payload) {
|
|
|
247
224
|
}
|
|
248
225
|
|
|
249
226
|
//#endregion
|
|
250
|
-
//#region src/
|
|
227
|
+
//#region src/schemas/common.ts
|
|
251
228
|
const PLATFORM_ID_SET = new Set(PLATFORM_IDS);
|
|
252
|
-
const VERSION_REGEX = /^[1-9]\d*\.\d+$/;
|
|
253
|
-
const platformIdSchema = z.enum(PLATFORM_IDS);
|
|
254
|
-
const titleSchema = z.string().trim().min(1, "Title is required").max(80, "Title must be 80 characters or less");
|
|
255
|
-
const descriptionSchema = z.string().trim().min(1, "Description is required").max(500, "Description must be 500 characters or less");
|
|
256
|
-
const versionSchema = z.string().trim().regex(VERSION_REGEX, "Version must be in MAJOR.MINOR format (e.g., 1.3)");
|
|
257
|
-
const majorVersionSchema = z.number().int().positive("Major version must be a positive integer");
|
|
258
229
|
const TAG_REGEX = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
259
230
|
const TAG_ERROR = "Must be lowercase alphanumeric with hyphens (e.g., my-tag)";
|
|
231
|
+
/**
|
|
232
|
+
* Schema for a single tag.
|
|
233
|
+
* - Max 35 characters
|
|
234
|
+
* - Lowercase alphanumeric with hyphens
|
|
235
|
+
* - Platform names blocked (redundant with platform field)
|
|
236
|
+
*/
|
|
260
237
|
const tagSchema = z.string().trim().min(1, "Tag cannot be empty").max(35, "Tag must be 35 characters or less").regex(TAG_REGEX, TAG_ERROR).refine((tag) => !PLATFORM_ID_SET.has(tag), { message: "Platform names cannot be used as tags (redundant with platform field)" });
|
|
238
|
+
/**
|
|
239
|
+
* Schema for tags array.
|
|
240
|
+
* - 1-10 tags required
|
|
241
|
+
*/
|
|
261
242
|
const tagsSchema = z.array(tagSchema).min(1, "At least one tag is required").max(10, "Maximum 10 tags allowed");
|
|
243
|
+
const NAME_REGEX = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
244
|
+
const NAME_ERROR = "Must be lowercase alphanumeric with hyphens (e.g., my-preset)";
|
|
245
|
+
/**
|
|
246
|
+
* Schema for preset/rule name.
|
|
247
|
+
* - Max 64 characters
|
|
248
|
+
* - Lowercase alphanumeric with hyphens
|
|
249
|
+
*/
|
|
250
|
+
const nameSchema = z.string().trim().min(1, "Name is required").max(64, "Name must be 64 characters or less").regex(NAME_REGEX, NAME_ERROR);
|
|
251
|
+
/**
|
|
252
|
+
* Schema for display title.
|
|
253
|
+
* - Max 80 characters
|
|
254
|
+
*/
|
|
255
|
+
const titleSchema = z.string().trim().min(1, "Title is required").max(80, "Title must be 80 characters or less");
|
|
256
|
+
/**
|
|
257
|
+
* Schema for description.
|
|
258
|
+
* - Max 500 characters
|
|
259
|
+
*/
|
|
260
|
+
const descriptionSchema = z.string().trim().max(500, "Description must be 500 characters or less");
|
|
261
|
+
|
|
262
|
+
//#endregion
|
|
263
|
+
//#region src/preset/schema.ts
|
|
264
|
+
const VERSION_REGEX$1 = /^[1-9]\d*\.\d+$/;
|
|
265
|
+
const platformIdSchema = z.enum(PLATFORM_IDS);
|
|
266
|
+
/**
|
|
267
|
+
* Schema for required description (presets require a description).
|
|
268
|
+
* Extends base descriptionSchema with min(1) constraint.
|
|
269
|
+
*/
|
|
270
|
+
const requiredDescriptionSchema = descriptionSchema.min(1, "Description is required");
|
|
271
|
+
const versionSchema = z.string().trim().regex(VERSION_REGEX$1, "Version must be in MAJOR.MINOR format (e.g., 1.3)");
|
|
272
|
+
const majorVersionSchema = z.number().int().positive("Major version must be a positive integer");
|
|
262
273
|
const featureSchema = z.string().trim().min(1, "Feature cannot be empty").max(100, "Feature must be 100 characters or less");
|
|
263
274
|
const featuresSchema = z.array(featureSchema).max(5, "Maximum 5 features allowed");
|
|
264
275
|
const installMessageSchema = z.string().trim().max(2e3, "Install message must be 2000 characters or less");
|
|
265
276
|
const contentSchema = z.string();
|
|
266
|
-
const SLUG_REGEX = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
267
|
-
const SLUG_ERROR = "Must be lowercase alphanumeric with hyphens (e.g., my-preset)";
|
|
268
|
-
const slugSchema = z.string().trim().min(1, "Name is required").max(64, "Name must be 64 characters or less").regex(SLUG_REGEX, SLUG_ERROR);
|
|
269
277
|
const COMMON_LICENSES = [
|
|
270
278
|
"MIT",
|
|
271
279
|
"Apache-2.0",
|
|
@@ -278,44 +286,78 @@ const licenseSchema = z.string().trim().min(1, "License is required").max(128, "
|
|
|
278
286
|
const pathSchema = z.string().trim().min(1);
|
|
279
287
|
const ignorePatternSchema = z.string().trim().min(1, "Ignore pattern cannot be empty");
|
|
280
288
|
const ignoreSchema = z.array(ignorePatternSchema).max(50, "Maximum 50 ignore patterns allowed");
|
|
289
|
+
/**
|
|
290
|
+
* Schema for agentrulesDir - directory containing metadata files (README, LICENSE, INSTALL).
|
|
291
|
+
* Defaults to ".agentrules" if not specified.
|
|
292
|
+
* Use "." to read metadata from the project root.
|
|
293
|
+
*/
|
|
294
|
+
const agentrulesPathSchema = z.string().trim().min(1, "agentrulesDir cannot be empty");
|
|
295
|
+
/**
|
|
296
|
+
* Platform entry - either a platform ID string or an object with optional path.
|
|
297
|
+
*
|
|
298
|
+
* Examples:
|
|
299
|
+
* - "opencode" (shorthand, uses default directory)
|
|
300
|
+
* - { platform: "opencode", path: "rules" } (custom path)
|
|
301
|
+
*/
|
|
302
|
+
const platformEntryObjectSchema = z.object({
|
|
303
|
+
platform: platformIdSchema,
|
|
304
|
+
path: pathSchema.optional()
|
|
305
|
+
}).strict();
|
|
306
|
+
const platformEntrySchema = z.union([platformIdSchema, platformEntryObjectSchema]);
|
|
307
|
+
/**
|
|
308
|
+
* Preset config schema.
|
|
309
|
+
*
|
|
310
|
+
* Uses a unified `platforms` array that accepts either:
|
|
311
|
+
* - Platform ID strings: `["opencode", "claude"]`
|
|
312
|
+
* - Objects with optional path: `[{ platform: "opencode", path: "rules" }]`
|
|
313
|
+
* - Mixed: `["opencode", { platform: "claude", path: "my-claude" }]`
|
|
314
|
+
*/
|
|
281
315
|
const presetConfigSchema = z.object({
|
|
282
316
|
$schema: z.string().optional(),
|
|
283
|
-
name:
|
|
317
|
+
name: nameSchema,
|
|
284
318
|
title: titleSchema,
|
|
285
319
|
version: majorVersionSchema.optional(),
|
|
286
|
-
description:
|
|
320
|
+
description: requiredDescriptionSchema,
|
|
287
321
|
tags: tagsSchema,
|
|
288
322
|
features: featuresSchema.optional(),
|
|
289
323
|
license: licenseSchema,
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
324
|
+
ignore: ignoreSchema.optional(),
|
|
325
|
+
agentrulesDir: agentrulesPathSchema.optional(),
|
|
326
|
+
platforms: z.array(platformEntrySchema).min(1, "At least one platform is required")
|
|
293
327
|
}).strict();
|
|
294
328
|
const bundledFileSchema = z.object({
|
|
295
329
|
path: z.string().min(1),
|
|
296
330
|
size: z.number().int().nonnegative(),
|
|
297
331
|
checksum: z.string().length(64),
|
|
298
|
-
|
|
332
|
+
content: z.string()
|
|
333
|
+
});
|
|
334
|
+
/**
|
|
335
|
+
* Schema for per-platform variant in publish input.
|
|
336
|
+
*/
|
|
337
|
+
const publishVariantInputSchema = z.object({
|
|
338
|
+
platform: platformIdSchema,
|
|
339
|
+
files: z.array(bundledFileSchema).min(1),
|
|
340
|
+
readmeContent: contentSchema.optional(),
|
|
341
|
+
licenseContent: contentSchema.optional(),
|
|
342
|
+
installMessage: installMessageSchema.optional()
|
|
299
343
|
});
|
|
300
344
|
/**
|
|
301
|
-
* Schema for what clients send to publish a preset.
|
|
345
|
+
* Schema for what clients send to publish a preset (multi-platform).
|
|
346
|
+
*
|
|
347
|
+
* One publish call creates ONE version with ALL platform variants.
|
|
302
348
|
* Version is optional major version. Registry assigns full MAJOR.MINOR.
|
|
303
349
|
*
|
|
304
350
|
* Note: Clients send `name` (e.g., "my-preset"), and the registry defines the format of the slug.
|
|
305
351
|
* For example, a namespaced slug could be returned as "username/my-preset"
|
|
306
352
|
*/
|
|
307
353
|
const presetPublishInputSchema = z.object({
|
|
308
|
-
name:
|
|
309
|
-
platform: platformIdSchema,
|
|
354
|
+
name: nameSchema,
|
|
310
355
|
title: titleSchema,
|
|
311
|
-
description:
|
|
356
|
+
description: requiredDescriptionSchema,
|
|
312
357
|
tags: tagsSchema,
|
|
313
358
|
license: licenseSchema,
|
|
314
|
-
licenseContent: contentSchema.optional(),
|
|
315
|
-
readmeContent: contentSchema.optional(),
|
|
316
359
|
features: featuresSchema.optional(),
|
|
317
|
-
|
|
318
|
-
files: z.array(bundledFileSchema).min(1),
|
|
360
|
+
variants: z.array(publishVariantInputSchema).min(1, "At least one platform variant is required"),
|
|
319
361
|
version: majorVersionSchema.optional()
|
|
320
362
|
});
|
|
321
363
|
/**
|
|
@@ -329,18 +371,16 @@ const presetBundleSchema = presetPublishInputSchema.omit({
|
|
|
329
371
|
slug: z.string().trim().min(1),
|
|
330
372
|
version: versionSchema
|
|
331
373
|
});
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
});
|
|
343
|
-
const presetIndexSchema = z.record(z.string(), presetSchema);
|
|
374
|
+
|
|
375
|
+
//#endregion
|
|
376
|
+
//#region src/preset/types.ts
|
|
377
|
+
/**
|
|
378
|
+
* Normalize a raw platform entry to the object form.
|
|
379
|
+
*/
|
|
380
|
+
function normalizePlatformEntry(entry) {
|
|
381
|
+
if (typeof entry === "string") return { platform: entry };
|
|
382
|
+
return entry;
|
|
383
|
+
}
|
|
344
384
|
|
|
345
385
|
//#endregion
|
|
346
386
|
//#region src/builder/utils.ts
|
|
@@ -349,9 +389,10 @@ function cleanInstallMessage(value) {
|
|
|
349
389
|
const trimmed = value.trim();
|
|
350
390
|
return trimmed.length > 0 ? trimmed : void 0;
|
|
351
391
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
392
|
+
/**
|
|
393
|
+
* Validate raw preset config from JSON.
|
|
394
|
+
* Returns the raw config shape (before normalization).
|
|
395
|
+
*/
|
|
355
396
|
function validatePresetConfig(config, slug) {
|
|
356
397
|
try {
|
|
357
398
|
return presetConfigSchema.parse(config);
|
|
@@ -385,122 +426,128 @@ async function sha256(data) {
|
|
|
385
426
|
}
|
|
386
427
|
/**
|
|
387
428
|
* Builds a PresetPublishInput from preset input.
|
|
388
|
-
*
|
|
429
|
+
*
|
|
430
|
+
* PresetInput always has platformFiles array (unified format).
|
|
389
431
|
*/
|
|
390
432
|
async function buildPresetPublishInput(options) {
|
|
391
|
-
const { preset
|
|
392
|
-
if (!NAME_PATTERN.test(
|
|
393
|
-
const
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
-
if (
|
|
397
|
-
const
|
|
398
|
-
const
|
|
399
|
-
const
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
433
|
+
const { preset, version } = options;
|
|
434
|
+
if (!NAME_PATTERN.test(preset.name)) throw new Error(`Invalid name "${preset.name}". Names must be lowercase kebab-case.`);
|
|
435
|
+
const config = validatePresetConfig(preset.config, preset.name);
|
|
436
|
+
const majorVersion = version ?? config.version;
|
|
437
|
+
const platforms = preset.config.platforms;
|
|
438
|
+
if (platforms.length === 0) throw new Error(`Preset ${preset.name} must specify at least one platform.`);
|
|
439
|
+
for (const entry of platforms) ensureKnownPlatform(entry.platform, preset.name);
|
|
440
|
+
const variants = [];
|
|
441
|
+
for (const entry of platforms) {
|
|
442
|
+
const platformData = preset.platformFiles.find((pf) => pf.platform === entry.platform);
|
|
443
|
+
if (!platformData) throw new Error(`Preset ${preset.name} is missing files for platform "${entry.platform}".`);
|
|
444
|
+
if (platformData.files.length === 0) throw new Error(`Preset ${preset.name} has no files for platform "${entry.platform}".`);
|
|
445
|
+
const files = await createBundledFilesFromInputs(platformData.files);
|
|
446
|
+
variants.push({
|
|
447
|
+
platform: entry.platform,
|
|
448
|
+
files,
|
|
449
|
+
readmeContent: platformData.readmeContent?.trim() || preset.readmeContent?.trim() || void 0,
|
|
450
|
+
licenseContent: platformData.licenseContent?.trim() || preset.licenseContent?.trim() || void 0,
|
|
451
|
+
installMessage: cleanInstallMessage(platformData.installMessage) || cleanInstallMessage(preset.installMessage)
|
|
452
|
+
});
|
|
453
|
+
}
|
|
403
454
|
return {
|
|
404
|
-
name:
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
readmeContent,
|
|
412
|
-
features,
|
|
413
|
-
installMessage,
|
|
414
|
-
files,
|
|
455
|
+
name: preset.name,
|
|
456
|
+
title: config.title,
|
|
457
|
+
description: config.description,
|
|
458
|
+
tags: config.tags ?? [],
|
|
459
|
+
license: config.license,
|
|
460
|
+
features: config.features ?? [],
|
|
461
|
+
variants,
|
|
415
462
|
...majorVersion !== void 0 && { version: majorVersion }
|
|
416
463
|
};
|
|
417
464
|
}
|
|
418
465
|
/**
|
|
419
|
-
* Builds a static registry with
|
|
420
|
-
*
|
|
421
|
-
*
|
|
466
|
+
* Builds a static registry with items and bundles.
|
|
467
|
+
*
|
|
468
|
+
* Uses the same model as dynamic publishing:
|
|
469
|
+
* - Each PresetInput (single or multi-platform) becomes one item
|
|
470
|
+
* - Each platform variant becomes one bundle
|
|
422
471
|
*/
|
|
423
472
|
async function buildPresetRegistry(options) {
|
|
424
473
|
const bundleBase = normalizeBundleBase(options.bundleBase);
|
|
425
|
-
const
|
|
474
|
+
const items = [];
|
|
426
475
|
const bundles = [];
|
|
427
476
|
for (const presetInput of options.presets) {
|
|
428
|
-
|
|
429
|
-
const
|
|
430
|
-
const
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
const
|
|
439
|
-
|
|
440
|
-
const version = `${majorVersion}.0`;
|
|
441
|
-
const slug = presetInput.name;
|
|
442
|
-
const entry = {
|
|
443
|
-
name: encodeItemName(slug, platform),
|
|
444
|
-
slug,
|
|
445
|
-
platform,
|
|
446
|
-
title: presetConfig.title,
|
|
447
|
-
version,
|
|
448
|
-
description: presetConfig.description,
|
|
449
|
-
tags: presetConfig.tags ?? [],
|
|
450
|
-
license: presetConfig.license,
|
|
451
|
-
features,
|
|
452
|
-
bundleUrl: getBundlePath(bundleBase, slug, platform, version),
|
|
453
|
-
fileCount: files.length,
|
|
454
|
-
totalSize
|
|
455
|
-
};
|
|
456
|
-
entries.push(entry);
|
|
457
|
-
const bundle = {
|
|
477
|
+
const publishInput = await buildPresetPublishInput({ preset: presetInput });
|
|
478
|
+
const slug = publishInput.name;
|
|
479
|
+
const version = `${publishInput.version ?? 1}.0`;
|
|
480
|
+
const presetVariants = publishInput.variants.map((v) => ({
|
|
481
|
+
platform: v.platform,
|
|
482
|
+
bundleUrl: getBundlePath(bundleBase, slug, v.platform, version),
|
|
483
|
+
fileCount: v.files.length,
|
|
484
|
+
totalSize: v.files.reduce((sum, f) => sum + f.content.length, 0)
|
|
485
|
+
}));
|
|
486
|
+
presetVariants.sort((a, b) => a.platform.localeCompare(b.platform));
|
|
487
|
+
const item = {
|
|
488
|
+
kind: "preset",
|
|
458
489
|
slug,
|
|
459
|
-
|
|
460
|
-
title:
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
490
|
+
name: publishInput.title,
|
|
491
|
+
title: publishInput.title,
|
|
492
|
+
description: publishInput.description,
|
|
493
|
+
tags: publishInput.tags,
|
|
494
|
+
license: publishInput.license,
|
|
495
|
+
features: publishInput.features ?? [],
|
|
496
|
+
versions: [{
|
|
497
|
+
version,
|
|
498
|
+
isLatest: true,
|
|
499
|
+
variants: presetVariants
|
|
500
|
+
}]
|
|
470
501
|
};
|
|
471
|
-
|
|
502
|
+
items.push(item);
|
|
503
|
+
for (const variant of publishInput.variants) {
|
|
504
|
+
const bundle = {
|
|
505
|
+
name: publishInput.name,
|
|
506
|
+
slug,
|
|
507
|
+
platform: variant.platform,
|
|
508
|
+
title: publishInput.title,
|
|
509
|
+
version,
|
|
510
|
+
description: publishInput.description,
|
|
511
|
+
tags: publishInput.tags,
|
|
512
|
+
license: publishInput.license,
|
|
513
|
+
features: publishInput.features,
|
|
514
|
+
files: variant.files,
|
|
515
|
+
readmeContent: variant.readmeContent,
|
|
516
|
+
licenseContent: variant.licenseContent,
|
|
517
|
+
installMessage: variant.installMessage
|
|
518
|
+
};
|
|
519
|
+
bundles.push(bundle);
|
|
520
|
+
}
|
|
472
521
|
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
}, {});
|
|
522
|
+
items.sort((a, b) => a.slug.localeCompare(b.slug));
|
|
523
|
+
bundles.sort((a, b) => {
|
|
524
|
+
if (a.slug === b.slug) return a.platform.localeCompare(b.platform);
|
|
525
|
+
return a.slug.localeCompare(b.slug);
|
|
526
|
+
});
|
|
479
527
|
return {
|
|
480
|
-
|
|
481
|
-
index,
|
|
528
|
+
items,
|
|
482
529
|
bundles
|
|
483
530
|
};
|
|
484
531
|
}
|
|
485
532
|
async function createBundledFilesFromInputs(files) {
|
|
486
533
|
const results = await Promise.all(files.map(async (file) => {
|
|
487
|
-
const payload = normalizeFilePayload(file.
|
|
488
|
-
const
|
|
534
|
+
const payload = normalizeFilePayload(file.content);
|
|
535
|
+
const content = encodeFilePayload(payload, file.path);
|
|
489
536
|
const checksum = await sha256(payload);
|
|
490
537
|
return {
|
|
491
538
|
path: toPosixPath(file.path),
|
|
492
539
|
size: payload.length,
|
|
493
540
|
checksum,
|
|
494
|
-
|
|
541
|
+
content
|
|
495
542
|
};
|
|
496
543
|
}));
|
|
497
544
|
return results.sort((a, b) => a.path.localeCompare(b.path));
|
|
498
545
|
}
|
|
499
|
-
function normalizeFilePayload(
|
|
500
|
-
if (typeof
|
|
501
|
-
if (
|
|
502
|
-
if (ArrayBuffer.isView(
|
|
503
|
-
return new Uint8Array(
|
|
546
|
+
function normalizeFilePayload(content) {
|
|
547
|
+
if (typeof content === "string") return new TextEncoder().encode(content);
|
|
548
|
+
if (content instanceof ArrayBuffer) return new Uint8Array(content);
|
|
549
|
+
if (ArrayBuffer.isView(content)) return new Uint8Array(content.buffer, content.byteOffset, content.byteLength);
|
|
550
|
+
return new Uint8Array(content);
|
|
504
551
|
}
|
|
505
552
|
function encodeFilePayload(data, filePath) {
|
|
506
553
|
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
@@ -510,36 +557,22 @@ function encodeFilePayload(data, filePath) {
|
|
|
510
557
|
throw new Error(`Binary files are not supported: "${filePath}". Only UTF-8 text files are allowed.`);
|
|
511
558
|
}
|
|
512
559
|
}
|
|
513
|
-
/**
|
|
514
|
-
* Normalize bundle base by removing trailing slashes.
|
|
515
|
-
* Returns empty string if base is undefined/empty (use default relative path).
|
|
516
|
-
*/
|
|
517
560
|
function normalizeBundleBase(base) {
|
|
518
561
|
if (!base) return "";
|
|
519
562
|
return base.replace(/\/+$/, "");
|
|
520
563
|
}
|
|
521
|
-
|
|
522
|
-
* Returns the bundle URL/path for a preset.
|
|
523
|
-
* Format: {base}/{STATIC_BUNDLE_DIR}/{slug}/{platform}/{version}
|
|
524
|
-
*/
|
|
525
|
-
function getBundlePath(base, slug, platform, version = LATEST_VERSION) {
|
|
564
|
+
function getBundlePath(base, slug, platform, version) {
|
|
526
565
|
const prefix = base ? `${base}/` : "";
|
|
527
566
|
return `${prefix}${STATIC_BUNDLE_DIR}/${slug}/${platform}/${version}`;
|
|
528
567
|
}
|
|
529
568
|
function ensureKnownPlatform(platform, slug) {
|
|
530
569
|
if (!isSupportedPlatform(platform)) throw new Error(`Unknown platform "${platform}" in ${slug}. Supported: ${PLATFORM_IDS.join(", ")}`);
|
|
531
570
|
}
|
|
532
|
-
function sortBySlugAndPlatform(items) {
|
|
533
|
-
items.sort((a, b) => {
|
|
534
|
-
if (a.slug === b.slug) return a.platform.localeCompare(b.platform);
|
|
535
|
-
return a.slug.localeCompare(b.slug);
|
|
536
|
-
});
|
|
537
|
-
}
|
|
538
571
|
|
|
539
572
|
//#endregion
|
|
540
573
|
//#region src/client/bundle.ts
|
|
541
574
|
function decodeBundledFile(file) {
|
|
542
|
-
return encodeUtf8(file.
|
|
575
|
+
return encodeUtf8(file.content);
|
|
543
576
|
}
|
|
544
577
|
async function verifyBundledFileChecksum(file, payload) {
|
|
545
578
|
const bytes = toUint8Array(payload);
|
|
@@ -567,39 +600,48 @@ async function sha256Hex(payload) {
|
|
|
567
600
|
}
|
|
568
601
|
|
|
569
602
|
//#endregion
|
|
570
|
-
//#region src/
|
|
603
|
+
//#region src/constants.ts
|
|
604
|
+
/**
|
|
605
|
+
* Shared constants for agentrules presets and registry.
|
|
606
|
+
*/
|
|
607
|
+
/** Filename for preset configuration */
|
|
608
|
+
const PRESET_CONFIG_FILENAME = "agentrules.json";
|
|
609
|
+
/** Directory name for preset metadata (README, LICENSE, etc.) */
|
|
610
|
+
const AGENT_RULES_DIR = ".agentrules";
|
|
611
|
+
/** JSON Schema URL for preset configuration */
|
|
612
|
+
const PRESET_SCHEMA_URL = "https://agentrules.directory/schema/agentrules.json";
|
|
613
|
+
/** API root path segment */
|
|
614
|
+
const API_PATH = "api";
|
|
615
|
+
/** Default version identifier for latest preset version */
|
|
616
|
+
const LATEST_VERSION = "latest";
|
|
571
617
|
/**
|
|
572
|
-
*
|
|
573
|
-
* Returns the entry metadata and the absolute bundle URL.
|
|
618
|
+
* API endpoint paths (relative to registry base URL).
|
|
574
619
|
*
|
|
575
|
-
*
|
|
576
|
-
*
|
|
577
|
-
*
|
|
578
|
-
* @param version - Version to resolve (defaults to "latest")
|
|
620
|
+
* Note on slug handling:
|
|
621
|
+
* - Slugs may contain slashes (e.g., "username/my-preset") which flow through as path segments
|
|
622
|
+
* - The client is responsible for validating values before making requests
|
|
579
623
|
*/
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
}
|
|
600
|
-
}
|
|
624
|
+
const API_ENDPOINTS = {
|
|
625
|
+
presets: {
|
|
626
|
+
base: `${API_PATH}/presets`,
|
|
627
|
+
unpublish: (slug, version) => `${API_PATH}/presets/${slug}/${version}`
|
|
628
|
+
},
|
|
629
|
+
auth: {
|
|
630
|
+
session: `${API_PATH}/auth/get-session`,
|
|
631
|
+
deviceCode: `${API_PATH}/auth/device/code`,
|
|
632
|
+
deviceToken: `${API_PATH}/auth/device/token`
|
|
633
|
+
},
|
|
634
|
+
rules: {
|
|
635
|
+
base: `${API_PATH}/rules`,
|
|
636
|
+
bySlug: (slug) => `${API_PATH}/rules/${slug}`
|
|
637
|
+
},
|
|
638
|
+
items: { get: (slug) => `${API_PATH}/items/${slug}` }
|
|
639
|
+
};
|
|
640
|
+
|
|
641
|
+
//#endregion
|
|
642
|
+
//#region src/client/registry.ts
|
|
601
643
|
/**
|
|
602
|
-
* Fetches a bundle from an absolute URL
|
|
644
|
+
* Fetches a bundle from an absolute URL.
|
|
603
645
|
*/
|
|
604
646
|
async function fetchBundle(bundleUrl) {
|
|
605
647
|
const response = await fetch(bundleUrl);
|
|
@@ -610,22 +652,200 @@ async function fetchBundle(bundleUrl) {
|
|
|
610
652
|
throw new Error(`Unable to parse bundle JSON: ${error.message}`);
|
|
611
653
|
}
|
|
612
654
|
}
|
|
655
|
+
/**
|
|
656
|
+
* Resolves a slug to get all versions and platform variants.
|
|
657
|
+
*
|
|
658
|
+
* @param baseUrl - Registry base URL
|
|
659
|
+
* @param slug - Content slug (may contain slashes, e.g., "username/my-preset")
|
|
660
|
+
* @param version - Optional version filter (server may ignore for static registries)
|
|
661
|
+
* @returns Resolved data, or null if not found
|
|
662
|
+
* @throws Error on network/server errors
|
|
663
|
+
*/
|
|
664
|
+
async function resolveSlug(baseUrl, slug, version) {
|
|
665
|
+
const url = new URL(API_ENDPOINTS.items.get(slug), baseUrl);
|
|
666
|
+
if (version) url.searchParams.set("version", version);
|
|
667
|
+
let response;
|
|
668
|
+
try {
|
|
669
|
+
response = await fetch(url);
|
|
670
|
+
} catch (error) {
|
|
671
|
+
throw new Error(`Failed to connect to registry: ${error.message}`);
|
|
672
|
+
}
|
|
673
|
+
if (response.status === 404) return null;
|
|
674
|
+
if (!response.ok) {
|
|
675
|
+
let errorMessage = `Registry returned ${response.status} ${response.statusText}`;
|
|
676
|
+
try {
|
|
677
|
+
const body = await response.json();
|
|
678
|
+
if (body && typeof body === "object" && "error" in body && typeof body.error === "string") errorMessage = body.error;
|
|
679
|
+
} catch {}
|
|
680
|
+
throw new Error(errorMessage);
|
|
681
|
+
}
|
|
682
|
+
const data = await response.json();
|
|
683
|
+
if (data.kind === "preset") {
|
|
684
|
+
for (const ver of data.versions) for (const variant of ver.variants) if ("bundleUrl" in variant) variant.bundleUrl = new URL(variant.bundleUrl, baseUrl).toString();
|
|
685
|
+
}
|
|
686
|
+
return data;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
//#endregion
|
|
690
|
+
//#region src/resolve/schema.ts
|
|
691
|
+
const VERSION_REGEX = /^[1-9]\d*\.\d+$/;
|
|
692
|
+
const presetVariantBundleSchema = z.object({
|
|
693
|
+
platform: platformIdSchema,
|
|
694
|
+
bundleUrl: z.string().min(1),
|
|
695
|
+
fileCount: z.number().int().nonnegative(),
|
|
696
|
+
totalSize: z.number().int().nonnegative()
|
|
697
|
+
});
|
|
698
|
+
const presetVariantInlineSchema = z.object({
|
|
699
|
+
platform: platformIdSchema,
|
|
700
|
+
content: z.string().min(1),
|
|
701
|
+
fileCount: z.number().int().nonnegative(),
|
|
702
|
+
totalSize: z.number().int().nonnegative()
|
|
703
|
+
});
|
|
704
|
+
const presetVariantSchema = z.union([presetVariantBundleSchema, presetVariantInlineSchema]);
|
|
705
|
+
const ruleVariantSchema = z.object({
|
|
706
|
+
platform: platformIdSchema,
|
|
707
|
+
type: z.string().min(1),
|
|
708
|
+
content: z.string().min(1)
|
|
709
|
+
});
|
|
710
|
+
const presetVersionSchema = z.object({
|
|
711
|
+
version: z.string().regex(VERSION_REGEX, "Version must be MAJOR.MINOR format"),
|
|
712
|
+
isLatest: z.boolean(),
|
|
713
|
+
publishedAt: z.string().datetime().optional(),
|
|
714
|
+
variants: z.array(presetVariantSchema).min(1)
|
|
715
|
+
});
|
|
716
|
+
const ruleVersionSchema = z.object({
|
|
717
|
+
version: z.string().regex(VERSION_REGEX, "Version must be MAJOR.MINOR format"),
|
|
718
|
+
isLatest: z.boolean(),
|
|
719
|
+
publishedAt: z.string().datetime().optional(),
|
|
720
|
+
variants: z.array(ruleVariantSchema).min(1)
|
|
721
|
+
});
|
|
722
|
+
const resolvedPresetSchema = z.object({
|
|
723
|
+
kind: z.literal("preset"),
|
|
724
|
+
slug: z.string().min(1),
|
|
725
|
+
name: z.string().min(1),
|
|
726
|
+
title: z.string().min(1),
|
|
727
|
+
description: z.string(),
|
|
728
|
+
tags: z.array(z.string()),
|
|
729
|
+
license: z.string(),
|
|
730
|
+
features: z.array(z.string()),
|
|
731
|
+
versions: z.array(presetVersionSchema).min(1)
|
|
732
|
+
});
|
|
733
|
+
const resolvedRuleSchema = z.object({
|
|
734
|
+
kind: z.literal("rule"),
|
|
735
|
+
slug: z.string().min(1),
|
|
736
|
+
name: z.string().min(1),
|
|
737
|
+
title: z.string().min(1),
|
|
738
|
+
description: z.string(),
|
|
739
|
+
tags: z.array(z.string()),
|
|
740
|
+
versions: z.array(ruleVersionSchema).min(1)
|
|
741
|
+
});
|
|
742
|
+
const resolveResponseSchema = z.discriminatedUnion("kind", [resolvedPresetSchema, resolvedRuleSchema]);
|
|
743
|
+
|
|
744
|
+
//#endregion
|
|
745
|
+
//#region src/resolve/utils.ts
|
|
746
|
+
/**
|
|
747
|
+
* Type guard for preset
|
|
748
|
+
*/
|
|
749
|
+
function isPreset(item) {
|
|
750
|
+
return item.kind === "preset";
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* Type guard for rule
|
|
754
|
+
*/
|
|
755
|
+
function isRule(item) {
|
|
756
|
+
return item.kind === "rule";
|
|
757
|
+
}
|
|
758
|
+
/**
|
|
759
|
+
* Type guard for preset variant with bundleUrl
|
|
760
|
+
*/
|
|
761
|
+
function hasBundle(variant) {
|
|
762
|
+
return "bundleUrl" in variant;
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* Type guard for preset variant with inline content
|
|
766
|
+
*/
|
|
767
|
+
function hasInlineContent(variant) {
|
|
768
|
+
return "content" in variant;
|
|
769
|
+
}
|
|
770
|
+
/**
|
|
771
|
+
* Get the latest version from a resolved preset
|
|
772
|
+
*/
|
|
773
|
+
function getLatestPresetVersion(item) {
|
|
774
|
+
return item.versions.find((v) => v.isLatest);
|
|
775
|
+
}
|
|
776
|
+
/**
|
|
777
|
+
* Get the latest version from a resolved rule
|
|
778
|
+
*/
|
|
779
|
+
function getLatestRuleVersion(item) {
|
|
780
|
+
return item.versions.find((v) => v.isLatest);
|
|
781
|
+
}
|
|
782
|
+
/**
|
|
783
|
+
* Get a specific version from a resolved preset
|
|
784
|
+
*/
|
|
785
|
+
function getPresetVersion(item, version) {
|
|
786
|
+
return item.versions.find((v) => v.version === version);
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Get a specific version from a resolved rule
|
|
790
|
+
*/
|
|
791
|
+
function getRuleVersion(item, version) {
|
|
792
|
+
return item.versions.find((v) => v.version === version);
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* Get a specific platform variant from a preset version
|
|
796
|
+
*/
|
|
797
|
+
function getPresetVariant(version, platform) {
|
|
798
|
+
return version.variants.find((v) => v.platform === platform);
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* Get a specific platform variant from a rule version
|
|
802
|
+
*/
|
|
803
|
+
function getRuleVariant(version, platform) {
|
|
804
|
+
return version.variants.find((v) => v.platform === platform);
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Get all available platforms for a preset version
|
|
808
|
+
*/
|
|
809
|
+
function getPresetPlatforms(version) {
|
|
810
|
+
return version.variants.map((v) => v.platform);
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* Get all available platforms for a rule version
|
|
814
|
+
*/
|
|
815
|
+
function getRulePlatforms(version) {
|
|
816
|
+
return version.variants.map((v) => v.platform);
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* Check if a platform is available in any version of a preset
|
|
820
|
+
*/
|
|
821
|
+
function presetHasPlatform(item, platform) {
|
|
822
|
+
return item.versions.some((v) => v.variants.some((variant) => variant.platform === platform));
|
|
823
|
+
}
|
|
824
|
+
/**
|
|
825
|
+
* Check if a platform is available in any version of a rule
|
|
826
|
+
*/
|
|
827
|
+
function ruleHasPlatform(item, platform) {
|
|
828
|
+
return item.versions.some((v) => v.variants.some((variant) => variant.platform === platform));
|
|
829
|
+
}
|
|
613
830
|
|
|
614
831
|
//#endregion
|
|
615
832
|
//#region src/rule/schema.ts
|
|
616
|
-
const NAME_REGEX = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
617
833
|
/**
|
|
618
|
-
*
|
|
619
|
-
*
|
|
834
|
+
* Rule-specific schema aliases.
|
|
835
|
+
* All use shared schemas for consistency with presets:
|
|
836
|
+
* - name: max 64 chars, lowercase kebab-case
|
|
837
|
+
* - title: max 80 chars
|
|
838
|
+
* - description: max 500 chars (optional for rules)
|
|
839
|
+
* - tags: max 35 chars each, 1-10 required, platform names blocked
|
|
620
840
|
*/
|
|
621
|
-
const ruleNameSchema =
|
|
622
|
-
const ruleTitleSchema =
|
|
623
|
-
const ruleDescriptionSchema =
|
|
841
|
+
const ruleNameSchema = nameSchema;
|
|
842
|
+
const ruleTitleSchema = titleSchema;
|
|
843
|
+
const ruleDescriptionSchema = descriptionSchema;
|
|
844
|
+
const ruleTagSchema = tagSchema;
|
|
845
|
+
const ruleTagsSchema = tagsSchema;
|
|
624
846
|
const rulePlatformSchema = z.enum(PLATFORM_IDS);
|
|
625
847
|
const ruleTypeSchema = z.string().trim().min(1).max(32);
|
|
626
848
|
const ruleContentSchema = z.string().min(1, "Content is required").max(1e5, "Content must be 100KB or less");
|
|
627
|
-
const ruleTagSchema = z.string().trim().min(1).max(32).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "Tags must be lowercase alphanumeric with single hyphens between words");
|
|
628
|
-
const ruleTagsSchema = z.array(ruleTagSchema).min(1, "At least 1 tag is required").max(10, "Maximum 10 tags allowed");
|
|
629
849
|
/** Common fields shared across all platform-type combinations */
|
|
630
850
|
const ruleCommonFields = {
|
|
631
851
|
name: ruleNameSchema,
|
|
@@ -658,13 +878,6 @@ const rulePlatformTypeSchema = z.discriminatedUnion("platform", [
|
|
|
658
878
|
]);
|
|
659
879
|
/** Schema for rule creation with discriminated union for platform+type */
|
|
660
880
|
const ruleCreateInputSchema = z.object(ruleCommonFields).and(rulePlatformTypeSchema);
|
|
661
|
-
const ruleUpdateInputSchema = z.object({
|
|
662
|
-
name: ruleNameSchema,
|
|
663
|
-
title: ruleTitleSchema.optional(),
|
|
664
|
-
description: ruleDescriptionSchema.optional(),
|
|
665
|
-
content: ruleContentSchema.optional(),
|
|
666
|
-
tags: ruleTagsSchema.optional()
|
|
667
|
-
});
|
|
668
881
|
|
|
669
882
|
//#endregion
|
|
670
883
|
//#region src/utils/diff.ts
|
|
@@ -690,4 +903,4 @@ function normalizeBundlePath(value) {
|
|
|
690
903
|
}
|
|
691
904
|
|
|
692
905
|
//#endregion
|
|
693
|
-
export { AGENT_RULES_DIR, API_ENDPOINTS, COMMON_LICENSES, LATEST_VERSION, PLATFORMS, PLATFORM_IDS, PLATFORM_ID_TUPLE, PLATFORM_RULE_TYPES, PRESET_CONFIG_FILENAME, PRESET_SCHEMA_URL, STATIC_BUNDLE_DIR, buildPresetPublishInput, buildPresetRegistry, bundledFileSchema, cleanInstallMessage, createDiffPreview, decodeBundledFile, decodeUtf8, descriptionSchema,
|
|
906
|
+
export { AGENT_RULES_DIR, API_ENDPOINTS, COMMON_LICENSES, LATEST_VERSION, PLATFORMS, PLATFORM_IDS, PLATFORM_ID_TUPLE, PLATFORM_RULE_TYPES, PRESET_CONFIG_FILENAME, PRESET_SCHEMA_URL, STATIC_BUNDLE_DIR, buildPresetPublishInput, buildPresetRegistry, bundledFileSchema, cleanInstallMessage, createDiffPreview, decodeBundledFile, decodeUtf8, descriptionSchema, encodeUtf8, fetchBundle, getInstallPath, getLatestPresetVersion, getLatestRuleVersion, getPlatformConfig, getPlatformFromDir, getPresetPlatforms, getPresetVariant, getPresetVersion, getRulePlatforms, getRuleTypeConfig, getRuleVariant, getRuleVersion, getValidRuleTypes, hasBundle, hasInlineContent, isLikelyText, isPlatformDir, isPreset, isRule, isSupportedPlatform, isValidRuleType, licenseSchema, nameSchema, normalizeBundlePath, normalizePlatformEntry, normalizePlatformInput, platformIdSchema, presetBundleSchema, presetConfigSchema, presetHasPlatform, presetPublishInputSchema, presetVariantSchema, presetVersionSchema, publishVariantInputSchema, requiredDescriptionSchema, resolveResponseSchema, resolveSlug, resolvedPresetSchema, resolvedRuleSchema, ruleContentSchema, ruleCreateInputSchema, ruleDescriptionSchema, ruleHasPlatform, ruleNameSchema, rulePlatformSchema, rulePlatformTypeSchema, ruleTagSchema, ruleTagsSchema, ruleTitleSchema, ruleTypeSchema, ruleVariantSchema, ruleVersionSchema, tagSchema, tagsSchema, titleSchema, toPosixPath, toUint8Array, toUtf8String, validatePresetConfig, verifyBundledFileChecksum };
|