@character-foundry/character-foundry 0.1.3 → 0.1.6

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 +1859 -0
  3. package/dist/app-framework.cjs.map +1 -0
  4. package/dist/app-framework.d.cts +896 -0
  5. package/dist/app-framework.d.ts +896 -2
  6. package/dist/app-framework.js +1835 -1
  7. package/dist/app-framework.js.map +1 -1
  8. package/dist/charx.cjs +979 -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 +955 -1
  13. package/dist/charx.js.map +1 -1
  14. package/dist/core.cjs +755 -0
  15. package/dist/core.cjs.map +1 -0
  16. package/dist/core.d.cts +404 -0
  17. package/dist/core.d.ts +404 -2
  18. package/dist/core.js +731 -1
  19. package/dist/core.js.map +1 -1
  20. package/dist/exporter.cjs +7619 -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 +7602 -1
  25. package/dist/exporter.js.map +1 -1
  26. package/dist/federation.cjs +3916 -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 +3892 -1
  31. package/dist/federation.js.map +1 -1
  32. package/dist/index.cjs +9213 -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 +9196 -26
  37. package/dist/index.js.map +1 -1
  38. package/dist/loader.cjs +8951 -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 +8934 -1
  43. package/dist/loader.js.map +1 -1
  44. package/dist/lorebook.cjs +866 -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 +842 -1
  49. package/dist/lorebook.js.map +1 -1
  50. package/dist/media.cjs +6661 -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 +6644 -1
  55. package/dist/media.js.map +1 -1
  56. package/dist/normalizer.cjs +503 -0
  57. package/dist/normalizer.cjs.map +1 -0
  58. package/dist/normalizer.d.cts +1217 -0
  59. package/dist/normalizer.d.ts +1217 -2
  60. package/dist/normalizer.js +479 -1
  61. package/dist/normalizer.js.map +1 -1
  62. package/dist/png.cjs +797 -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 +773 -1
  67. package/dist/png.js.map +1 -1
  68. package/dist/schemas.cjs +879 -0
  69. package/dist/schemas.cjs.map +1 -0
  70. package/dist/schemas.d.cts +2208 -0
  71. package/dist/schemas.d.ts +2208 -2
  72. package/dist/schemas.js +855 -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 +7907 -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 +7890 -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/png.js CHANGED
@@ -1,2 +1,774 @@
1
- export * from '@character-foundry/png';
1
+ // ../png/dist/index.js
2
+ import { Inflate } from "fflate";
3
+
4
+ // ../core/dist/index.js
5
+ function readUInt32BE(data, offset) {
6
+ return (data[offset] << 24 | data[offset + 1] << 16 | data[offset + 2] << 8 | data[offset + 3]) >>> 0;
7
+ }
8
+ function writeUInt32BE(data, value, offset) {
9
+ data[offset] = value >>> 24 & 255;
10
+ data[offset + 1] = value >>> 16 & 255;
11
+ data[offset + 2] = value >>> 8 & 255;
12
+ data[offset + 3] = value & 255;
13
+ }
14
+ function indexOf(data, search, fromIndex = 0) {
15
+ outer: for (let i = fromIndex; i <= data.length - search.length; i++) {
16
+ for (let j = 0; j < search.length; j++) {
17
+ if (data[i + j] !== search[j]) continue outer;
18
+ }
19
+ return i;
20
+ }
21
+ return -1;
22
+ }
23
+ function concat(...arrays) {
24
+ const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);
25
+ const result = new Uint8Array(totalLength);
26
+ let offset = 0;
27
+ for (const arr of arrays) {
28
+ result.set(arr, offset);
29
+ offset += arr.length;
30
+ }
31
+ return result;
32
+ }
33
+ function slice(data, start, end) {
34
+ return data.subarray(start, end);
35
+ }
36
+ function fromString(str) {
37
+ return new TextEncoder().encode(str);
38
+ }
39
+ function toString(data) {
40
+ return new TextDecoder().decode(data);
41
+ }
42
+ function fromLatin1(str) {
43
+ const result = new Uint8Array(str.length);
44
+ for (let i = 0; i < str.length; i++) {
45
+ result[i] = str.charCodeAt(i) & 255;
46
+ }
47
+ return result;
48
+ }
49
+ function toLatin1(data) {
50
+ let result = "";
51
+ for (let i = 0; i < data.length; i++) {
52
+ result += String.fromCharCode(data[i]);
53
+ }
54
+ return result;
55
+ }
56
+ function alloc(size) {
57
+ return new Uint8Array(size);
58
+ }
59
+ var isNode = typeof process !== "undefined" && process.versions != null && process.versions.node != null;
60
+ var LARGE_BUFFER_THRESHOLD = 1024 * 1024;
61
+ function encode(data) {
62
+ if (isNode) {
63
+ return Buffer.from(data).toString("base64");
64
+ }
65
+ if (data.length > LARGE_BUFFER_THRESHOLD) {
66
+ return encodeChunked(data);
67
+ }
68
+ let binary = "";
69
+ for (let i = 0; i < data.length; i++) {
70
+ binary += String.fromCharCode(data[i]);
71
+ }
72
+ return btoa(binary);
73
+ }
74
+ function decode(base64) {
75
+ if (isNode) {
76
+ return new Uint8Array(Buffer.from(base64, "base64"));
77
+ }
78
+ const binary = atob(base64);
79
+ const result = new Uint8Array(binary.length);
80
+ for (let i = 0; i < binary.length; i++) {
81
+ result[i] = binary.charCodeAt(i);
82
+ }
83
+ return result;
84
+ }
85
+ var ENCODE_CHUNK_SIZE = 64 * 1024;
86
+ function encodeChunked(data) {
87
+ if (isNode) {
88
+ return Buffer.from(data).toString("base64");
89
+ }
90
+ const chunks = [];
91
+ for (let i = 0; i < data.length; i += ENCODE_CHUNK_SIZE) {
92
+ const chunk = data.subarray(i, Math.min(i + ENCODE_CHUNK_SIZE, data.length));
93
+ let binary = "";
94
+ for (let j = 0; j < chunk.length; j++) {
95
+ binary += String.fromCharCode(chunk[j]);
96
+ }
97
+ chunks.push(binary);
98
+ }
99
+ return btoa(chunks.join(""));
100
+ }
101
+ var FOUNDRY_ERROR_MARKER = /* @__PURE__ */ Symbol.for("@character-foundry/core:FoundryError");
102
+ var FoundryError = class extends Error {
103
+ constructor(message, code) {
104
+ super(message);
105
+ this.code = code;
106
+ this.name = "FoundryError";
107
+ if (Error.captureStackTrace) {
108
+ Error.captureStackTrace(this, this.constructor);
109
+ }
110
+ }
111
+ /** @internal Marker for cross-module identification */
112
+ [FOUNDRY_ERROR_MARKER] = true;
113
+ };
114
+ var ParseError = class extends FoundryError {
115
+ constructor(message, format) {
116
+ super(message, "PARSE_ERROR");
117
+ this.format = format;
118
+ this.name = "ParseError";
119
+ }
120
+ };
121
+ var SizeLimitError = class extends FoundryError {
122
+ constructor(actualSize, maxSize, context) {
123
+ const actualMB = (actualSize / 1024 / 1024).toFixed(2);
124
+ const maxMB = (maxSize / 1024 / 1024).toFixed(2);
125
+ const msg = context ? `${context}: Size ${actualMB}MB exceeds limit ${maxMB}MB` : `Size ${actualMB}MB exceeds limit ${maxMB}MB`;
126
+ super(msg, "SIZE_LIMIT_EXCEEDED");
127
+ this.actualSize = actualSize;
128
+ this.maxSize = maxSize;
129
+ this.name = "SizeLimitError";
130
+ }
131
+ };
132
+
133
+ // ../schemas/dist/index.js
134
+ import { z } from "zod";
135
+ import { z as z2 } from "zod";
136
+ import { z as z3 } from "zod";
137
+ import "zod";
138
+ var ISO8601Schema = z.string().datetime();
139
+ var UUIDSchema = z.string().uuid();
140
+ var SpecSchema = z.enum(["v2", "v3"]);
141
+ var SourceFormatSchema = z.enum([
142
+ "png_v2",
143
+ // PNG with 'chara' chunk (v2)
144
+ "png_v3",
145
+ // PNG with 'ccv3' chunk (v3)
146
+ "json_v2",
147
+ // Raw JSON v2
148
+ "json_v3",
149
+ // Raw JSON v3
150
+ "charx",
151
+ // ZIP with card.json (v3 spec)
152
+ "charx_risu",
153
+ // ZIP with card.json + module.risum
154
+ "charx_jpeg",
155
+ // JPEG with appended ZIP (read-only)
156
+ "voxta"
157
+ // VoxPkg format
158
+ ]);
159
+ var OriginalShapeSchema = z.enum(["wrapped", "unwrapped", "legacy"]);
160
+ var AssetTypeSchema = z.enum([
161
+ "icon",
162
+ "background",
163
+ "emotion",
164
+ "user_icon",
165
+ "sound",
166
+ "video",
167
+ "custom",
168
+ "x-risu-asset"
169
+ ]);
170
+ var AssetDescriptorSchema = z.object({
171
+ type: AssetTypeSchema,
172
+ uri: z.string(),
173
+ name: z.string(),
174
+ ext: z.string()
175
+ });
176
+ var ExtractedAssetSchema = z.object({
177
+ descriptor: AssetDescriptorSchema,
178
+ data: z.instanceof(Uint8Array),
179
+ mimeType: z.string()
180
+ });
181
+ var CCv2LorebookEntrySchema = z2.object({
182
+ keys: z2.array(z2.string()),
183
+ content: z2.string(),
184
+ enabled: z2.boolean(),
185
+ insertion_order: z2.number().int(),
186
+ // Optional fields
187
+ extensions: z2.record(z2.unknown()).optional(),
188
+ case_sensitive: z2.boolean().optional(),
189
+ name: z2.string().optional(),
190
+ priority: z2.number().int().optional(),
191
+ id: z2.number().int().optional(),
192
+ comment: z2.string().optional(),
193
+ selective: z2.boolean().optional(),
194
+ secondary_keys: z2.array(z2.string()).optional(),
195
+ constant: z2.boolean().optional(),
196
+ position: z2.enum(["before_char", "after_char"]).optional()
197
+ });
198
+ var CCv2CharacterBookSchema = z2.object({
199
+ name: z2.string().optional(),
200
+ description: z2.string().optional(),
201
+ scan_depth: z2.number().int().nonnegative().optional(),
202
+ token_budget: z2.number().int().nonnegative().optional(),
203
+ recursive_scanning: z2.boolean().optional(),
204
+ extensions: z2.record(z2.unknown()).optional(),
205
+ entries: z2.array(CCv2LorebookEntrySchema)
206
+ });
207
+ var CCv2DataSchema = z2.object({
208
+ // Core fields - use .default('') to handle missing fields in malformed cards
209
+ name: z2.string().default(""),
210
+ description: z2.string().default(""),
211
+ personality: z2.string().nullable().default(""),
212
+ // Can be null in wild (141 cards)
213
+ scenario: z2.string().default(""),
214
+ first_mes: z2.string().default(""),
215
+ mes_example: z2.string().nullable().default(""),
216
+ // Can be null in wild (186 cards)
217
+ // Optional fields
218
+ creator_notes: z2.string().optional(),
219
+ system_prompt: z2.string().optional(),
220
+ post_history_instructions: z2.string().optional(),
221
+ alternate_greetings: z2.array(z2.string()).optional(),
222
+ character_book: CCv2CharacterBookSchema.optional().nullable(),
223
+ tags: z2.array(z2.string()).optional(),
224
+ creator: z2.string().optional(),
225
+ character_version: z2.string().optional(),
226
+ extensions: z2.record(z2.unknown()).optional()
227
+ });
228
+ var CCv2WrappedSchema = z2.object({
229
+ spec: z2.literal("chara_card_v2"),
230
+ spec_version: z2.literal("2.0"),
231
+ data: CCv2DataSchema
232
+ });
233
+ var CCv3LorebookEntrySchema = z3.object({
234
+ keys: z3.array(z3.string()),
235
+ content: z3.string(),
236
+ enabled: z3.boolean(),
237
+ insertion_order: z3.number().int(),
238
+ // Optional fields
239
+ case_sensitive: z3.boolean().optional(),
240
+ name: z3.string().optional(),
241
+ priority: z3.number().int().optional(),
242
+ id: z3.number().int().optional(),
243
+ comment: z3.string().optional(),
244
+ selective: z3.boolean().optional(),
245
+ secondary_keys: z3.array(z3.string()).optional(),
246
+ constant: z3.boolean().optional(),
247
+ position: z3.enum(["before_char", "after_char"]).optional(),
248
+ extensions: z3.record(z3.unknown()).optional(),
249
+ // v3 specific
250
+ automation_id: z3.string().optional(),
251
+ role: z3.enum(["system", "user", "assistant"]).optional(),
252
+ group: z3.string().optional(),
253
+ scan_frequency: z3.number().int().nonnegative().optional(),
254
+ probability: z3.number().min(0).max(1).optional(),
255
+ use_regex: z3.boolean().optional(),
256
+ depth: z3.number().int().nonnegative().optional(),
257
+ selective_logic: z3.enum(["AND", "NOT"]).optional()
258
+ });
259
+ var CCv3CharacterBookSchema = z3.object({
260
+ name: z3.string().optional(),
261
+ description: z3.string().optional(),
262
+ scan_depth: z3.number().int().nonnegative().optional(),
263
+ token_budget: z3.number().int().nonnegative().optional(),
264
+ recursive_scanning: z3.boolean().optional(),
265
+ extensions: z3.record(z3.unknown()).optional(),
266
+ entries: z3.array(CCv3LorebookEntrySchema)
267
+ });
268
+ var CCv3DataInnerSchema = z3.object({
269
+ // Core fields - use .default('') to handle missing fields in malformed cards
270
+ name: z3.string().default(""),
271
+ description: z3.string().default(""),
272
+ personality: z3.string().nullable().default(""),
273
+ // Can be null in wild (141 cards)
274
+ scenario: z3.string().default(""),
275
+ first_mes: z3.string().default(""),
276
+ mes_example: z3.string().nullable().default(""),
277
+ // Can be null in wild (186 cards)
278
+ // "Required" per spec but often missing in wild - use defaults for leniency
279
+ creator: z3.string().default(""),
280
+ character_version: z3.string().default(""),
281
+ tags: z3.array(z3.string()).default([]),
282
+ group_only_greetings: z3.array(z3.string()).default([]),
283
+ // Optional fields
284
+ creator_notes: z3.string().optional(),
285
+ system_prompt: z3.string().optional(),
286
+ post_history_instructions: z3.string().optional(),
287
+ alternate_greetings: z3.array(z3.string()).optional(),
288
+ character_book: CCv3CharacterBookSchema.optional().nullable(),
289
+ extensions: z3.record(z3.unknown()).optional(),
290
+ // v3 specific
291
+ assets: z3.array(AssetDescriptorSchema).optional(),
292
+ nickname: z3.string().optional(),
293
+ creator_notes_multilingual: z3.record(z3.string()).optional(),
294
+ source: z3.array(z3.string()).optional(),
295
+ creation_date: z3.number().int().nonnegative().optional(),
296
+ // Unix timestamp in seconds
297
+ modification_date: z3.number().int().nonnegative().optional()
298
+ // Unix timestamp in seconds
299
+ });
300
+ var CCv3DataSchema = z3.object({
301
+ spec: z3.literal("chara_card_v3"),
302
+ spec_version: z3.literal("3.0"),
303
+ data: CCv3DataInnerSchema
304
+ });
305
+ var V3_ONLY_FIELDS = ["group_only_greetings", "creation_date", "modification_date", "assets"];
306
+ function detectSpec(data) {
307
+ return detectSpecDetailed(data).spec;
308
+ }
309
+ function detectSpecDetailed(data) {
310
+ const result = {
311
+ spec: null,
312
+ confidence: "low",
313
+ indicators: [],
314
+ warnings: []
315
+ };
316
+ if (!data || typeof data !== "object") {
317
+ result.indicators.push("Input is not an object");
318
+ return result;
319
+ }
320
+ const obj = data;
321
+ const dataObj = obj.data && typeof obj.data === "object" ? obj.data : null;
322
+ if (obj.spec === "chara_card_v3") {
323
+ result.spec = "v3";
324
+ result.confidence = "high";
325
+ result.indicators.push('spec field is "chara_card_v3"');
326
+ if (obj.spec_version && obj.spec_version !== "3.0") {
327
+ result.warnings.push(`spec_version "${obj.spec_version}" inconsistent with v3 spec`);
328
+ }
329
+ return result;
330
+ }
331
+ if (obj.spec === "chara_card_v2") {
332
+ result.spec = "v2";
333
+ result.confidence = "high";
334
+ result.indicators.push('spec field is "chara_card_v2"');
335
+ if (dataObj) {
336
+ for (const field of V3_ONLY_FIELDS) {
337
+ if (field in dataObj) {
338
+ result.warnings.push(`V3-only field "${field}" found in V2 card`);
339
+ }
340
+ }
341
+ }
342
+ if (obj.spec_version && obj.spec_version !== "2.0") {
343
+ result.warnings.push(`spec_version "${obj.spec_version}" inconsistent with v2 spec`);
344
+ }
345
+ return result;
346
+ }
347
+ if (typeof obj.spec_version === "string") {
348
+ if (obj.spec_version.startsWith("3")) {
349
+ result.spec = "v3";
350
+ result.confidence = "high";
351
+ result.indicators.push(`spec_version "${obj.spec_version}" starts with "3"`);
352
+ return result;
353
+ }
354
+ if (obj.spec_version.startsWith("2")) {
355
+ result.spec = "v2";
356
+ result.confidence = "high";
357
+ result.indicators.push(`spec_version "${obj.spec_version}" starts with "2"`);
358
+ return result;
359
+ }
360
+ }
361
+ if (obj.spec_version === 2 || obj.spec_version === 2) {
362
+ result.spec = "v2";
363
+ result.confidence = "high";
364
+ result.indicators.push(`spec_version is numeric ${obj.spec_version}`);
365
+ return result;
366
+ }
367
+ if (dataObj) {
368
+ const v3Fields = [];
369
+ for (const field of V3_ONLY_FIELDS) {
370
+ if (field in dataObj) {
371
+ v3Fields.push(field);
372
+ }
373
+ }
374
+ if (v3Fields.length > 0) {
375
+ result.spec = "v3";
376
+ result.confidence = "medium";
377
+ result.indicators.push(`Has V3-only fields: ${v3Fields.join(", ")}`);
378
+ return result;
379
+ }
380
+ }
381
+ const rootV3Fields = [];
382
+ for (const field of V3_ONLY_FIELDS) {
383
+ if (field in obj) {
384
+ rootV3Fields.push(field);
385
+ }
386
+ }
387
+ if (rootV3Fields.length > 0) {
388
+ result.spec = "v3";
389
+ result.confidence = "medium";
390
+ result.indicators.push(`Has V3-only fields at root: ${rootV3Fields.join(", ")}`);
391
+ result.warnings.push("V3 fields found at root level instead of data object");
392
+ return result;
393
+ }
394
+ if (obj.spec && dataObj) {
395
+ const dataName = dataObj.name;
396
+ if (dataName && typeof dataName === "string") {
397
+ if (typeof obj.spec === "string") {
398
+ if (obj.spec.includes("v3") || obj.spec.includes("3")) {
399
+ result.spec = "v3";
400
+ result.confidence = "medium";
401
+ result.indicators.push(`spec field "${obj.spec}" contains "v3" or "3"`);
402
+ return result;
403
+ }
404
+ if (obj.spec.includes("v2") || obj.spec.includes("2")) {
405
+ result.spec = "v2";
406
+ result.confidence = "medium";
407
+ result.indicators.push(`spec field "${obj.spec}" contains "v2" or "2"`);
408
+ return result;
409
+ }
410
+ }
411
+ result.spec = "v3";
412
+ result.confidence = "medium";
413
+ result.indicators.push("Has wrapped format with spec and data.name");
414
+ return result;
415
+ }
416
+ }
417
+ if (obj.name && typeof obj.name === "string") {
418
+ if ("description" in obj || "personality" in obj || "scenario" in obj) {
419
+ result.spec = "v2";
420
+ result.confidence = "medium";
421
+ result.indicators.push("Unwrapped format with name, description/personality/scenario");
422
+ return result;
423
+ }
424
+ }
425
+ if (dataObj && typeof dataObj.name === "string") {
426
+ if ("description" in dataObj || "personality" in dataObj) {
427
+ result.spec = "v2";
428
+ result.confidence = "low";
429
+ result.indicators.push("Has data object with name and card fields, but no spec");
430
+ result.warnings.push("Missing spec field");
431
+ return result;
432
+ }
433
+ }
434
+ result.indicators.push("No card structure detected");
435
+ return result;
436
+ }
437
+
438
+ // ../png/dist/index.js
439
+ var PNG_SIGNATURE = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]);
440
+ var MAX_CHUNK_SIZE = 50 * 1024 * 1024;
441
+ var MAX_INFLATED_SIZE = 16 * 1024 * 1024;
442
+ function inflateSyncWithLimit(compressed, maxSize = MAX_INFLATED_SIZE) {
443
+ const chunks = [];
444
+ let totalSize = 0;
445
+ let error = null;
446
+ const inflater = new Inflate((data, final) => {
447
+ if (error) return;
448
+ if (data && data.length > 0) {
449
+ totalSize += data.length;
450
+ if (totalSize > maxSize) {
451
+ error = new SizeLimitError(totalSize, maxSize, "inflated zTXt chunk");
452
+ return;
453
+ }
454
+ chunks.push(data);
455
+ }
456
+ });
457
+ try {
458
+ inflater.push(compressed instanceof Uint8Array ? compressed : new Uint8Array(compressed), true);
459
+ } catch (e) {
460
+ throw new ParseError(`Decompression failed: ${e instanceof Error ? e.message : String(e)}`, "png");
461
+ }
462
+ if (error) {
463
+ throw error;
464
+ }
465
+ return concat(...chunks);
466
+ }
467
+ var TEXT_CHUNK_KEYS = [
468
+ // v3 keys
469
+ "ccv3",
470
+ "chara_card_v3",
471
+ // v2 keys (most common)
472
+ "chara",
473
+ "ccv2",
474
+ "character",
475
+ // Alternative/legacy keys
476
+ "charactercard",
477
+ "card",
478
+ "CharacterCard",
479
+ "Chara"
480
+ ];
481
+ function isPNG(data) {
482
+ if (data.length < 8) return false;
483
+ for (let i = 0; i < 8; i++) {
484
+ if (data[i] !== PNG_SIGNATURE[i]) return false;
485
+ }
486
+ return true;
487
+ }
488
+ function parseTextChunks(data) {
489
+ const textChunks = [];
490
+ if (!isPNG(data)) {
491
+ return textChunks;
492
+ }
493
+ let offset = 8;
494
+ while (offset < data.length) {
495
+ if (offset + 4 > data.length) break;
496
+ const length = readUInt32BE(data, offset);
497
+ offset += 4;
498
+ if (offset + 4 > data.length) break;
499
+ const typeBytes = slice(data, offset, offset + 4);
500
+ const type = toLatin1(typeBytes);
501
+ offset += 4;
502
+ if (length > MAX_CHUNK_SIZE) {
503
+ throw new SizeLimitError(length, MAX_CHUNK_SIZE, `PNG chunk '${type}'`);
504
+ }
505
+ if (offset + length > data.length) break;
506
+ const chunkData = slice(data, offset, offset + length);
507
+ offset += length;
508
+ if (offset + 4 > data.length) break;
509
+ offset += 4;
510
+ if (type === "tEXt") {
511
+ const nullIndex = indexOf(chunkData, new Uint8Array([0]));
512
+ if (nullIndex !== -1) {
513
+ const keyword = toLatin1(slice(chunkData, 0, nullIndex));
514
+ const text = toString(slice(chunkData, nullIndex + 1));
515
+ textChunks.push({ keyword, text });
516
+ }
517
+ }
518
+ if (type === "zTXt") {
519
+ const nullIndex = indexOf(chunkData, new Uint8Array([0]));
520
+ if (nullIndex !== -1) {
521
+ const keyword = toLatin1(slice(chunkData, 0, nullIndex));
522
+ const compressionMethod = chunkData[nullIndex + 1];
523
+ if (compressionMethod === 0) {
524
+ try {
525
+ const compressedData = slice(chunkData, nullIndex + 2);
526
+ const decompressed = inflateSyncWithLimit(compressedData, MAX_INFLATED_SIZE);
527
+ const text = toString(decompressed);
528
+ textChunks.push({ keyword, text });
529
+ } catch (err) {
530
+ if (err instanceof SizeLimitError) {
531
+ throw err;
532
+ }
533
+ }
534
+ }
535
+ }
536
+ }
537
+ if (type === "IEND") break;
538
+ }
539
+ return textChunks;
540
+ }
541
+ function listChunks(data) {
542
+ const chunks = [];
543
+ if (!isPNG(data)) {
544
+ return chunks;
545
+ }
546
+ let offset = 8;
547
+ while (offset < data.length) {
548
+ if (offset + 4 > data.length) break;
549
+ const length = readUInt32BE(data, offset);
550
+ const chunkStart = offset;
551
+ offset += 4;
552
+ if (offset + 4 > data.length) break;
553
+ const type = toLatin1(slice(data, offset, offset + 4));
554
+ offset += 4;
555
+ chunks.push({ type, offset: chunkStart, length });
556
+ offset += length + 4;
557
+ if (type === "IEND") break;
558
+ }
559
+ return chunks;
560
+ }
561
+ function tryParseChunk(chunkData) {
562
+ try {
563
+ return JSON.parse(chunkData);
564
+ } catch {
565
+ try {
566
+ const decoded = toString(decode(chunkData));
567
+ return JSON.parse(decoded);
568
+ } catch {
569
+ throw new ParseError("Not valid JSON or base64-encoded JSON", "png");
570
+ }
571
+ }
572
+ }
573
+ function hasLorebookInData(json) {
574
+ if (!json || typeof json !== "object") return false;
575
+ const obj = json;
576
+ const data = obj.data;
577
+ if (data?.character_book) {
578
+ const book = data.character_book;
579
+ if (Array.isArray(book.entries) && book.entries.length > 0) return true;
580
+ }
581
+ if (obj.character_book) {
582
+ const book = obj.character_book;
583
+ if (Array.isArray(book.entries) && book.entries.length > 0) return true;
584
+ }
585
+ return false;
586
+ }
587
+ function extractFromPNG(data) {
588
+ if (!isPNG(data)) {
589
+ throw new ParseError("Invalid PNG signature", "png");
590
+ }
591
+ const textChunks = parseTextChunks(data);
592
+ if (textChunks.length === 0) {
593
+ throw new ParseError("No text chunks found in PNG", "png");
594
+ }
595
+ let fallbackResult = null;
596
+ for (const key of TEXT_CHUNK_KEYS) {
597
+ const matchingChunks = textChunks.filter((c) => c.keyword === key);
598
+ for (const chunk of matchingChunks) {
599
+ try {
600
+ const json = tryParseChunk(chunk.text);
601
+ const spec = detectSpec(json);
602
+ if (spec === "v3" || spec === "v2") {
603
+ const result = {
604
+ data: json,
605
+ spec,
606
+ extraChunks: textChunks.filter((c) => c.keyword !== key)
607
+ };
608
+ if (hasLorebookInData(json)) {
609
+ return result;
610
+ }
611
+ if (!fallbackResult) {
612
+ fallbackResult = result;
613
+ }
614
+ }
615
+ if (!spec && json && typeof json === "object") {
616
+ const obj = json;
617
+ let inferredResult = null;
618
+ if (obj.spec === "chara_card_v3" && obj.data && obj.data.name) {
619
+ inferredResult = { data: json, spec: "v3" };
620
+ } else if (obj.spec === "chara_card_v2" && obj.data && obj.data.name) {
621
+ inferredResult = { data: json, spec: "v2" };
622
+ } else if (obj.name && (obj.description || obj.personality || obj.scenario)) {
623
+ inferredResult = { data: json, spec: "v2" };
624
+ }
625
+ if (inferredResult) {
626
+ const fullResult = {
627
+ ...inferredResult,
628
+ extraChunks: textChunks.filter((c) => c.keyword !== key)
629
+ };
630
+ if (hasLorebookInData(json)) {
631
+ return fullResult;
632
+ }
633
+ if (!fallbackResult) fallbackResult = fullResult;
634
+ }
635
+ }
636
+ } catch {
637
+ }
638
+ }
639
+ }
640
+ if (fallbackResult) {
641
+ return fallbackResult;
642
+ }
643
+ throw new ParseError("No valid character card data found in PNG", "png");
644
+ }
645
+ var CRC_TABLE = null;
646
+ function getCRCTable() {
647
+ if (CRC_TABLE) return CRC_TABLE;
648
+ CRC_TABLE = new Uint32Array(256);
649
+ for (let i = 0; i < 256; i++) {
650
+ let c = i;
651
+ for (let j = 0; j < 8; j++) {
652
+ c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
653
+ }
654
+ CRC_TABLE[i] = c;
655
+ }
656
+ return CRC_TABLE;
657
+ }
658
+ function crc32(data) {
659
+ const table = getCRCTable();
660
+ let crc = 4294967295;
661
+ for (let i = 0; i < data.length; i++) {
662
+ crc = table[(crc ^ data[i]) & 255] ^ crc >>> 8;
663
+ }
664
+ return (crc ^ 4294967295) >>> 0;
665
+ }
666
+ function crc32Bytes(data) {
667
+ const crc = crc32(data);
668
+ const result = alloc(4);
669
+ writeUInt32BE(result, crc, 0);
670
+ return result;
671
+ }
672
+ function removeAllTextChunks(pngBuffer) {
673
+ if (!isPNG(pngBuffer)) {
674
+ throw new ParseError("Invalid PNG signature", "png");
675
+ }
676
+ const chunks = [slice(pngBuffer, 0, 8)];
677
+ let offset = 8;
678
+ while (offset < pngBuffer.length) {
679
+ if (offset + 4 > pngBuffer.length) break;
680
+ const length = readUInt32BE(pngBuffer, offset);
681
+ const lengthBuf = slice(pngBuffer, offset, offset + 4);
682
+ offset += 4;
683
+ if (offset + 4 > pngBuffer.length) break;
684
+ const type = toLatin1(slice(pngBuffer, offset, offset + 4));
685
+ const typeBuf = slice(pngBuffer, offset, offset + 4);
686
+ offset += 4;
687
+ if (offset + length + 4 > pngBuffer.length) break;
688
+ const dataBuf = slice(pngBuffer, offset, offset + length);
689
+ const crcBuf = slice(pngBuffer, offset + length, offset + length + 4);
690
+ offset += length + 4;
691
+ if (type === "tEXt" || type === "zTXt") {
692
+ continue;
693
+ }
694
+ chunks.push(lengthBuf, typeBuf, dataBuf, crcBuf);
695
+ if (type === "IEND") break;
696
+ }
697
+ return concat(...chunks);
698
+ }
699
+ function findIendOffset(pngBuffer) {
700
+ for (let i = pngBuffer.length - 12; i >= 8; i--) {
701
+ if (pngBuffer[i + 4] === 73 && // 'I'
702
+ pngBuffer[i + 5] === 69 && // 'E'
703
+ pngBuffer[i + 6] === 78 && // 'N'
704
+ pngBuffer[i + 7] === 68) {
705
+ return i;
706
+ }
707
+ }
708
+ return -1;
709
+ }
710
+ function createTextChunk(keyword, text) {
711
+ const keywordBuffer = fromLatin1(keyword);
712
+ const textBuffer = fromString(text);
713
+ const chunkData = concat(
714
+ keywordBuffer,
715
+ new Uint8Array([0]),
716
+ // null separator
717
+ textBuffer
718
+ );
719
+ const chunkType = fromLatin1("tEXt");
720
+ const crc = crc32Bytes(concat(chunkType, chunkData));
721
+ const lengthBuffer = alloc(4);
722
+ writeUInt32BE(lengthBuffer, chunkData.length, 0);
723
+ return concat(lengthBuffer, chunkType, chunkData, crc);
724
+ }
725
+ function injectTextChunk(pngBuffer, keyword, text) {
726
+ const iendOffset = findIendOffset(pngBuffer);
727
+ if (iendOffset === -1) {
728
+ throw new ParseError("Invalid PNG: IEND chunk not found", "png");
729
+ }
730
+ const textChunk = createTextChunk(keyword, text);
731
+ const beforeIend = slice(pngBuffer, 0, iendOffset);
732
+ const iendAndAfter = slice(pngBuffer, iendOffset);
733
+ return concat(beforeIend, textChunk, iendAndAfter);
734
+ }
735
+ function embedIntoPNG(imageBuffer, cardData, options = {}) {
736
+ const {
737
+ key = "chara",
738
+ base64 = true,
739
+ minify = true
740
+ } = options;
741
+ const cleanPng = removeAllTextChunks(imageBuffer);
742
+ const json = minify ? JSON.stringify(cardData) : JSON.stringify(cardData, null, 2);
743
+ const text = base64 ? encode(fromString(json)) : json;
744
+ return injectTextChunk(cleanPng, key, text);
745
+ }
746
+ function validatePNGSize(buffer, limits) {
747
+ const sizeMB = buffer.length / (1024 * 1024);
748
+ const warnings = [];
749
+ if (sizeMB > limits.max) {
750
+ return {
751
+ valid: false,
752
+ warnings: [`PNG size (${sizeMB.toFixed(2)}MB) exceeds maximum (${limits.max}MB)`]
753
+ };
754
+ }
755
+ if (sizeMB > limits.warn) {
756
+ warnings.push(`PNG size (${sizeMB.toFixed(2)}MB) is large (recommended: <${limits.warn}MB)`);
757
+ }
758
+ return { valid: true, warnings };
759
+ }
760
+ export {
761
+ PNG_SIGNATURE,
762
+ TEXT_CHUNK_KEYS,
763
+ crc32,
764
+ crc32Bytes,
765
+ embedIntoPNG,
766
+ extractFromPNG,
767
+ injectTextChunk,
768
+ isPNG,
769
+ listChunks,
770
+ parseTextChunks,
771
+ removeAllTextChunks,
772
+ validatePNGSize
773
+ };
2
774
  //# sourceMappingURL=png.js.map