@character-foundry/character-foundry 0.1.3 → 0.1.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.
Files changed (100) hide show
  1. package/README.md +70 -0
  2. package/dist/app-framework.cjs +1742 -0
  3. package/dist/app-framework.cjs.map +1 -0
  4. package/dist/app-framework.d.cts +881 -0
  5. package/dist/app-framework.d.ts +881 -2
  6. package/dist/app-framework.js +1718 -1
  7. package/dist/app-framework.js.map +1 -1
  8. package/dist/charx.cjs +917 -0
  9. package/dist/charx.cjs.map +1 -0
  10. package/dist/charx.d.cts +640 -0
  11. package/dist/charx.d.ts +640 -2
  12. package/dist/charx.js +893 -1
  13. package/dist/charx.js.map +1 -1
  14. package/dist/core.cjs +668 -0
  15. package/dist/core.cjs.map +1 -0
  16. package/dist/core.d.cts +363 -0
  17. package/dist/core.d.ts +363 -2
  18. package/dist/core.js +644 -1
  19. package/dist/core.js.map +1 -1
  20. package/dist/exporter.cjs +7539 -0
  21. package/dist/exporter.cjs.map +1 -0
  22. package/dist/exporter.d.cts +681 -0
  23. package/dist/exporter.d.ts +681 -2
  24. package/dist/exporter.js +7522 -1
  25. package/dist/exporter.js.map +1 -1
  26. package/dist/federation.cjs +3915 -0
  27. package/dist/federation.cjs.map +1 -0
  28. package/dist/federation.d.cts +2951 -0
  29. package/dist/federation.d.ts +2951 -2
  30. package/dist/federation.js +3891 -1
  31. package/dist/federation.js.map +1 -1
  32. package/dist/index.cjs +9109 -0
  33. package/dist/index.cjs.map +1 -0
  34. package/dist/index.d.cts +1119 -0
  35. package/dist/index.d.ts +1113 -20
  36. package/dist/index.js +9092 -26
  37. package/dist/index.js.map +1 -1
  38. package/dist/loader.cjs +8923 -0
  39. package/dist/loader.cjs.map +1 -0
  40. package/dist/loader.d.cts +1037 -0
  41. package/dist/loader.d.ts +1037 -2
  42. package/dist/loader.js +8906 -1
  43. package/dist/loader.js.map +1 -1
  44. package/dist/lorebook.cjs +865 -0
  45. package/dist/lorebook.cjs.map +1 -0
  46. package/dist/lorebook.d.cts +1008 -0
  47. package/dist/lorebook.d.ts +1008 -2
  48. package/dist/lorebook.js +841 -1
  49. package/dist/lorebook.js.map +1 -1
  50. package/dist/media.cjs +6660 -0
  51. package/dist/media.cjs.map +1 -0
  52. package/dist/media.d.cts +87 -0
  53. package/dist/media.d.ts +87 -2
  54. package/dist/media.js +6643 -1
  55. package/dist/media.js.map +1 -1
  56. package/dist/normalizer.cjs +502 -0
  57. package/dist/normalizer.cjs.map +1 -0
  58. package/dist/normalizer.d.cts +1216 -0
  59. package/dist/normalizer.d.ts +1216 -2
  60. package/dist/normalizer.js +478 -1
  61. package/dist/normalizer.js.map +1 -1
  62. package/dist/png.cjs +778 -0
  63. package/dist/png.cjs.map +1 -0
  64. package/dist/png.d.cts +786 -0
  65. package/dist/png.d.ts +786 -2
  66. package/dist/png.js +754 -1
  67. package/dist/png.js.map +1 -1
  68. package/dist/schemas.cjs +799 -0
  69. package/dist/schemas.cjs.map +1 -0
  70. package/dist/schemas.d.cts +2178 -0
  71. package/dist/schemas.d.ts +2178 -2
  72. package/dist/schemas.js +775 -1
  73. package/dist/schemas.js.map +1 -1
  74. package/dist/tokenizers.cjs +153 -0
  75. package/dist/tokenizers.cjs.map +1 -0
  76. package/dist/tokenizers.d.cts +155 -0
  77. package/dist/tokenizers.d.ts +155 -2
  78. package/dist/tokenizers.js +129 -1
  79. package/dist/tokenizers.js.map +1 -1
  80. package/dist/voxta.cjs +7995 -0
  81. package/dist/voxta.cjs.map +1 -0
  82. package/dist/voxta.d.cts +1349 -0
  83. package/dist/voxta.d.ts +1349 -2
  84. package/dist/voxta.js +7978 -1
  85. package/dist/voxta.js.map +1 -1
  86. package/package.json +177 -45
  87. package/dist/app-framework.d.ts.map +0 -1
  88. package/dist/charx.d.ts.map +0 -1
  89. package/dist/core.d.ts.map +0 -1
  90. package/dist/exporter.d.ts.map +0 -1
  91. package/dist/federation.d.ts.map +0 -1
  92. package/dist/index.d.ts.map +0 -1
  93. package/dist/loader.d.ts.map +0 -1
  94. package/dist/lorebook.d.ts.map +0 -1
  95. package/dist/media.d.ts.map +0 -1
  96. package/dist/normalizer.d.ts.map +0 -1
  97. package/dist/png.d.ts.map +0 -1
  98. package/dist/schemas.d.ts.map +0 -1
  99. package/dist/tokenizers.d.ts.map +0 -1
  100. package/dist/voxta.d.ts.map +0 -1
package/dist/charx.cjs ADDED
@@ -0,0 +1,917 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/charx.ts
21
+ var charx_exports = {};
22
+ __export(charx_exports, {
23
+ isCharX: () => isCharX,
24
+ isJpegCharX: () => isJpegCharX,
25
+ readCardJsonOnly: () => readCardJsonOnly,
26
+ readCharX: () => readCharX,
27
+ readCharXAsync: () => readCharXAsync,
28
+ writeCharX: () => writeCharX,
29
+ writeCharXAsync: () => writeCharXAsync
30
+ });
31
+ module.exports = __toCommonJS(charx_exports);
32
+
33
+ // ../core/dist/index.js
34
+ function fromString(str) {
35
+ return new TextEncoder().encode(str);
36
+ }
37
+ function toString(data) {
38
+ return new TextDecoder().decode(data);
39
+ }
40
+ var isNode = typeof process !== "undefined" && process.versions != null && process.versions.node != null;
41
+ function decode(base64) {
42
+ if (isNode) {
43
+ return new Uint8Array(Buffer.from(base64, "base64"));
44
+ }
45
+ const binary = atob(base64);
46
+ const result = new Uint8Array(binary.length);
47
+ for (let i = 0; i < binary.length; i++) {
48
+ result[i] = binary.charCodeAt(i);
49
+ }
50
+ return result;
51
+ }
52
+ var ENCODE_CHUNK_SIZE = 64 * 1024;
53
+ var FOUNDRY_ERROR_MARKER = /* @__PURE__ */ Symbol.for("@character-foundry/core:FoundryError");
54
+ var FoundryError = class extends Error {
55
+ constructor(message, code) {
56
+ super(message);
57
+ this.code = code;
58
+ this.name = "FoundryError";
59
+ if (Error.captureStackTrace) {
60
+ Error.captureStackTrace(this, this.constructor);
61
+ }
62
+ }
63
+ /** @internal Marker for cross-module identification */
64
+ [FOUNDRY_ERROR_MARKER] = true;
65
+ };
66
+ var ParseError = class extends FoundryError {
67
+ constructor(message, format) {
68
+ super(message, "PARSE_ERROR");
69
+ this.format = format;
70
+ this.name = "ParseError";
71
+ }
72
+ };
73
+ var SizeLimitError = class extends FoundryError {
74
+ constructor(actualSize, maxSize, context) {
75
+ const actualMB = (actualSize / 1024 / 1024).toFixed(2);
76
+ const maxMB = (maxSize / 1024 / 1024).toFixed(2);
77
+ const msg = context ? `${context}: Size ${actualMB}MB exceeds limit ${maxMB}MB` : `Size ${actualMB}MB exceeds limit ${maxMB}MB`;
78
+ super(msg, "SIZE_LIMIT_EXCEEDED");
79
+ this.actualSize = actualSize;
80
+ this.maxSize = maxSize;
81
+ this.name = "SizeLimitError";
82
+ }
83
+ };
84
+ function normalizeURI(uri) {
85
+ const trimmed = uri.trim();
86
+ if (trimmed.startsWith("embedded://")) {
87
+ return "embeded://" + trimmed.substring("embedded://".length);
88
+ }
89
+ if (trimmed.startsWith("__asset:")) {
90
+ const id = trimmed.substring("__asset:".length);
91
+ return `pngchunk:${id}`;
92
+ }
93
+ if (trimmed.startsWith("asset:")) {
94
+ const id = trimmed.substring("asset:".length);
95
+ return `pngchunk:${id}`;
96
+ }
97
+ if (trimmed.startsWith("chara-ext-asset_:")) {
98
+ const id = trimmed.substring("chara-ext-asset_:".length);
99
+ return `pngchunk:${id}`;
100
+ }
101
+ if (trimmed.startsWith("chara-ext-asset_")) {
102
+ const id = trimmed.substring("chara-ext-asset_".length);
103
+ return `pngchunk:${id}`;
104
+ }
105
+ return trimmed;
106
+ }
107
+ function parseURI(uri) {
108
+ const trimmed = uri.trim();
109
+ const normalized = normalizeURI(trimmed);
110
+ if (trimmed.startsWith("__asset:") || trimmed.startsWith("asset:") || trimmed.startsWith("chara-ext-asset_") || trimmed.startsWith("pngchunk:")) {
111
+ let assetId;
112
+ if (trimmed.startsWith("__asset:")) {
113
+ assetId = trimmed.substring("__asset:".length);
114
+ } else if (trimmed.startsWith("asset:")) {
115
+ assetId = trimmed.substring("asset:".length);
116
+ } else if (trimmed.startsWith("chara-ext-asset_:")) {
117
+ assetId = trimmed.substring("chara-ext-asset_:".length);
118
+ } else if (trimmed.startsWith("pngchunk:")) {
119
+ assetId = trimmed.substring("pngchunk:".length);
120
+ } else {
121
+ assetId = trimmed.substring("chara-ext-asset_".length);
122
+ }
123
+ const candidates = [
124
+ assetId,
125
+ // "0" or "filename.png"
126
+ trimmed,
127
+ // Original URI
128
+ `asset:${assetId}`,
129
+ // "asset:0"
130
+ `__asset:${assetId}`,
131
+ // "__asset:0"
132
+ `__asset_${assetId}`,
133
+ // "__asset_0"
134
+ `chara-ext-asset_${assetId}`,
135
+ // "chara-ext-asset_0"
136
+ `chara-ext-asset_:${assetId}`,
137
+ // "chara-ext-asset_:0"
138
+ `pngchunk:${assetId}`
139
+ // "pngchunk:0"
140
+ ];
141
+ return {
142
+ scheme: "pngchunk",
143
+ originalUri: uri,
144
+ normalizedUri: normalized,
145
+ chunkKey: assetId,
146
+ chunkCandidates: candidates
147
+ };
148
+ }
149
+ if (trimmed === "ccdefault:" || trimmed.startsWith("ccdefault:")) {
150
+ return {
151
+ scheme: "ccdefault",
152
+ originalUri: uri,
153
+ normalizedUri: normalized
154
+ };
155
+ }
156
+ if (trimmed.startsWith("embeded://") || trimmed.startsWith("embedded://")) {
157
+ const path = trimmed.startsWith("embeded://") ? trimmed.substring("embeded://".length) : trimmed.substring("embedded://".length);
158
+ return {
159
+ scheme: "embeded",
160
+ originalUri: uri,
161
+ normalizedUri: normalized,
162
+ path
163
+ };
164
+ }
165
+ if (trimmed.startsWith("https://")) {
166
+ return {
167
+ scheme: "https",
168
+ originalUri: uri,
169
+ normalizedUri: normalized,
170
+ url: trimmed
171
+ };
172
+ }
173
+ if (trimmed.startsWith("http://")) {
174
+ return {
175
+ scheme: "http",
176
+ originalUri: uri,
177
+ normalizedUri: normalized,
178
+ url: trimmed
179
+ };
180
+ }
181
+ if (trimmed.startsWith("data:")) {
182
+ const parsed = parseDataURI(trimmed);
183
+ return {
184
+ scheme: "data",
185
+ originalUri: uri,
186
+ normalizedUri: normalized,
187
+ ...parsed
188
+ };
189
+ }
190
+ if (trimmed.startsWith("file://")) {
191
+ const path = trimmed.substring("file://".length);
192
+ return {
193
+ scheme: "file",
194
+ originalUri: uri,
195
+ normalizedUri: normalized,
196
+ path
197
+ };
198
+ }
199
+ if (/^[a-zA-Z0-9_-]+$/.test(trimmed)) {
200
+ return {
201
+ scheme: "internal",
202
+ originalUri: uri,
203
+ normalizedUri: normalized,
204
+ path: trimmed
205
+ };
206
+ }
207
+ return {
208
+ scheme: "unknown",
209
+ originalUri: uri,
210
+ normalizedUri: normalized
211
+ };
212
+ }
213
+ function parseDataURI(uri) {
214
+ const match = uri.match(/^data:([^;,]+)?(;base64)?,(.*)$/);
215
+ if (!match) {
216
+ return {};
217
+ }
218
+ return {
219
+ mimeType: match[1] || "text/plain",
220
+ encoding: match[2] ? "base64" : void 0,
221
+ data: match[3]
222
+ };
223
+ }
224
+ function getMimeTypeFromExt(ext) {
225
+ const extToMime = {
226
+ // Images
227
+ "png": "image/png",
228
+ "jpg": "image/jpeg",
229
+ "jpeg": "image/jpeg",
230
+ "webp": "image/webp",
231
+ "gif": "image/gif",
232
+ "avif": "image/avif",
233
+ "svg": "image/svg+xml",
234
+ "bmp": "image/bmp",
235
+ "ico": "image/x-icon",
236
+ // Audio
237
+ "mp3": "audio/mpeg",
238
+ "wav": "audio/wav",
239
+ "ogg": "audio/ogg",
240
+ "flac": "audio/flac",
241
+ "m4a": "audio/mp4",
242
+ "aac": "audio/aac",
243
+ // Video
244
+ "mp4": "video/mp4",
245
+ "webm": "video/webm",
246
+ "avi": "video/x-msvideo",
247
+ "mov": "video/quicktime",
248
+ "mkv": "video/x-matroska",
249
+ // Text/Data
250
+ "json": "application/json",
251
+ "txt": "text/plain",
252
+ "html": "text/html",
253
+ "css": "text/css",
254
+ "js": "application/javascript"
255
+ };
256
+ return extToMime[ext.toLowerCase()] || "application/octet-stream";
257
+ }
258
+
259
+ // ../core/dist/zip.js
260
+ var import_fflate = require("fflate");
261
+ function indexOf(data, search, fromIndex = 0) {
262
+ outer: for (let i = fromIndex; i <= data.length - search.length; i++) {
263
+ for (let j = 0; j < search.length; j++) {
264
+ if (data[i + j] !== search[j]) continue outer;
265
+ }
266
+ return i;
267
+ }
268
+ return -1;
269
+ }
270
+ function concat(...arrays) {
271
+ const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);
272
+ const result = new Uint8Array(totalLength);
273
+ let offset = 0;
274
+ for (const arr of arrays) {
275
+ result.set(arr, offset);
276
+ offset += arr.length;
277
+ }
278
+ return result;
279
+ }
280
+ var ZIP_SIGNATURE = new Uint8Array([80, 75, 3, 4]);
281
+ var JPEG_SIGNATURE = new Uint8Array([255, 216, 255]);
282
+ var DEFAULT_ZIP_LIMITS = {
283
+ maxFileSize: 50 * 1024 * 1024,
284
+ // 50MB per file (Risu standard)
285
+ maxTotalSize: 500 * 1024 * 1024,
286
+ // 500MB total (CharX can have many expression assets)
287
+ maxFiles: 1e4,
288
+ // CharX cards can have 2k+ expression assets
289
+ unsafePathHandling: "skip"
290
+ // Backwards compatible default
291
+ };
292
+ function isJPEG(data) {
293
+ return data.length >= 3 && data[0] === 255 && data[1] === 216 && data[2] === 255;
294
+ }
295
+ function isJpegCharX(data) {
296
+ if (!isJPEG(data)) return false;
297
+ return indexOf(data, ZIP_SIGNATURE) > 0;
298
+ }
299
+ function findZipStart(data) {
300
+ const index = indexOf(data, ZIP_SIGNATURE);
301
+ if (index > 0) {
302
+ return data.subarray(index);
303
+ }
304
+ return data;
305
+ }
306
+ function getZipOffset(data) {
307
+ return indexOf(data, ZIP_SIGNATURE);
308
+ }
309
+ function isPathSafe(path) {
310
+ if (path.startsWith("/") || /^[a-zA-Z]:/.test(path)) {
311
+ return false;
312
+ }
313
+ if (path.includes("..")) {
314
+ return false;
315
+ }
316
+ if (path.includes("\\")) {
317
+ return false;
318
+ }
319
+ return true;
320
+ }
321
+ var ZipPreflightError = class extends Error {
322
+ constructor(message, totalSize, maxSize, oversizedEntry, entrySize, maxEntrySize) {
323
+ super(message);
324
+ this.totalSize = totalSize;
325
+ this.maxSize = maxSize;
326
+ this.oversizedEntry = oversizedEntry;
327
+ this.entrySize = entrySize;
328
+ this.maxEntrySize = maxEntrySize;
329
+ this.name = "ZipPreflightError";
330
+ }
331
+ };
332
+ function streamingUnzipSync(data, limits = DEFAULT_ZIP_LIMITS) {
333
+ const zipData = findZipStart(data);
334
+ const result = {};
335
+ let totalBytes = 0;
336
+ let fileCount = 0;
337
+ let error = null;
338
+ const unsafePathHandling = limits.unsafePathHandling ?? "skip";
339
+ const fileChunks = /* @__PURE__ */ new Map();
340
+ const unzipper = new import_fflate.Unzip((file) => {
341
+ if (error) return;
342
+ if (file.name.endsWith("/")) {
343
+ file.start();
344
+ return;
345
+ }
346
+ if (!isPathSafe(file.name)) {
347
+ const reason = file.name.includes("..") ? "path traversal (..)" : file.name.startsWith("/") || /^[a-zA-Z]:/.test(file.name) ? "absolute path" : "backslash in path";
348
+ if (unsafePathHandling === "reject") {
349
+ error = new ZipPreflightError(
350
+ `Unsafe path detected: "${file.name}" - ${reason}. This may be a path traversal attack.`
351
+ );
352
+ file.terminate();
353
+ return;
354
+ }
355
+ if (unsafePathHandling === "warn" && limits.onUnsafePath) {
356
+ limits.onUnsafePath(file.name, reason);
357
+ }
358
+ file.ondata = () => {
359
+ };
360
+ file.start();
361
+ return;
362
+ }
363
+ fileCount++;
364
+ if (fileCount > limits.maxFiles) {
365
+ error = new ZipPreflightError(
366
+ `File count ${fileCount} exceeds limit ${limits.maxFiles}`
367
+ );
368
+ file.terminate();
369
+ return;
370
+ }
371
+ const chunks = [];
372
+ fileChunks.set(file.name, chunks);
373
+ let fileBytes = 0;
374
+ file.ondata = (err, chunk, final) => {
375
+ if (error) return;
376
+ if (err) {
377
+ error = err;
378
+ return;
379
+ }
380
+ if (chunk && chunk.length > 0) {
381
+ fileBytes += chunk.length;
382
+ totalBytes += chunk.length;
383
+ if (fileBytes > limits.maxFileSize) {
384
+ error = new ZipPreflightError(
385
+ `File "${file.name}" actual size ${fileBytes} exceeds limit ${limits.maxFileSize}`,
386
+ void 0,
387
+ void 0,
388
+ file.name,
389
+ fileBytes,
390
+ limits.maxFileSize
391
+ );
392
+ file.terminate();
393
+ return;
394
+ }
395
+ if (totalBytes > limits.maxTotalSize) {
396
+ error = new ZipPreflightError(
397
+ `Total actual size ${totalBytes} exceeds limit ${limits.maxTotalSize}`,
398
+ totalBytes,
399
+ limits.maxTotalSize
400
+ );
401
+ file.terminate();
402
+ return;
403
+ }
404
+ chunks.push(chunk);
405
+ }
406
+ if (final && !error) {
407
+ result[file.name] = concat(...chunks);
408
+ }
409
+ };
410
+ file.start();
411
+ });
412
+ unzipper.register(import_fflate.UnzipInflate);
413
+ unzipper.register(import_fflate.UnzipPassThrough);
414
+ unzipper.push(zipData, true);
415
+ if (error) {
416
+ throw error;
417
+ }
418
+ return result;
419
+ }
420
+
421
+ // ../schemas/dist/index.js
422
+ var import_zod = require("zod");
423
+ var import_zod2 = require("zod");
424
+ var import_zod3 = require("zod");
425
+ var import_zod4 = require("zod");
426
+ var ISO8601Schema = import_zod.z.string().datetime();
427
+ var UUIDSchema = import_zod.z.string().uuid();
428
+ var SpecSchema = import_zod.z.enum(["v2", "v3"]);
429
+ var SourceFormatSchema = import_zod.z.enum([
430
+ "png_v2",
431
+ // PNG with 'chara' chunk (v2)
432
+ "png_v3",
433
+ // PNG with 'ccv3' chunk (v3)
434
+ "json_v2",
435
+ // Raw JSON v2
436
+ "json_v3",
437
+ // Raw JSON v3
438
+ "charx",
439
+ // ZIP with card.json (v3 spec)
440
+ "charx_risu",
441
+ // ZIP with card.json + module.risum
442
+ "charx_jpeg",
443
+ // JPEG with appended ZIP (read-only)
444
+ "voxta"
445
+ // VoxPkg format
446
+ ]);
447
+ var OriginalShapeSchema = import_zod.z.enum(["wrapped", "unwrapped", "legacy"]);
448
+ var AssetTypeSchema = import_zod.z.enum([
449
+ "icon",
450
+ "background",
451
+ "emotion",
452
+ "user_icon",
453
+ "sound",
454
+ "video",
455
+ "custom",
456
+ "x-risu-asset"
457
+ ]);
458
+ var AssetDescriptorSchema = import_zod.z.object({
459
+ type: AssetTypeSchema,
460
+ uri: import_zod.z.string(),
461
+ name: import_zod.z.string(),
462
+ ext: import_zod.z.string()
463
+ });
464
+ var ExtractedAssetSchema = import_zod.z.object({
465
+ descriptor: AssetDescriptorSchema,
466
+ data: import_zod.z.instanceof(Uint8Array),
467
+ mimeType: import_zod.z.string()
468
+ });
469
+ var CCv2LorebookEntrySchema = import_zod2.z.object({
470
+ keys: import_zod2.z.array(import_zod2.z.string()),
471
+ content: import_zod2.z.string(),
472
+ enabled: import_zod2.z.boolean(),
473
+ insertion_order: import_zod2.z.number().int(),
474
+ // Optional fields
475
+ extensions: import_zod2.z.record(import_zod2.z.unknown()).optional(),
476
+ case_sensitive: import_zod2.z.boolean().optional(),
477
+ name: import_zod2.z.string().optional(),
478
+ priority: import_zod2.z.number().int().optional(),
479
+ id: import_zod2.z.number().int().optional(),
480
+ comment: import_zod2.z.string().optional(),
481
+ selective: import_zod2.z.boolean().optional(),
482
+ secondary_keys: import_zod2.z.array(import_zod2.z.string()).optional(),
483
+ constant: import_zod2.z.boolean().optional(),
484
+ position: import_zod2.z.enum(["before_char", "after_char"]).optional()
485
+ });
486
+ var CCv2CharacterBookSchema = import_zod2.z.object({
487
+ name: import_zod2.z.string().optional(),
488
+ description: import_zod2.z.string().optional(),
489
+ scan_depth: import_zod2.z.number().int().nonnegative().optional(),
490
+ token_budget: import_zod2.z.number().int().nonnegative().optional(),
491
+ recursive_scanning: import_zod2.z.boolean().optional(),
492
+ extensions: import_zod2.z.record(import_zod2.z.unknown()).optional(),
493
+ entries: import_zod2.z.array(CCv2LorebookEntrySchema)
494
+ });
495
+ var CCv2DataSchema = import_zod2.z.object({
496
+ // Core fields - use .default('') to handle missing fields in malformed cards
497
+ name: import_zod2.z.string().default(""),
498
+ description: import_zod2.z.string().default(""),
499
+ personality: import_zod2.z.string().nullable().default(""),
500
+ // Can be null in wild (141 cards)
501
+ scenario: import_zod2.z.string().default(""),
502
+ first_mes: import_zod2.z.string().default(""),
503
+ mes_example: import_zod2.z.string().nullable().default(""),
504
+ // Can be null in wild (186 cards)
505
+ // Optional fields
506
+ creator_notes: import_zod2.z.string().optional(),
507
+ system_prompt: import_zod2.z.string().optional(),
508
+ post_history_instructions: import_zod2.z.string().optional(),
509
+ alternate_greetings: import_zod2.z.array(import_zod2.z.string()).optional(),
510
+ character_book: CCv2CharacterBookSchema.optional().nullable(),
511
+ tags: import_zod2.z.array(import_zod2.z.string()).optional(),
512
+ creator: import_zod2.z.string().optional(),
513
+ character_version: import_zod2.z.string().optional(),
514
+ extensions: import_zod2.z.record(import_zod2.z.unknown()).optional()
515
+ });
516
+ var CCv2WrappedSchema = import_zod2.z.object({
517
+ spec: import_zod2.z.literal("chara_card_v2"),
518
+ spec_version: import_zod2.z.literal("2.0"),
519
+ data: CCv2DataSchema
520
+ });
521
+ var CCv3LorebookEntrySchema = import_zod3.z.object({
522
+ keys: import_zod3.z.array(import_zod3.z.string()),
523
+ content: import_zod3.z.string(),
524
+ enabled: import_zod3.z.boolean(),
525
+ insertion_order: import_zod3.z.number().int(),
526
+ // Optional fields
527
+ case_sensitive: import_zod3.z.boolean().optional(),
528
+ name: import_zod3.z.string().optional(),
529
+ priority: import_zod3.z.number().int().optional(),
530
+ id: import_zod3.z.number().int().optional(),
531
+ comment: import_zod3.z.string().optional(),
532
+ selective: import_zod3.z.boolean().optional(),
533
+ secondary_keys: import_zod3.z.array(import_zod3.z.string()).optional(),
534
+ constant: import_zod3.z.boolean().optional(),
535
+ position: import_zod3.z.enum(["before_char", "after_char"]).optional(),
536
+ extensions: import_zod3.z.record(import_zod3.z.unknown()).optional(),
537
+ // v3 specific
538
+ automation_id: import_zod3.z.string().optional(),
539
+ role: import_zod3.z.enum(["system", "user", "assistant"]).optional(),
540
+ group: import_zod3.z.string().optional(),
541
+ scan_frequency: import_zod3.z.number().int().nonnegative().optional(),
542
+ probability: import_zod3.z.number().min(0).max(1).optional(),
543
+ use_regex: import_zod3.z.boolean().optional(),
544
+ depth: import_zod3.z.number().int().nonnegative().optional(),
545
+ selective_logic: import_zod3.z.enum(["AND", "NOT"]).optional()
546
+ });
547
+ var CCv3CharacterBookSchema = import_zod3.z.object({
548
+ name: import_zod3.z.string().optional(),
549
+ description: import_zod3.z.string().optional(),
550
+ scan_depth: import_zod3.z.number().int().nonnegative().optional(),
551
+ token_budget: import_zod3.z.number().int().nonnegative().optional(),
552
+ recursive_scanning: import_zod3.z.boolean().optional(),
553
+ extensions: import_zod3.z.record(import_zod3.z.unknown()).optional(),
554
+ entries: import_zod3.z.array(CCv3LorebookEntrySchema)
555
+ });
556
+ var CCv3DataInnerSchema = import_zod3.z.object({
557
+ // Core fields - use .default('') to handle missing fields in malformed cards
558
+ name: import_zod3.z.string().default(""),
559
+ description: import_zod3.z.string().default(""),
560
+ personality: import_zod3.z.string().nullable().default(""),
561
+ // Can be null in wild (141 cards)
562
+ scenario: import_zod3.z.string().default(""),
563
+ first_mes: import_zod3.z.string().default(""),
564
+ mes_example: import_zod3.z.string().nullable().default(""),
565
+ // Can be null in wild (186 cards)
566
+ // "Required" per spec but often missing in wild - use defaults for leniency
567
+ creator: import_zod3.z.string().default(""),
568
+ character_version: import_zod3.z.string().default(""),
569
+ tags: import_zod3.z.array(import_zod3.z.string()).default([]),
570
+ group_only_greetings: import_zod3.z.array(import_zod3.z.string()).default([]),
571
+ // Optional fields
572
+ creator_notes: import_zod3.z.string().optional(),
573
+ system_prompt: import_zod3.z.string().optional(),
574
+ post_history_instructions: import_zod3.z.string().optional(),
575
+ alternate_greetings: import_zod3.z.array(import_zod3.z.string()).optional(),
576
+ character_book: CCv3CharacterBookSchema.optional().nullable(),
577
+ extensions: import_zod3.z.record(import_zod3.z.unknown()).optional(),
578
+ // v3 specific
579
+ assets: import_zod3.z.array(AssetDescriptorSchema).optional(),
580
+ nickname: import_zod3.z.string().optional(),
581
+ creator_notes_multilingual: import_zod3.z.record(import_zod3.z.string()).optional(),
582
+ source: import_zod3.z.array(import_zod3.z.string()).optional(),
583
+ creation_date: import_zod3.z.number().int().nonnegative().optional(),
584
+ // Unix timestamp in seconds
585
+ modification_date: import_zod3.z.number().int().nonnegative().optional()
586
+ // Unix timestamp in seconds
587
+ });
588
+ var CCv3DataSchema = import_zod3.z.object({
589
+ spec: import_zod3.z.literal("chara_card_v3"),
590
+ spec_version: import_zod3.z.literal("3.0"),
591
+ data: CCv3DataInnerSchema
592
+ });
593
+ function hasRisuExtensions(extensions) {
594
+ if (!extensions) return false;
595
+ return "risuai" in extensions || "risu" in extensions;
596
+ }
597
+
598
+ // ../charx/dist/index.js
599
+ var import_fflate2 = require("fflate");
600
+ var DEFAULT_OPTIONS = {
601
+ maxFileSize: 10 * 1024 * 1024,
602
+ // 10MB
603
+ maxAssetSize: 50 * 1024 * 1024,
604
+ // 50MB (Risu standard)
605
+ maxTotalSize: 200 * 1024 * 1024,
606
+ // 200MB
607
+ preserveXMeta: true,
608
+ preserveModuleRisum: true
609
+ };
610
+ function isCharX(data) {
611
+ const zipOffset = getZipOffset(data);
612
+ if (zipOffset < 0) return false;
613
+ const zipData = data.subarray(zipOffset);
614
+ const cardJsonMarker = new TextEncoder().encode("card.json");
615
+ const scanSize = Math.min(zipData.length, 65536);
616
+ const startOffset = zipData.length - scanSize;
617
+ for (let i = startOffset; i < zipData.length - cardJsonMarker.length; i++) {
618
+ let found = true;
619
+ for (let j = 0; j < cardJsonMarker.length; j++) {
620
+ if (zipData[i + j] !== cardJsonMarker[j]) {
621
+ found = false;
622
+ break;
623
+ }
624
+ }
625
+ if (found) return true;
626
+ }
627
+ return false;
628
+ }
629
+ function readCharX(data, options = {}) {
630
+ const opts = { ...DEFAULT_OPTIONS, ...options };
631
+ let unzipped;
632
+ try {
633
+ unzipped = streamingUnzipSync(data, {
634
+ maxFileSize: opts.maxAssetSize,
635
+ maxTotalSize: opts.maxTotalSize,
636
+ maxFiles: 1e4
637
+ // CharX can have many assets
638
+ });
639
+ } catch (err) {
640
+ if (err instanceof ZipPreflightError) {
641
+ throw new SizeLimitError(
642
+ err.totalSize || err.entrySize || 0,
643
+ err.maxSize || err.maxEntrySize || opts.maxTotalSize,
644
+ err.oversizedEntry || "CharX archive"
645
+ );
646
+ }
647
+ throw new ParseError(
648
+ `Failed to unzip CharX: ${err instanceof Error ? err.message : String(err)}`,
649
+ "charx"
650
+ );
651
+ }
652
+ let cardJson = null;
653
+ const assets = [];
654
+ const metadata = /* @__PURE__ */ new Map();
655
+ let moduleRisum;
656
+ for (const [fileName, fileData] of Object.entries(unzipped)) {
657
+ if (fileName.endsWith("/") || fileData.length === 0) continue;
658
+ if (fileName === "card.json") {
659
+ if (fileData.length > opts.maxFileSize) {
660
+ throw new SizeLimitError(fileData.length, opts.maxFileSize, "card.json");
661
+ }
662
+ try {
663
+ const content = toString(fileData);
664
+ cardJson = JSON.parse(content);
665
+ } catch (err) {
666
+ throw new ParseError(
667
+ `Failed to parse card.json: ${err instanceof Error ? err.message : String(err)}`,
668
+ "charx"
669
+ );
670
+ }
671
+ continue;
672
+ }
673
+ if (opts.preserveXMeta) {
674
+ const metaMatch = fileName.match(/^x_meta\/(\d+)\.json$/);
675
+ if (metaMatch) {
676
+ const index = parseInt(metaMatch[1], 10);
677
+ try {
678
+ const content = toString(fileData);
679
+ const meta = JSON.parse(content);
680
+ metadata.set(index, meta);
681
+ } catch {
682
+ }
683
+ continue;
684
+ }
685
+ }
686
+ if (fileName === "module.risum" && opts.preserveModuleRisum) {
687
+ moduleRisum = fileData;
688
+ continue;
689
+ }
690
+ if (fileName.startsWith("assets/")) {
691
+ const name = fileName.split("/").pop() || "unknown";
692
+ const ext = name.split(".").pop() || "bin";
693
+ assets.push({
694
+ path: fileName,
695
+ descriptor: {
696
+ type: "custom",
697
+ name: name.replace(/\.[^.]+$/, ""),
698
+ // Remove extension
699
+ uri: `embeded://${fileName}`,
700
+ ext
701
+ },
702
+ buffer: fileData
703
+ });
704
+ continue;
705
+ }
706
+ }
707
+ if (!cardJson) {
708
+ throw new ParseError("CharX file does not contain card.json", "charx");
709
+ }
710
+ if (cardJson.spec !== "chara_card_v3") {
711
+ throw new ParseError(
712
+ `Invalid card spec: expected "chara_card_v3", got "${cardJson.spec}"`,
713
+ "charx"
714
+ );
715
+ }
716
+ const matchedAssets = matchAssetsToDescriptors(assets, cardJson.data.assets || []);
717
+ const isRisuFormat = !!moduleRisum || hasRisuExtensions(cardJson.data.extensions);
718
+ return {
719
+ card: cardJson,
720
+ assets: matchedAssets,
721
+ metadata: metadata.size > 0 ? metadata : void 0,
722
+ moduleRisum,
723
+ isRisuFormat
724
+ };
725
+ }
726
+ function matchAssetsToDescriptors(extractedAssets, descriptors) {
727
+ const assetsByPath = /* @__PURE__ */ new Map();
728
+ for (const asset of extractedAssets) {
729
+ assetsByPath.set(asset.path, asset);
730
+ }
731
+ const matched = [];
732
+ for (const descriptor of descriptors) {
733
+ const parsed = parseURI(descriptor.uri);
734
+ if (parsed.scheme === "embeded" && parsed.path) {
735
+ const asset = assetsByPath.get(parsed.path);
736
+ if (asset) {
737
+ matched.push({
738
+ ...asset,
739
+ descriptor
740
+ });
741
+ } else {
742
+ matched.push({
743
+ path: parsed.path,
744
+ descriptor,
745
+ buffer: void 0
746
+ });
747
+ }
748
+ } else if (parsed.scheme === "ccdefault") {
749
+ matched.push({
750
+ path: "ccdefault:",
751
+ descriptor,
752
+ buffer: void 0
753
+ });
754
+ } else if (parsed.scheme === "https" || parsed.scheme === "http") {
755
+ matched.push({
756
+ path: descriptor.uri,
757
+ descriptor,
758
+ buffer: void 0
759
+ });
760
+ } else if (parsed.scheme === "data") {
761
+ if (parsed.data && parsed.encoding === "base64") {
762
+ const buffer = decode(parsed.data);
763
+ matched.push({
764
+ path: "data:",
765
+ descriptor,
766
+ buffer
767
+ });
768
+ } else {
769
+ matched.push({
770
+ path: "data:",
771
+ descriptor,
772
+ buffer: void 0
773
+ });
774
+ }
775
+ }
776
+ }
777
+ return matched;
778
+ }
779
+ function readCardJsonOnly(data) {
780
+ let unzipped;
781
+ try {
782
+ unzipped = streamingUnzipSync(data, {
783
+ maxFileSize: DEFAULT_OPTIONS.maxFileSize,
784
+ // 10MB for card.json
785
+ maxTotalSize: DEFAULT_OPTIONS.maxTotalSize,
786
+ // 200MB total
787
+ maxFiles: 1e4
788
+ });
789
+ } catch (err) {
790
+ throw new ParseError(
791
+ `Failed to unzip CharX: ${err instanceof Error ? err.message : String(err)}`,
792
+ "charx"
793
+ );
794
+ }
795
+ const cardData = unzipped["card.json"];
796
+ if (!cardData) {
797
+ throw new ParseError("card.json not found in CharX file", "charx");
798
+ }
799
+ try {
800
+ const content = toString(cardData);
801
+ return JSON.parse(content);
802
+ } catch (err) {
803
+ throw new ParseError(
804
+ `Failed to parse card.json: ${err instanceof Error ? err.message : String(err)}`,
805
+ "charx"
806
+ );
807
+ }
808
+ }
809
+ async function readCharXAsync(data, options = {}) {
810
+ const result = readCharX(data, options);
811
+ if (!options.fetchRemoteAssets || !options.assetFetcher) {
812
+ return result;
813
+ }
814
+ const fetchedAssets = await Promise.all(
815
+ result.assets.map(async (asset) => {
816
+ if (asset.buffer) {
817
+ return asset;
818
+ }
819
+ const parsed = parseURI(asset.descriptor.uri);
820
+ if ((parsed.scheme === "https" || parsed.scheme === "http") && parsed.url) {
821
+ try {
822
+ const buffer = await options.assetFetcher(parsed.url);
823
+ if (buffer) {
824
+ return { ...asset, buffer };
825
+ }
826
+ } catch {
827
+ }
828
+ }
829
+ return asset;
830
+ })
831
+ );
832
+ return {
833
+ ...result,
834
+ assets: fetchedAssets
835
+ };
836
+ }
837
+ function getCharxCategory(mimetype) {
838
+ if (mimetype.startsWith("image/")) return "images";
839
+ if (mimetype.startsWith("audio/")) return "audio";
840
+ if (mimetype.startsWith("video/")) return "video";
841
+ return "other";
842
+ }
843
+ function sanitizeName(name, ext) {
844
+ let safeName = name;
845
+ if (safeName.toLowerCase().endsWith(`.${ext.toLowerCase()}`)) {
846
+ safeName = safeName.substring(0, safeName.length - (ext.length + 1));
847
+ }
848
+ safeName = safeName.replace(/[._]/g, "-").replace(/[^a-zA-Z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
849
+ if (!safeName) safeName = "asset";
850
+ return safeName;
851
+ }
852
+ function writeCharX(card, assets, options = {}) {
853
+ const {
854
+ spec = "v3",
855
+ compressionLevel = 6,
856
+ emitXMeta = spec === "risu",
857
+ emitReadme = false,
858
+ moduleRisum
859
+ } = options;
860
+ const transformedCard = transformAssetUris(card, assets);
861
+ const zipEntries = {};
862
+ const cardJson = JSON.stringify(transformedCard, null, 2);
863
+ zipEntries["card.json"] = [fromString(cardJson), { level: compressionLevel }];
864
+ if (emitReadme) {
865
+ const readme = `Character: ${card.data.name}
866
+ Created with Character Foundry
867
+
868
+ This is a CharX character card package.
869
+ Import this file into SillyTavern, RisuAI, or other compatible applications.
870
+ `;
871
+ zipEntries["readme.txt"] = [fromString(readme), { level: compressionLevel }];
872
+ }
873
+ let assetCount = 0;
874
+ for (let i = 0; i < assets.length; i++) {
875
+ const asset = assets[i];
876
+ const mimetype = getMimeTypeFromExt(asset.ext);
877
+ const category = getCharxCategory(mimetype);
878
+ const safeName = sanitizeName(asset.name, asset.ext);
879
+ const assetPath = `assets/${asset.type}/${category}/${safeName}.${asset.ext}`;
880
+ zipEntries[assetPath] = [asset.data, { level: compressionLevel }];
881
+ assetCount++;
882
+ if (emitXMeta && mimetype.startsWith("image/")) {
883
+ const metaJson = JSON.stringify({
884
+ type: mimetype.split("/")[1]?.toUpperCase() || "PNG"
885
+ });
886
+ zipEntries[`x_meta/${i}.json`] = [fromString(metaJson), { level: compressionLevel }];
887
+ }
888
+ }
889
+ if (moduleRisum) {
890
+ zipEntries["module.risum"] = [moduleRisum, { level: compressionLevel }];
891
+ }
892
+ const buffer = (0, import_fflate2.zipSync)(zipEntries);
893
+ return {
894
+ buffer,
895
+ assetCount,
896
+ totalSize: buffer.length
897
+ };
898
+ }
899
+ function transformAssetUris(card, assets) {
900
+ const transformed = JSON.parse(JSON.stringify(card));
901
+ transformed.data.assets = assets.map((asset) => {
902
+ const mimetype = getMimeTypeFromExt(asset.ext);
903
+ const category = getCharxCategory(mimetype);
904
+ const safeName = sanitizeName(asset.name, asset.ext);
905
+ return {
906
+ type: asset.type,
907
+ uri: `embeded://assets/${asset.type}/${category}/${safeName}.${asset.ext}`,
908
+ name: safeName,
909
+ ext: asset.ext
910
+ };
911
+ });
912
+ return transformed;
913
+ }
914
+ async function writeCharXAsync(card, assets, options = {}) {
915
+ return writeCharX(card, assets, options);
916
+ }
917
+ //# sourceMappingURL=charx.cjs.map