@agentrules/core 0.0.1 → 0.0.3
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 +140 -0
- package/dist/index.d.ts +138 -77
- package/dist/index.js +260 -159
- package/package.json +2 -2
package/README.md
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# @agentrules/core
|
|
2
|
+
|
|
3
|
+
Shared types and utilities for the AGENT_RULES ecosystem.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @agentrules/core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **Types** - TypeScript definitions for presets, bundles, and registry entries
|
|
14
|
+
- **Validation** - Zod schemas for validating `agentrules.json` configs
|
|
15
|
+
- **Registry Builder** - Transform preset inputs into registry JSON artifacts
|
|
16
|
+
- **Bundle Utilities** - Checksum verification, encoding/decoding helpers
|
|
17
|
+
- **Diff Utilities** - Generate previews for file conflicts
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
### Building Registry Data
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { buildRegistryData } from "@agentrules/core";
|
|
25
|
+
|
|
26
|
+
const result = buildRegistryData({
|
|
27
|
+
bundleBase: "/r",
|
|
28
|
+
presets: [
|
|
29
|
+
{
|
|
30
|
+
slug: "my-preset",
|
|
31
|
+
config: {
|
|
32
|
+
name: "my-preset",
|
|
33
|
+
title: "My Preset",
|
|
34
|
+
version: "1.0.0",
|
|
35
|
+
description: "A helpful preset",
|
|
36
|
+
platforms: {
|
|
37
|
+
opencode: { path: ".opencode" },
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
platforms: [
|
|
41
|
+
{
|
|
42
|
+
platform: "opencode",
|
|
43
|
+
files: [
|
|
44
|
+
{ path: "AGENT_RULES.md", contents: "# Rules\n" },
|
|
45
|
+
{ path: "config.json", contents: '{"key": "value"}' },
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// result.entries → array for registry.json
|
|
54
|
+
// result.index → object for registry.index.json
|
|
55
|
+
// result.bundles → per-platform bundle payloads
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Validating Preset Config
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
import { validatePresetConfig, presetConfigSchema } from "@agentrules/core";
|
|
62
|
+
|
|
63
|
+
// Quick validation (throws on error)
|
|
64
|
+
const config = validatePresetConfig(jsonData, "my-preset");
|
|
65
|
+
|
|
66
|
+
// Zod schema for custom handling
|
|
67
|
+
const result = presetConfigSchema.safeParse(jsonData);
|
|
68
|
+
if (!result.success) {
|
|
69
|
+
console.error(result.error.issues);
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Fetching from Registry
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
import {
|
|
77
|
+
fetchRegistryIndex,
|
|
78
|
+
fetchRegistryBundle,
|
|
79
|
+
resolveRegistryEntry,
|
|
80
|
+
} from "@agentrules/core";
|
|
81
|
+
|
|
82
|
+
const index = await fetchRegistryIndex("https://agentrules.directory/r/");
|
|
83
|
+
const entry = resolveRegistryEntry(index, "agentic-dev-starter", "opencode");
|
|
84
|
+
const { bundle } = await fetchRegistryBundle(
|
|
85
|
+
"https://agentrules.directory/r/",
|
|
86
|
+
entry.bundlePath
|
|
87
|
+
);
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Working with Bundles
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
import {
|
|
94
|
+
decodeBundledFile,
|
|
95
|
+
verifyBundledFileChecksum,
|
|
96
|
+
isLikelyText,
|
|
97
|
+
} from "@agentrules/core";
|
|
98
|
+
|
|
99
|
+
for (const file of bundle.files) {
|
|
100
|
+
const data = decodeBundledFile(file);
|
|
101
|
+
await verifyBundledFileChecksum(file, data);
|
|
102
|
+
|
|
103
|
+
if (isLikelyText(data)) {
|
|
104
|
+
console.log(`Text file: ${file.path}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Preset Config Format
|
|
110
|
+
|
|
111
|
+
Presets use `agentrules.json`:
|
|
112
|
+
|
|
113
|
+
```json
|
|
114
|
+
{
|
|
115
|
+
"$schema": "https://agentrules.directory/schema/agentrules.json",
|
|
116
|
+
"name": "my-preset",
|
|
117
|
+
"title": "My Preset",
|
|
118
|
+
"version": "1.0.0",
|
|
119
|
+
"description": "Description here",
|
|
120
|
+
"author": { "name": "Your Name" },
|
|
121
|
+
"license": "MIT",
|
|
122
|
+
"tags": ["starter", "typescript"],
|
|
123
|
+
"platforms": {
|
|
124
|
+
"opencode": {
|
|
125
|
+
"path": "opencode/files/.opencode",
|
|
126
|
+
"features": ["Feature 1", "Feature 2"],
|
|
127
|
+
"installMessage": "Thanks for installing!"
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Development
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
bun install
|
|
137
|
+
bun run build # build with tsdown
|
|
138
|
+
bun run test # run tests
|
|
139
|
+
bun run typecheck # type checking
|
|
140
|
+
```
|
package/dist/index.d.ts
CHANGED
|
@@ -1,31 +1,62 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
|
|
3
|
-
//#region src/types.d.ts
|
|
4
|
-
|
|
3
|
+
//#region src/types/platform.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Single source of truth for platform IDs.
|
|
7
|
+
* Add new platforms here - types and config will follow.
|
|
8
|
+
*/
|
|
9
|
+
declare const PLATFORM_ID_TUPLE: readonly ["opencode", "codex", "claude", "cursor"];
|
|
10
|
+
/** Union type of supported platform IDs, derived from PLATFORM_ID_TUPLE */
|
|
11
|
+
type PlatformId = (typeof PLATFORM_ID_TUPLE)[number];
|
|
12
|
+
/** List of supported platform IDs as a readonly tuple */
|
|
13
|
+
declare const PLATFORM_IDS: readonly ["opencode", "codex", "claude", "cursor"];
|
|
14
|
+
type PlatformConfig = {
|
|
15
|
+
/** Directory name for project installs (e.g., ".opencode") */
|
|
16
|
+
projectDir: string;
|
|
17
|
+
/** Path for global installs (e.g., "~/.config/opencode") */
|
|
18
|
+
globalDir: string;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Platform-specific configuration.
|
|
22
|
+
* Single source of truth for all platform paths.
|
|
23
|
+
*/
|
|
24
|
+
declare const PLATFORMS: Record<PlatformId, PlatformConfig>;
|
|
25
|
+
/**
|
|
26
|
+
* Convention: preset files under this directory map to the platform config directory.
|
|
27
|
+
* e.g., `config/agent.md` → `.opencode/agent.md` (project) or `agent.md` (global)
|
|
28
|
+
*/
|
|
29
|
+
declare const CONFIG_DIR_NAME = "config";
|
|
30
|
+
declare function isSupportedPlatform(value: string): value is PlatformId;
|
|
31
|
+
declare function normalizePlatformInput(value: string): PlatformId;
|
|
32
|
+
//#endregion
|
|
33
|
+
//#region src/types/definitions.d.ts
|
|
5
34
|
type AuthorInfo = {
|
|
6
35
|
name: string;
|
|
7
36
|
email?: string;
|
|
8
37
|
url?: string;
|
|
9
38
|
};
|
|
10
|
-
type
|
|
39
|
+
type PlatformPresetConfig = {
|
|
40
|
+
path: string;
|
|
11
41
|
features?: string[];
|
|
12
42
|
installMessage?: string;
|
|
13
43
|
};
|
|
14
44
|
type PresetConfig = {
|
|
45
|
+
$schema?: string;
|
|
46
|
+
name: string;
|
|
15
47
|
title: string;
|
|
16
48
|
version: string;
|
|
17
49
|
description: string;
|
|
18
50
|
tags?: string[];
|
|
19
51
|
author?: AuthorInfo;
|
|
20
52
|
license?: string;
|
|
21
|
-
|
|
22
|
-
platforms: Partial<Record<PlatformId, PlatformSpecificConfig>>;
|
|
53
|
+
platforms: Partial<Record<PlatformId, PlatformPresetConfig>>;
|
|
23
54
|
};
|
|
24
55
|
type BundledFile = {
|
|
25
56
|
path: string;
|
|
57
|
+
/** File size in bytes */
|
|
26
58
|
size: number;
|
|
27
59
|
checksum: string;
|
|
28
|
-
encoding: "utf-8" | "base64";
|
|
29
60
|
contents: string;
|
|
30
61
|
};
|
|
31
62
|
type RegistryBundle = {
|
|
@@ -39,6 +70,7 @@ type RegistryBundle = {
|
|
|
39
70
|
license?: string;
|
|
40
71
|
features?: string[];
|
|
41
72
|
installMessage?: string;
|
|
73
|
+
readme?: string;
|
|
42
74
|
files: BundledFile[];
|
|
43
75
|
};
|
|
44
76
|
type RegistryEntry = {
|
|
@@ -55,73 +87,32 @@ type RegistryEntry = {
|
|
|
55
87
|
installMessage?: string;
|
|
56
88
|
bundlePath: string;
|
|
57
89
|
fileCount: number;
|
|
58
|
-
|
|
59
|
-
|
|
90
|
+
/** Total size of all files in bytes */
|
|
91
|
+
totalSize: number;
|
|
92
|
+
/** Whether the preset has a README */
|
|
93
|
+
hasReadme?: boolean;
|
|
60
94
|
};
|
|
61
95
|
type RegistryData = {
|
|
62
96
|
$schema: string;
|
|
63
97
|
items: RegistryEntry[];
|
|
64
98
|
};
|
|
65
99
|
type RegistryIndex = Record<string, RegistryEntry>;
|
|
66
|
-
type RegistryIndexItem = RegistryEntry;
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
declare function cleanInstallMessage(value: unknown): string | undefined;
|
|
71
|
-
declare function encodeItemName(slug: string, platform: PlatformId): string;
|
|
72
|
-
declare function validatePresetConfig(config: unknown, slug: string): PresetConfig;
|
|
73
|
-
declare function collectBundledFiles(files: Record<string, string>): BundledFile[];
|
|
74
|
-
|
|
75
|
-
//#endregion
|
|
76
|
-
//#region src/bundle.d.ts
|
|
77
|
-
declare function decodeBundledFile(file: BundledFile): Uint8Array;
|
|
78
|
-
declare function verifyBundledFileChecksum(file: BundledFile, payload: ArrayBuffer | ArrayBufferView): Promise<void>;
|
|
79
|
-
declare function isLikelyText(payload: ArrayBuffer | ArrayBufferView): boolean;
|
|
80
|
-
declare function toUtf8String(payload: ArrayBuffer | ArrayBufferView): string;
|
|
81
|
-
|
|
82
|
-
//#endregion
|
|
83
|
-
//#region src/diff.d.ts
|
|
84
|
-
type DiffPreviewOptions = {
|
|
85
|
-
context?: number;
|
|
86
|
-
maxLines?: number;
|
|
100
|
+
type RegistryIndexItem = RegistryEntry;
|
|
101
|
+
type RegistryFileInput = {
|
|
102
|
+
path: string;
|
|
103
|
+
contents: ArrayBuffer | ArrayBufferView | string;
|
|
87
104
|
};
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
//#region src/encoding.d.ts
|
|
92
|
-
declare function toPosixPath(pathValue: string): string;
|
|
93
|
-
declare function encodeUtf8(value: string): Uint8Array<ArrayBuffer>;
|
|
94
|
-
declare function decodeUtf8(payload: ArrayBuffer | ArrayBufferView): string;
|
|
95
|
-
declare function toUint8Array(payload: ArrayBuffer | ArrayBufferView): Uint8Array<ArrayBufferLike>;
|
|
96
|
-
|
|
97
|
-
//#endregion
|
|
98
|
-
//#region src/paths.d.ts
|
|
99
|
-
declare function normalizeBundlePath(value: string): string;
|
|
100
|
-
declare function normalizePathFragment(value?: string): string | undefined;
|
|
101
|
-
declare function maybeStripPrefix(pathInput: string, prefix?: string): string;
|
|
102
|
-
|
|
103
|
-
//#endregion
|
|
104
|
-
//#region src/platform.d.ts
|
|
105
|
-
declare const PLATFORM_IDS: ["opencode", "codex", "claude", "cursor"];
|
|
106
|
-
declare function isSupportedPlatform(value: string): value is PlatformId;
|
|
107
|
-
declare function normalizePlatformInput(value: string): PlatformId;
|
|
108
|
-
|
|
109
|
-
//#endregion
|
|
110
|
-
//#region src/preset.d.ts
|
|
111
|
-
declare function definePreset(config: PresetConfig): PresetConfig;
|
|
112
|
-
|
|
113
|
-
//#endregion
|
|
114
|
-
//#region src/registry.d.ts
|
|
115
|
-
type FetchRegistryBundleResult = {
|
|
116
|
-
bundle: RegistryBundle;
|
|
117
|
-
etag: string | null;
|
|
105
|
+
type RegistryPlatformInput = {
|
|
106
|
+
platform: PlatformId;
|
|
107
|
+
files: RegistryFileInput[];
|
|
118
108
|
};
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
//#
|
|
109
|
+
type RegistryPresetInput = {
|
|
110
|
+
slug: string;
|
|
111
|
+
config: PresetConfig;
|
|
112
|
+
platforms: RegistryPlatformInput[];
|
|
113
|
+
readme?: string;
|
|
114
|
+
}; //#endregion
|
|
115
|
+
//#region src/types/schema.d.ts
|
|
125
116
|
declare const platformIdSchema: z.ZodEnum<{
|
|
126
117
|
opencode: "opencode";
|
|
127
118
|
codex: "codex";
|
|
@@ -133,7 +124,14 @@ declare const authorSchema: z.ZodObject<{
|
|
|
133
124
|
email: z.ZodOptional<z.ZodEmail>;
|
|
134
125
|
url: z.ZodOptional<z.ZodURL>;
|
|
135
126
|
}, z.core.$strict>;
|
|
136
|
-
declare const
|
|
127
|
+
declare const platformPresetConfigSchema: z.ZodObject<{
|
|
128
|
+
path: z.ZodString;
|
|
129
|
+
features: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
130
|
+
installMessage: z.ZodOptional<z.ZodString>;
|
|
131
|
+
}, z.core.$strict>;
|
|
132
|
+
declare const presetConfigSchema: z.ZodObject<{
|
|
133
|
+
$schema: z.ZodOptional<z.ZodString>;
|
|
134
|
+
name: z.ZodString;
|
|
137
135
|
title: z.ZodString;
|
|
138
136
|
version: z.ZodString;
|
|
139
137
|
description: z.ZodString;
|
|
@@ -144,14 +142,18 @@ declare const platformPresetSchema: z.ZodObject<{
|
|
|
144
142
|
url: z.ZodOptional<z.ZodURL>;
|
|
145
143
|
}, z.core.$strict>>;
|
|
146
144
|
license: z.ZodOptional<z.ZodString>;
|
|
147
|
-
|
|
148
|
-
|
|
145
|
+
platforms: z.ZodObject<{
|
|
146
|
+
[x: string]: z.ZodOptional<z.ZodObject<{
|
|
147
|
+
path: z.ZodString;
|
|
148
|
+
features: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
149
|
+
installMessage: z.ZodOptional<z.ZodString>;
|
|
150
|
+
}, z.core.$strict>>;
|
|
151
|
+
}, z.core.$strip>;
|
|
149
152
|
}, z.core.$strict>;
|
|
150
153
|
declare const bundledFileSchema: z.ZodObject<{
|
|
151
154
|
path: z.ZodString;
|
|
152
155
|
size: z.ZodNumber;
|
|
153
156
|
checksum: z.ZodString;
|
|
154
|
-
encoding: z.ZodUnion<readonly [z.ZodLiteral<"utf-8">, z.ZodLiteral<"base64">]>;
|
|
155
157
|
contents: z.ZodString;
|
|
156
158
|
}, z.core.$strip>;
|
|
157
159
|
declare const registryBundleSchema: z.ZodObject<{
|
|
@@ -178,11 +180,12 @@ declare const registryBundleSchema: z.ZodObject<{
|
|
|
178
180
|
path: z.ZodString;
|
|
179
181
|
size: z.ZodNumber;
|
|
180
182
|
checksum: z.ZodString;
|
|
181
|
-
encoding: z.ZodUnion<readonly [z.ZodLiteral<"utf-8">, z.ZodLiteral<"base64">]>;
|
|
182
183
|
contents: z.ZodString;
|
|
183
184
|
}, z.core.$strip>>;
|
|
184
185
|
}, z.core.$strip>;
|
|
185
186
|
declare const registryEntrySchema: z.ZodObject<{
|
|
187
|
+
features: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
188
|
+
installMessage: z.ZodOptional<z.ZodString>;
|
|
186
189
|
title: z.ZodString;
|
|
187
190
|
version: z.ZodString;
|
|
188
191
|
description: z.ZodString;
|
|
@@ -193,8 +196,6 @@ declare const registryEntrySchema: z.ZodObject<{
|
|
|
193
196
|
url: z.ZodOptional<z.ZodURL>;
|
|
194
197
|
}, z.core.$strict>>;
|
|
195
198
|
license: z.ZodOptional<z.ZodString>;
|
|
196
|
-
features: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
197
|
-
installMessage: z.ZodOptional<z.ZodString>;
|
|
198
199
|
platform: z.ZodEnum<{
|
|
199
200
|
opencode: "opencode";
|
|
200
201
|
codex: "codex";
|
|
@@ -205,9 +206,11 @@ declare const registryEntrySchema: z.ZodObject<{
|
|
|
205
206
|
name: z.ZodString;
|
|
206
207
|
bundlePath: z.ZodString;
|
|
207
208
|
fileCount: z.ZodNumber;
|
|
208
|
-
|
|
209
|
+
totalSize: z.ZodNumber;
|
|
209
210
|
}, z.core.$strip>;
|
|
210
211
|
declare const registryIndexSchema: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
212
|
+
features: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
213
|
+
installMessage: z.ZodOptional<z.ZodString>;
|
|
211
214
|
title: z.ZodString;
|
|
212
215
|
version: z.ZodString;
|
|
213
216
|
description: z.ZodString;
|
|
@@ -218,8 +221,6 @@ declare const registryIndexSchema: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
|
218
221
|
url: z.ZodOptional<z.ZodURL>;
|
|
219
222
|
}, z.core.$strict>>;
|
|
220
223
|
license: z.ZodOptional<z.ZodString>;
|
|
221
|
-
features: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
222
|
-
installMessage: z.ZodOptional<z.ZodString>;
|
|
223
224
|
platform: z.ZodEnum<{
|
|
224
225
|
opencode: "opencode";
|
|
225
226
|
codex: "codex";
|
|
@@ -230,8 +231,68 @@ declare const registryIndexSchema: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
|
230
231
|
name: z.ZodString;
|
|
231
232
|
bundlePath: z.ZodString;
|
|
232
233
|
fileCount: z.ZodNumber;
|
|
233
|
-
|
|
234
|
+
totalSize: z.ZodNumber;
|
|
234
235
|
}, z.core.$strip>>;
|
|
235
236
|
|
|
236
237
|
//#endregion
|
|
237
|
-
|
|
238
|
+
//#region src/builder/registry.d.ts
|
|
239
|
+
type BuildRegistryDataOptions = {
|
|
240
|
+
presets: RegistryPresetInput[];
|
|
241
|
+
bundleBase?: string;
|
|
242
|
+
};
|
|
243
|
+
type BuildRegistryDataResult = {
|
|
244
|
+
entries: RegistryEntry[];
|
|
245
|
+
index: RegistryIndex;
|
|
246
|
+
bundles: RegistryBundle[];
|
|
247
|
+
};
|
|
248
|
+
declare function buildRegistryData(options: BuildRegistryDataOptions): BuildRegistryDataResult;
|
|
249
|
+
|
|
250
|
+
//#endregion
|
|
251
|
+
//#region src/builder/utils.d.ts
|
|
252
|
+
declare function normalizeBundlePublicBase(value: string): string;
|
|
253
|
+
declare function isAbsoluteUrl(value: string): boolean;
|
|
254
|
+
declare function cleanInstallMessage(value: unknown): string | undefined;
|
|
255
|
+
declare function encodeItemName(slug: string, platform: PlatformId): string;
|
|
256
|
+
declare function validatePresetConfig(config: unknown, slug: string): PresetConfig;
|
|
257
|
+
declare function collectBundledFiles(files: Record<string, string>): BundledFile[];
|
|
258
|
+
|
|
259
|
+
//#endregion
|
|
260
|
+
//#region src/client/bundle.d.ts
|
|
261
|
+
declare function decodeBundledFile(file: BundledFile): Uint8Array;
|
|
262
|
+
declare function verifyBundledFileChecksum(file: BundledFile, payload: ArrayBuffer | ArrayBufferView): Promise<void>;
|
|
263
|
+
declare function isLikelyText(payload: ArrayBuffer | ArrayBufferView): boolean;
|
|
264
|
+
declare function toUtf8String(payload: ArrayBuffer | ArrayBufferView): string;
|
|
265
|
+
|
|
266
|
+
//#endregion
|
|
267
|
+
//#region src/client/registry.d.ts
|
|
268
|
+
type FetchRegistryBundleResult = {
|
|
269
|
+
bundle: RegistryBundle;
|
|
270
|
+
etag: string | null;
|
|
271
|
+
};
|
|
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
|
+
|
|
276
|
+
//#endregion
|
|
277
|
+
//#region src/utils/diff.d.ts
|
|
278
|
+
type DiffPreviewOptions = {
|
|
279
|
+
context?: number;
|
|
280
|
+
maxLines?: number;
|
|
281
|
+
};
|
|
282
|
+
declare function createDiffPreview(path: string, currentText: string, incomingText: string, options?: DiffPreviewOptions): string;
|
|
283
|
+
|
|
284
|
+
//#endregion
|
|
285
|
+
//#region src/utils/encoding.d.ts
|
|
286
|
+
declare function toPosixPath(pathValue: string): string;
|
|
287
|
+
declare function encodeUtf8(value: string): Uint8Array<ArrayBuffer>;
|
|
288
|
+
declare function decodeUtf8(payload: ArrayBuffer | ArrayBufferView): string;
|
|
289
|
+
declare function toUint8Array(payload: ArrayBuffer | ArrayBufferView): Uint8Array<ArrayBufferLike>;
|
|
290
|
+
|
|
291
|
+
//#endregion
|
|
292
|
+
//#region src/utils/paths.d.ts
|
|
293
|
+
declare function normalizeBundlePath(value: string): string;
|
|
294
|
+
declare function normalizePathFragment(value?: string): string | undefined;
|
|
295
|
+
declare function maybeStripPrefix(pathInput: string, prefix?: string): string;
|
|
296
|
+
|
|
297
|
+
//#endregion
|
|
298
|
+
export { AuthorInfo, BuildRegistryDataOptions, BuildRegistryDataResult, BundledFile, CONFIG_DIR_NAME, DiffPreviewOptions, FetchRegistryBundleResult, PLATFORMS, PLATFORM_IDS, PlatformId, PlatformPresetConfig, PresetConfig, RegistryBundle, RegistryData, RegistryEntry, RegistryFileInput, RegistryIndex, RegistryIndexItem, RegistryPlatformInput, RegistryPresetInput, authorSchema, buildRegistryData, bundledFileSchema, cleanInstallMessage, collectBundledFiles, createDiffPreview, decodeBundledFile, decodeUtf8, encodeItemName, encodeUtf8, fetchRegistryBundle, fetchRegistryIndex, isAbsoluteUrl, isLikelyText, isSupportedPlatform, maybeStripPrefix, normalizeBundlePath, normalizeBundlePublicBase, normalizePathFragment, normalizePlatformInput, platformIdSchema, platformPresetConfigSchema, presetConfigSchema, registryBundleSchema, registryEntrySchema, registryIndexSchema, resolveRegistryEntry, toPosixPath, toUint8Array, toUtf8String, validatePresetConfig, verifyBundledFileChecksum };
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,122 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createHash } from "crypto";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
+
import { createTwoFilesPatch } from "diff";
|
|
3
4
|
|
|
4
|
-
//#region src/
|
|
5
|
+
//#region src/types/platform.ts
|
|
6
|
+
/**
|
|
7
|
+
* Single source of truth for platform IDs.
|
|
8
|
+
* Add new platforms here - types and config will follow.
|
|
9
|
+
*/
|
|
10
|
+
const PLATFORM_ID_TUPLE = [
|
|
11
|
+
"opencode",
|
|
12
|
+
"codex",
|
|
13
|
+
"claude",
|
|
14
|
+
"cursor"
|
|
15
|
+
];
|
|
16
|
+
/** List of supported platform IDs as a readonly tuple */
|
|
17
|
+
const PLATFORM_IDS = PLATFORM_ID_TUPLE;
|
|
18
|
+
/**
|
|
19
|
+
* Platform-specific configuration.
|
|
20
|
+
* Single source of truth for all platform paths.
|
|
21
|
+
*/
|
|
22
|
+
const PLATFORMS = {
|
|
23
|
+
opencode: {
|
|
24
|
+
projectDir: ".opencode",
|
|
25
|
+
globalDir: "~/.config/opencode"
|
|
26
|
+
},
|
|
27
|
+
codex: {
|
|
28
|
+
projectDir: ".codex",
|
|
29
|
+
globalDir: "~/.codex"
|
|
30
|
+
},
|
|
31
|
+
claude: {
|
|
32
|
+
projectDir: ".claude",
|
|
33
|
+
globalDir: "~/.claude"
|
|
34
|
+
},
|
|
35
|
+
cursor: {
|
|
36
|
+
projectDir: ".cursor",
|
|
37
|
+
globalDir: "~/.cursor"
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Convention: preset files under this directory map to the platform config directory.
|
|
42
|
+
* e.g., `config/agent.md` → `.opencode/agent.md` (project) or `agent.md` (global)
|
|
43
|
+
*/
|
|
44
|
+
const CONFIG_DIR_NAME = "config";
|
|
45
|
+
function isSupportedPlatform(value) {
|
|
46
|
+
return PLATFORM_ID_TUPLE.includes(value);
|
|
47
|
+
}
|
|
48
|
+
function normalizePlatformInput(value) {
|
|
49
|
+
const normalized = value.toLowerCase();
|
|
50
|
+
if (isSupportedPlatform(normalized)) return normalized;
|
|
51
|
+
throw new Error(`Unknown platform "${value}". Supported platforms: ${PLATFORM_IDS.join(", ")}.`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/types/schema.ts
|
|
56
|
+
const SEMVER_REGEX = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-(?:0|[1-9A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9A-Za-z-][0-9A-Za-z-]*))*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
57
|
+
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
|
+
const titleSchema = z.string().trim().min(1).max(120);
|
|
64
|
+
const descriptionSchema = z.string().trim().min(1).max(500);
|
|
65
|
+
const versionSchema = z.string().trim().regex(SEMVER_REGEX, "Version must follow semantic versioning");
|
|
66
|
+
const tagSchema = z.string().trim().min(1).max(48);
|
|
67
|
+
const tagsSchema = z.array(tagSchema).max(10);
|
|
68
|
+
const featureSchema = z.string().trim().min(1).max(160);
|
|
69
|
+
const featuresSchema = z.array(featureSchema).max(10);
|
|
70
|
+
const installMessageSchema = z.string().trim().max(4e3);
|
|
71
|
+
const licenseSchema = z.string().trim().max(80);
|
|
72
|
+
const slugSchema = z.string().trim().min(1).max(64).regex(/^[a-z0-9-]+$/, "Name must be lowercase kebab-case");
|
|
73
|
+
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
|
+
const presetConfigSchema = z.object({
|
|
81
|
+
$schema: z.string().optional(),
|
|
82
|
+
name: slugSchema,
|
|
83
|
+
title: titleSchema,
|
|
84
|
+
version: versionSchema,
|
|
85
|
+
description: descriptionSchema,
|
|
86
|
+
tags: tagsSchema.optional(),
|
|
87
|
+
author: authorSchema.optional(),
|
|
88
|
+
license: licenseSchema.optional(),
|
|
89
|
+
platforms: platformsObjectSchema
|
|
90
|
+
}).strict();
|
|
91
|
+
const bundledFileSchema = z.object({
|
|
92
|
+
path: z.string().min(1),
|
|
93
|
+
size: z.number().int().nonnegative(),
|
|
94
|
+
checksum: z.string().length(64),
|
|
95
|
+
contents: z.string()
|
|
96
|
+
});
|
|
97
|
+
const registryBundleSchema = z.object({
|
|
98
|
+
slug: z.string().trim().min(1),
|
|
99
|
+
platform: platformIdSchema,
|
|
100
|
+
title: titleSchema,
|
|
101
|
+
version: versionSchema,
|
|
102
|
+
description: descriptionSchema,
|
|
103
|
+
tags: tagsSchema,
|
|
104
|
+
author: authorSchema.optional(),
|
|
105
|
+
license: licenseSchema.optional(),
|
|
106
|
+
features: featuresSchema.optional(),
|
|
107
|
+
installMessage: installMessageSchema.optional(),
|
|
108
|
+
files: z.array(bundledFileSchema).min(1)
|
|
109
|
+
});
|
|
110
|
+
const registryEntrySchema = registryBundleSchema.omit({ files: true }).extend({
|
|
111
|
+
name: z.string().trim().min(1),
|
|
112
|
+
bundlePath: z.string().trim().min(1),
|
|
113
|
+
fileCount: z.number().int().nonnegative(),
|
|
114
|
+
totalSize: z.number().int().nonnegative()
|
|
115
|
+
});
|
|
116
|
+
const registryIndexSchema = z.record(z.string(), registryEntrySchema);
|
|
117
|
+
|
|
118
|
+
//#endregion
|
|
119
|
+
//#region src/utils/encoding.ts
|
|
5
120
|
function toPosixPath(pathValue) {
|
|
6
121
|
return pathValue.split("\\").join("/");
|
|
7
122
|
}
|
|
@@ -21,7 +136,7 @@ function toUint8Array(payload) {
|
|
|
21
136
|
}
|
|
22
137
|
|
|
23
138
|
//#endregion
|
|
24
|
-
//#region src/
|
|
139
|
+
//#region src/builder/utils.ts
|
|
25
140
|
function normalizeBundlePublicBase(value) {
|
|
26
141
|
const trimmed = value.trim();
|
|
27
142
|
if (!trimmed) throw new Error("--bundle-base must be a non-empty string");
|
|
@@ -43,8 +158,9 @@ function encodeItemName(slug, platform) {
|
|
|
43
158
|
return `${slug}.${platform}`;
|
|
44
159
|
}
|
|
45
160
|
function validatePresetConfig(config, slug) {
|
|
46
|
-
if (!config || typeof config !== "object") throw new Error(`Invalid preset config
|
|
161
|
+
if (!config || typeof config !== "object") throw new Error(`Invalid preset config for ${slug}`);
|
|
47
162
|
const preset = config;
|
|
163
|
+
if (!preset.name || typeof preset.name !== "string") throw new Error(`Preset ${slug} is missing a name`);
|
|
48
164
|
if (!preset.title || typeof preset.title !== "string") throw new Error(`Preset ${slug} is missing a title`);
|
|
49
165
|
if (!preset.version || typeof preset.version !== "string") throw new Error(`Preset ${slug} is missing a version`);
|
|
50
166
|
if (!preset.description || typeof preset.description !== "string") throw new Error(`Preset ${slug} is missing a description`);
|
|
@@ -59,20 +175,122 @@ function collectBundledFiles(files) {
|
|
|
59
175
|
path: normalizedPath,
|
|
60
176
|
size: payload.length,
|
|
61
177
|
checksum: "",
|
|
62
|
-
encoding: "utf-8",
|
|
63
178
|
contents
|
|
64
179
|
};
|
|
65
180
|
}).sort((a, b) => a.path.localeCompare(b.path));
|
|
66
181
|
}
|
|
67
182
|
|
|
68
183
|
//#endregion
|
|
69
|
-
//#region src/
|
|
70
|
-
const
|
|
71
|
-
|
|
184
|
+
//#region src/builder/registry.ts
|
|
185
|
+
const NAME_PATTERN = /^[a-z0-9-]+$/;
|
|
186
|
+
function buildRegistryData(options) {
|
|
187
|
+
const bundleBase = normalizeBundlePublicBase(options.bundleBase ?? "/r");
|
|
188
|
+
const entries = [];
|
|
189
|
+
const bundles = [];
|
|
190
|
+
for (const presetInput of options.presets) {
|
|
191
|
+
if (!NAME_PATTERN.test(presetInput.slug)) throw new Error(`Invalid slug "${presetInput.slug}". Slugs must be lowercase kebab-case.`);
|
|
192
|
+
const presetConfig = validatePresetConfig(presetInput.config, presetInput.slug);
|
|
193
|
+
if (presetInput.platforms.length === 0) throw new Error(`Preset ${presetInput.slug} has no platform inputs.`);
|
|
194
|
+
for (const platformInput of presetInput.platforms) {
|
|
195
|
+
const platform = platformInput.platform;
|
|
196
|
+
ensureKnownPlatform(platform, presetInput.slug);
|
|
197
|
+
const platformConfig = presetConfig.platforms?.[platform];
|
|
198
|
+
if (!platformConfig) throw new Error(`Preset ${presetInput.slug} has files for platform "${platform}" but no config entry.`);
|
|
199
|
+
if (platformInput.files.length === 0) throw new Error(`Preset ${presetInput.slug}/${platform} does not include any files.`);
|
|
200
|
+
const files = createBundledFilesFromInputs(platformInput.files);
|
|
201
|
+
const totalSize = files.reduce((sum, file) => sum + file.size, 0);
|
|
202
|
+
const installMessage = cleanInstallMessage(platformConfig.installMessage);
|
|
203
|
+
const features = platformConfig.features ?? [];
|
|
204
|
+
const readme = presetInput.readme?.trim() || void 0;
|
|
205
|
+
const entry = {
|
|
206
|
+
name: encodeItemName(presetInput.slug, platform),
|
|
207
|
+
slug: presetInput.slug,
|
|
208
|
+
platform,
|
|
209
|
+
title: presetConfig.title,
|
|
210
|
+
version: presetConfig.version,
|
|
211
|
+
description: presetConfig.description,
|
|
212
|
+
tags: presetConfig.tags ?? [],
|
|
213
|
+
author: presetConfig.author,
|
|
214
|
+
license: presetConfig.license,
|
|
215
|
+
features,
|
|
216
|
+
installMessage,
|
|
217
|
+
bundlePath: getBundlePublicPath(bundleBase, presetInput.slug, platform),
|
|
218
|
+
fileCount: files.length,
|
|
219
|
+
totalSize,
|
|
220
|
+
hasReadme: Boolean(readme)
|
|
221
|
+
};
|
|
222
|
+
const bundle = {
|
|
223
|
+
slug: presetInput.slug,
|
|
224
|
+
platform,
|
|
225
|
+
title: presetConfig.title,
|
|
226
|
+
version: presetConfig.version,
|
|
227
|
+
description: presetConfig.description,
|
|
228
|
+
tags: presetConfig.tags ?? [],
|
|
229
|
+
author: presetConfig.author,
|
|
230
|
+
license: presetConfig.license,
|
|
231
|
+
features,
|
|
232
|
+
installMessage,
|
|
233
|
+
readme,
|
|
234
|
+
files
|
|
235
|
+
};
|
|
236
|
+
entries.push(entry);
|
|
237
|
+
bundles.push(bundle);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
sortBySlugAndPlatform(entries);
|
|
241
|
+
sortBySlugAndPlatform(bundles);
|
|
242
|
+
const index = entries.reduce((acc, entry) => {
|
|
243
|
+
acc[entry.name] = entry;
|
|
244
|
+
return acc;
|
|
245
|
+
}, {});
|
|
246
|
+
return {
|
|
247
|
+
entries,
|
|
248
|
+
index,
|
|
249
|
+
bundles
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
function createBundledFilesFromInputs(files) {
|
|
253
|
+
return files.map((file) => {
|
|
254
|
+
const payload = normalizeFilePayload(file.contents);
|
|
255
|
+
const contents = encodeFilePayload(payload, file.path);
|
|
256
|
+
const checksum = createHash("sha256").update(payload).digest("hex");
|
|
257
|
+
return {
|
|
258
|
+
path: toPosixPath(file.path),
|
|
259
|
+
size: payload.length,
|
|
260
|
+
checksum,
|
|
261
|
+
contents
|
|
262
|
+
};
|
|
263
|
+
}).sort((a, b) => a.path.localeCompare(b.path));
|
|
264
|
+
}
|
|
265
|
+
function normalizeFilePayload(contents) {
|
|
266
|
+
if (typeof contents === "string") return Buffer.from(contents, "utf8");
|
|
267
|
+
if (contents instanceof ArrayBuffer) return Buffer.from(contents);
|
|
268
|
+
if (ArrayBuffer.isView(contents)) return Buffer.from(contents.buffer, contents.byteOffset, contents.byteLength);
|
|
269
|
+
return Buffer.from(contents);
|
|
270
|
+
}
|
|
271
|
+
function encodeFilePayload(buffer, filePath) {
|
|
272
|
+
const utf8 = buffer.toString("utf8");
|
|
273
|
+
if (!Buffer.from(utf8, "utf8").equals(buffer)) throw new Error(`Binary files are not supported: "${filePath}". Only UTF-8 text files are allowed.`);
|
|
274
|
+
return utf8;
|
|
275
|
+
}
|
|
276
|
+
function getBundlePublicPath(base, slug, platform) {
|
|
277
|
+
const prefix = base === "/" ? "" : base;
|
|
278
|
+
return `${prefix}/${slug}/${platform}.json`;
|
|
279
|
+
}
|
|
280
|
+
function ensureKnownPlatform(platform, slug) {
|
|
281
|
+
if (!isSupportedPlatform(platform)) throw new Error(`Unknown platform "${platform}" in ${slug}. Supported: ${PLATFORM_IDS.join(", ")}`);
|
|
282
|
+
}
|
|
283
|
+
function sortBySlugAndPlatform(items) {
|
|
284
|
+
items.sort((a, b) => {
|
|
285
|
+
if (a.slug === b.slug) return a.platform.localeCompare(b.platform);
|
|
286
|
+
return a.slug.localeCompare(b.slug);
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
//#endregion
|
|
291
|
+
//#region src/client/bundle.ts
|
|
72
292
|
function decodeBundledFile(file) {
|
|
73
|
-
|
|
74
|
-
if (file.encoding === "base64") return decodeBase64(file.contents);
|
|
75
|
-
throw new Error(`Unsupported encoding "${file.encoding}" for ${file.path}.`);
|
|
293
|
+
return encodeUtf8(file.contents);
|
|
76
294
|
}
|
|
77
295
|
async function verifyBundledFileChecksum(file, payload) {
|
|
78
296
|
const bytes = toUint8Array(payload);
|
|
@@ -92,39 +310,6 @@ function isLikelyText(payload) {
|
|
|
92
310
|
function toUtf8String(payload) {
|
|
93
311
|
return decodeUtf8(payload);
|
|
94
312
|
}
|
|
95
|
-
function decodeBase64(input) {
|
|
96
|
-
const sanitized = input.replace(/[^A-Za-z0-9+/=]/g, "");
|
|
97
|
-
if (sanitized.length % 4 !== 0) throw new Error("Invalid base64 payload length.");
|
|
98
|
-
let outputLength = sanitized.length / 4 * 3;
|
|
99
|
-
if (sanitized.endsWith("==")) outputLength -= 2;
|
|
100
|
-
else if (sanitized.endsWith("=")) outputLength -= 1;
|
|
101
|
-
const bytes = new Uint8Array(outputLength);
|
|
102
|
-
let byteIndex = 0;
|
|
103
|
-
for (let i = 0; i < sanitized.length; i += 4) {
|
|
104
|
-
const chunk = sanitized.slice(i, i + 4);
|
|
105
|
-
const enc1 = decodeBase64Char(chunk[0]);
|
|
106
|
-
const enc2 = decodeBase64Char(chunk[1]);
|
|
107
|
-
const enc3 = chunk[2] === "=" ? 0 : decodeBase64Char(chunk[2]);
|
|
108
|
-
const enc4 = chunk[3] === "=" ? 0 : decodeBase64Char(chunk[3]);
|
|
109
|
-
const combined = enc1 * 262144 + enc2 * 4096 + enc3 * 64 + enc4;
|
|
110
|
-
bytes[byteIndex] = Math.floor(combined / 65536) % 256;
|
|
111
|
-
byteIndex += 1;
|
|
112
|
-
if (chunk[2] !== "=") {
|
|
113
|
-
bytes[byteIndex] = Math.floor(combined / 256) % 256;
|
|
114
|
-
byteIndex += 1;
|
|
115
|
-
}
|
|
116
|
-
if (chunk[3] !== "=") {
|
|
117
|
-
bytes[byteIndex] = combined % 256;
|
|
118
|
-
byteIndex += 1;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
return bytes;
|
|
122
|
-
}
|
|
123
|
-
function decodeBase64Char(char) {
|
|
124
|
-
const value = BASE64_LOOKUP[char];
|
|
125
|
-
if (value === void 0) throw new Error(`Invalid base64 character "${char}".`);
|
|
126
|
-
return value;
|
|
127
|
-
}
|
|
128
313
|
async function sha256Hex(payload) {
|
|
129
314
|
const crypto = globalThis.crypto;
|
|
130
315
|
if (!crypto?.subtle) throw new Error("SHA-256 hashing requires Web Crypto API support.");
|
|
@@ -133,60 +318,7 @@ async function sha256Hex(payload) {
|
|
|
133
318
|
}
|
|
134
319
|
|
|
135
320
|
//#endregion
|
|
136
|
-
//#region src/
|
|
137
|
-
const DEFAULT_CONTEXT = 2;
|
|
138
|
-
const DEFAULT_MAX_LINES = 40;
|
|
139
|
-
function createDiffPreview(path, currentText, incomingText, options = {}) {
|
|
140
|
-
const patch = createTwoFilesPatch(`${path} (current)`, `${path} (incoming)`, currentText, incomingText, void 0, void 0, { context: options.context ?? DEFAULT_CONTEXT });
|
|
141
|
-
const lines = patch.trim().split("\n");
|
|
142
|
-
const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
|
|
143
|
-
const limited = lines.slice(0, maxLines);
|
|
144
|
-
if (lines.length > maxLines) limited.push("...");
|
|
145
|
-
return limited.join("\n");
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
//#endregion
|
|
149
|
-
//#region src/paths.ts
|
|
150
|
-
function normalizeBundlePath(value) {
|
|
151
|
-
return value.replace(/\\/g, "/").replace(/^\.\/+/, "").replace(/^\/+/, "");
|
|
152
|
-
}
|
|
153
|
-
function normalizePathFragment(value) {
|
|
154
|
-
if (!value) return;
|
|
155
|
-
const normalized = value.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
156
|
-
return normalized.replace(/\/+$/, "");
|
|
157
|
-
}
|
|
158
|
-
function maybeStripPrefix(pathInput, prefix) {
|
|
159
|
-
if (!prefix) return pathInput;
|
|
160
|
-
if (pathInput === prefix) return "";
|
|
161
|
-
if (pathInput.startsWith(`${prefix}/`)) return pathInput.slice(prefix.length + 1);
|
|
162
|
-
return pathInput;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
//#endregion
|
|
166
|
-
//#region src/platform.ts
|
|
167
|
-
const PLATFORM_IDS = [
|
|
168
|
-
"opencode",
|
|
169
|
-
"codex",
|
|
170
|
-
"claude",
|
|
171
|
-
"cursor"
|
|
172
|
-
];
|
|
173
|
-
function isSupportedPlatform(value) {
|
|
174
|
-
return PLATFORM_IDS.includes(value);
|
|
175
|
-
}
|
|
176
|
-
function normalizePlatformInput(value) {
|
|
177
|
-
const normalized = value.toLowerCase();
|
|
178
|
-
if (isSupportedPlatform(normalized)) return normalized;
|
|
179
|
-
throw new Error(`Unknown platform "${value}". Supported platforms: ${PLATFORM_IDS.join(", ")}.`);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
//#endregion
|
|
183
|
-
//#region src/preset.ts
|
|
184
|
-
function definePreset(config) {
|
|
185
|
-
return config;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
//#endregion
|
|
189
|
-
//#region src/registry.ts
|
|
321
|
+
//#region src/client/registry.ts
|
|
190
322
|
async function fetchRegistryIndex(baseUrl) {
|
|
191
323
|
const indexUrl = new URL("registry.index.json", baseUrl);
|
|
192
324
|
const response = await fetch(indexUrl);
|
|
@@ -222,7 +354,7 @@ function resolveRegistryEntry(index, input, explicitPlatform) {
|
|
|
222
354
|
if (!platform) {
|
|
223
355
|
const parts = normalizedInput.split(".");
|
|
224
356
|
const maybePlatform = parts.at(-1);
|
|
225
|
-
if (maybePlatform &&
|
|
357
|
+
if (maybePlatform && isSupportedPlatform(maybePlatform)) {
|
|
226
358
|
platform = maybePlatform;
|
|
227
359
|
slugHint = parts.slice(0, -1).join(".");
|
|
228
360
|
}
|
|
@@ -242,65 +374,34 @@ function resolveRegistryEntry(index, input, explicitPlatform) {
|
|
|
242
374
|
}
|
|
243
375
|
|
|
244
376
|
//#endregion
|
|
245
|
-
//#region src/
|
|
246
|
-
const
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
"
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
license: licenseSchema.optional(),
|
|
274
|
-
features: featuresSchema.optional(),
|
|
275
|
-
installMessage: installMessageSchema.optional()
|
|
276
|
-
}).strict();
|
|
277
|
-
const bundledFileSchema = z.object({
|
|
278
|
-
path: z.string().min(1),
|
|
279
|
-
size: z.number().int().nonnegative(),
|
|
280
|
-
checksum: z.string().length(64),
|
|
281
|
-
encoding: z.union([z.literal("utf-8"), z.literal("base64")]),
|
|
282
|
-
contents: z.string()
|
|
283
|
-
});
|
|
284
|
-
const registryBundleSchema = z.object({
|
|
285
|
-
slug: z.string().trim().min(1),
|
|
286
|
-
platform: platformIdSchema,
|
|
287
|
-
title: titleSchema,
|
|
288
|
-
version: versionSchema,
|
|
289
|
-
description: descriptionSchema,
|
|
290
|
-
tags: tagsSchema,
|
|
291
|
-
author: authorSchema.optional(),
|
|
292
|
-
license: licenseSchema.optional(),
|
|
293
|
-
features: featuresSchema.optional(),
|
|
294
|
-
installMessage: installMessageSchema.optional(),
|
|
295
|
-
files: z.array(bundledFileSchema).min(1)
|
|
296
|
-
});
|
|
297
|
-
const registryEntrySchema = registryBundleSchema.omit({ files: true }).extend({
|
|
298
|
-
name: z.string().trim().min(1),
|
|
299
|
-
bundlePath: z.string().trim().min(1),
|
|
300
|
-
fileCount: z.number().int().nonnegative(),
|
|
301
|
-
totalBytes: z.number().int().nonnegative()
|
|
302
|
-
});
|
|
303
|
-
const registryIndexSchema = z.record(z.string(), registryEntrySchema);
|
|
377
|
+
//#region src/utils/diff.ts
|
|
378
|
+
const DEFAULT_CONTEXT = 2;
|
|
379
|
+
const DEFAULT_MAX_LINES = 40;
|
|
380
|
+
function createDiffPreview(path, currentText, incomingText, options = {}) {
|
|
381
|
+
const patch = createTwoFilesPatch(`${path} (current)`, `${path} (incoming)`, currentText, incomingText, void 0, void 0, { context: options.context ?? DEFAULT_CONTEXT });
|
|
382
|
+
const lines = patch.trim().split("\n");
|
|
383
|
+
const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
|
|
384
|
+
const limited = lines.slice(0, maxLines);
|
|
385
|
+
if (lines.length > maxLines) limited.push("...");
|
|
386
|
+
return limited.join("\n");
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
//#endregion
|
|
390
|
+
//#region src/utils/paths.ts
|
|
391
|
+
function normalizeBundlePath(value) {
|
|
392
|
+
return value.replace(/\\/g, "/").replace(/^\.\/+/, "").replace(/^\/+/, "");
|
|
393
|
+
}
|
|
394
|
+
function normalizePathFragment(value) {
|
|
395
|
+
if (!value) return;
|
|
396
|
+
const normalized = value.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
397
|
+
return normalized.replace(/\/+$/, "");
|
|
398
|
+
}
|
|
399
|
+
function maybeStripPrefix(pathInput, prefix) {
|
|
400
|
+
if (!prefix) return pathInput;
|
|
401
|
+
if (pathInput === prefix) return "";
|
|
402
|
+
if (pathInput.startsWith(`${prefix}/`)) return pathInput.slice(prefix.length + 1);
|
|
403
|
+
return pathInput;
|
|
404
|
+
}
|
|
304
405
|
|
|
305
406
|
//#endregion
|
|
306
|
-
export { PLATFORM_IDS, authorSchema, bundledFileSchema, cleanInstallMessage, collectBundledFiles, createDiffPreview, decodeBundledFile, decodeUtf8,
|
|
407
|
+
export { CONFIG_DIR_NAME, PLATFORMS, PLATFORM_IDS, authorSchema, buildRegistryData, bundledFileSchema, cleanInstallMessage, collectBundledFiles, createDiffPreview, decodeBundledFile, decodeUtf8, encodeItemName, encodeUtf8, fetchRegistryBundle, fetchRegistryIndex, isAbsoluteUrl, isLikelyText, isSupportedPlatform, maybeStripPrefix, normalizeBundlePath, normalizeBundlePublicBase, normalizePathFragment, normalizePlatformInput, platformIdSchema, platformPresetConfigSchema, presetConfigSchema, registryBundleSchema, registryEntrySchema, registryIndexSchema, resolveRegistryEntry, toPosixPath, toUint8Array, toUtf8String, validatePresetConfig, verifyBundledFileChecksum };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentrules/core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"author": "Brian Cheung <bcheung.dev@gmail.com> (https://github.com/bcheung)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://docs.agentrules.directory",
|
|
@@ -51,4 +51,4 @@
|
|
|
51
51
|
"tsdown": "^0.9.0",
|
|
52
52
|
"typescript": "5.7.2"
|
|
53
53
|
}
|
|
54
|
-
}
|
|
54
|
+
}
|