@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/schemas.js CHANGED
@@ -1,2 +1,856 @@
1
- export * from '@character-foundry/schemas';
1
+ // ../schemas/dist/index.js
2
+ import { z } from "zod";
3
+ import { z as z2 } from "zod";
4
+ import { z as z3 } from "zod";
5
+ import "zod";
6
+ var ISO8601Schema = z.string().datetime();
7
+ var UUIDSchema = z.string().uuid();
8
+ var SpecSchema = z.enum(["v2", "v3"]);
9
+ var SourceFormatSchema = z.enum([
10
+ "png_v2",
11
+ // PNG with 'chara' chunk (v2)
12
+ "png_v3",
13
+ // PNG with 'ccv3' chunk (v3)
14
+ "json_v2",
15
+ // Raw JSON v2
16
+ "json_v3",
17
+ // Raw JSON v3
18
+ "charx",
19
+ // ZIP with card.json (v3 spec)
20
+ "charx_risu",
21
+ // ZIP with card.json + module.risum
22
+ "charx_jpeg",
23
+ // JPEG with appended ZIP (read-only)
24
+ "voxta"
25
+ // VoxPkg format
26
+ ]);
27
+ var OriginalShapeSchema = z.enum(["wrapped", "unwrapped", "legacy"]);
28
+ var AssetTypeSchema = z.enum([
29
+ "icon",
30
+ "background",
31
+ "emotion",
32
+ "user_icon",
33
+ "sound",
34
+ "video",
35
+ "custom",
36
+ "x-risu-asset"
37
+ ]);
38
+ var AssetDescriptorSchema = z.object({
39
+ type: AssetTypeSchema,
40
+ uri: z.string(),
41
+ name: z.string(),
42
+ ext: z.string()
43
+ });
44
+ var ExtractedAssetSchema = z.object({
45
+ descriptor: AssetDescriptorSchema,
46
+ data: z.instanceof(Uint8Array),
47
+ mimeType: z.string()
48
+ });
49
+ var CCv2LorebookEntrySchema = z2.object({
50
+ keys: z2.array(z2.string()),
51
+ content: z2.string(),
52
+ enabled: z2.boolean(),
53
+ insertion_order: z2.number().int(),
54
+ // Optional fields
55
+ extensions: z2.record(z2.unknown()).optional(),
56
+ case_sensitive: z2.boolean().optional(),
57
+ name: z2.string().optional(),
58
+ priority: z2.number().int().optional(),
59
+ id: z2.number().int().optional(),
60
+ comment: z2.string().optional(),
61
+ selective: z2.boolean().optional(),
62
+ secondary_keys: z2.array(z2.string()).optional(),
63
+ constant: z2.boolean().optional(),
64
+ position: z2.enum(["before_char", "after_char"]).optional()
65
+ });
66
+ var CCv2CharacterBookSchema = z2.object({
67
+ name: z2.string().optional(),
68
+ description: z2.string().optional(),
69
+ scan_depth: z2.number().int().nonnegative().optional(),
70
+ token_budget: z2.number().int().nonnegative().optional(),
71
+ recursive_scanning: z2.boolean().optional(),
72
+ extensions: z2.record(z2.unknown()).optional(),
73
+ entries: z2.array(CCv2LorebookEntrySchema)
74
+ });
75
+ var CCv2DataSchema = z2.object({
76
+ // Core fields - use .default('') to handle missing fields in malformed cards
77
+ name: z2.string().default(""),
78
+ description: z2.string().default(""),
79
+ personality: z2.string().nullable().default(""),
80
+ // Can be null in wild (141 cards)
81
+ scenario: z2.string().default(""),
82
+ first_mes: z2.string().default(""),
83
+ mes_example: z2.string().nullable().default(""),
84
+ // Can be null in wild (186 cards)
85
+ // Optional fields
86
+ creator_notes: z2.string().optional(),
87
+ system_prompt: z2.string().optional(),
88
+ post_history_instructions: z2.string().optional(),
89
+ alternate_greetings: z2.array(z2.string()).optional(),
90
+ character_book: CCv2CharacterBookSchema.optional().nullable(),
91
+ tags: z2.array(z2.string()).optional(),
92
+ creator: z2.string().optional(),
93
+ character_version: z2.string().optional(),
94
+ extensions: z2.record(z2.unknown()).optional()
95
+ });
96
+ var CCv2WrappedSchema = z2.object({
97
+ spec: z2.literal("chara_card_v2"),
98
+ spec_version: z2.literal("2.0"),
99
+ data: CCv2DataSchema
100
+ });
101
+ function isWrappedV2(data) {
102
+ return CCv2WrappedSchema.safeParse(data).success;
103
+ }
104
+ function isV2CardData(data) {
105
+ return CCv2WrappedSchema.safeParse(data).success || CCv2DataSchema.safeParse(data).success;
106
+ }
107
+ function parseWrappedV2(data) {
108
+ return CCv2WrappedSchema.parse(data);
109
+ }
110
+ function parseV2Data(data) {
111
+ return CCv2DataSchema.parse(data);
112
+ }
113
+ function looksLikeWrappedV2(data) {
114
+ if (!data || typeof data !== "object") return false;
115
+ const obj = data;
116
+ return obj.spec === "chara_card_v2" && obj.data !== null && typeof obj.data === "object";
117
+ }
118
+ function getV2Data(card) {
119
+ if (looksLikeWrappedV2(card)) {
120
+ return card.data;
121
+ }
122
+ return card;
123
+ }
124
+ var CCv3LorebookEntrySchema = z3.object({
125
+ keys: z3.array(z3.string()),
126
+ content: z3.string(),
127
+ enabled: z3.boolean(),
128
+ insertion_order: z3.number().int(),
129
+ // Optional fields
130
+ case_sensitive: z3.boolean().optional(),
131
+ name: z3.string().optional(),
132
+ priority: z3.number().int().optional(),
133
+ id: z3.number().int().optional(),
134
+ comment: z3.string().optional(),
135
+ selective: z3.boolean().optional(),
136
+ secondary_keys: z3.array(z3.string()).optional(),
137
+ constant: z3.boolean().optional(),
138
+ position: z3.enum(["before_char", "after_char"]).optional(),
139
+ extensions: z3.record(z3.unknown()).optional(),
140
+ // v3 specific
141
+ automation_id: z3.string().optional(),
142
+ role: z3.enum(["system", "user", "assistant"]).optional(),
143
+ group: z3.string().optional(),
144
+ scan_frequency: z3.number().int().nonnegative().optional(),
145
+ probability: z3.number().min(0).max(1).optional(),
146
+ use_regex: z3.boolean().optional(),
147
+ depth: z3.number().int().nonnegative().optional(),
148
+ selective_logic: z3.enum(["AND", "NOT"]).optional()
149
+ });
150
+ var CCv3CharacterBookSchema = z3.object({
151
+ name: z3.string().optional(),
152
+ description: z3.string().optional(),
153
+ scan_depth: z3.number().int().nonnegative().optional(),
154
+ token_budget: z3.number().int().nonnegative().optional(),
155
+ recursive_scanning: z3.boolean().optional(),
156
+ extensions: z3.record(z3.unknown()).optional(),
157
+ entries: z3.array(CCv3LorebookEntrySchema)
158
+ });
159
+ var CCv3DataInnerSchema = z3.object({
160
+ // Core fields - use .default('') to handle missing fields in malformed cards
161
+ name: z3.string().default(""),
162
+ description: z3.string().default(""),
163
+ personality: z3.string().nullable().default(""),
164
+ // Can be null in wild (141 cards)
165
+ scenario: z3.string().default(""),
166
+ first_mes: z3.string().default(""),
167
+ mes_example: z3.string().nullable().default(""),
168
+ // Can be null in wild (186 cards)
169
+ // "Required" per spec but often missing in wild - use defaults for leniency
170
+ creator: z3.string().default(""),
171
+ character_version: z3.string().default(""),
172
+ tags: z3.array(z3.string()).default([]),
173
+ group_only_greetings: z3.array(z3.string()).default([]),
174
+ // Optional fields
175
+ creator_notes: z3.string().optional(),
176
+ system_prompt: z3.string().optional(),
177
+ post_history_instructions: z3.string().optional(),
178
+ alternate_greetings: z3.array(z3.string()).optional(),
179
+ character_book: CCv3CharacterBookSchema.optional().nullable(),
180
+ extensions: z3.record(z3.unknown()).optional(),
181
+ // v3 specific
182
+ assets: z3.array(AssetDescriptorSchema).optional(),
183
+ nickname: z3.string().optional(),
184
+ creator_notes_multilingual: z3.record(z3.string()).optional(),
185
+ source: z3.array(z3.string()).optional(),
186
+ creation_date: z3.number().int().nonnegative().optional(),
187
+ // Unix timestamp in seconds
188
+ modification_date: z3.number().int().nonnegative().optional()
189
+ // Unix timestamp in seconds
190
+ });
191
+ var CCv3DataSchema = z3.object({
192
+ spec: z3.literal("chara_card_v3"),
193
+ spec_version: z3.literal("3.0"),
194
+ data: CCv3DataInnerSchema
195
+ });
196
+ function isV3Card(data) {
197
+ return CCv3DataSchema.safeParse(data).success;
198
+ }
199
+ function parseV3Card(data) {
200
+ return CCv3DataSchema.parse(data);
201
+ }
202
+ function parseV3DataInner(data) {
203
+ return CCv3DataInnerSchema.parse(data);
204
+ }
205
+ function getV3Data(card) {
206
+ return card.data;
207
+ }
208
+ function looksLikeV3Card(data) {
209
+ if (!data || typeof data !== "object") return false;
210
+ const obj = data;
211
+ return obj.spec === "chara_card_v3" && obj.data !== null && typeof obj.data === "object";
212
+ }
213
+ function hasRisuExtensions(extensions) {
214
+ if (!extensions) return false;
215
+ return "risuai" in extensions || "risu" in extensions;
216
+ }
217
+ function hasRisuScripts(extensions) {
218
+ if (!extensions) return false;
219
+ const risu = extensions.risuai;
220
+ if (!risu) return false;
221
+ return !!risu.triggerscript || !!risu.customScripts;
222
+ }
223
+ function hasDepthPrompt(extensions) {
224
+ if (!extensions) return false;
225
+ if ("depth_prompt" in extensions && extensions.depth_prompt) return true;
226
+ const risu = extensions.risuai;
227
+ return !!risu?.depth_prompt;
228
+ }
229
+ function createEmptyNormalizedCard() {
230
+ return {
231
+ name: "",
232
+ description: "",
233
+ personality: "",
234
+ scenario: "",
235
+ firstMes: "",
236
+ mesExample: "",
237
+ alternateGreetings: [],
238
+ groupOnlyGreetings: [],
239
+ tags: [],
240
+ extensions: {}
241
+ };
242
+ }
243
+ function createEmptyFeatures() {
244
+ return {
245
+ hasAlternateGreetings: false,
246
+ alternateGreetingsCount: 0,
247
+ totalGreetingsCount: 1,
248
+ // first_mes always counts as 1
249
+ hasLorebook: false,
250
+ lorebookEntriesCount: 0,
251
+ hasEmbeddedImages: false,
252
+ embeddedImagesCount: 0,
253
+ hasGallery: false,
254
+ hasRisuExtensions: false,
255
+ hasRisuScripts: false,
256
+ hasDepthPrompt: false,
257
+ hasVoxtaAppearance: false,
258
+ tokens: {
259
+ description: 0,
260
+ personality: 0,
261
+ scenario: 0,
262
+ firstMes: 0,
263
+ mesExample: 0,
264
+ systemPrompt: 0,
265
+ total: 0
266
+ }
267
+ };
268
+ }
269
+ function deriveFeatures(card) {
270
+ const isV3 = "assets" in card;
271
+ const altGreetings = card.alternate_greetings ?? [];
272
+ const hasAlternateGreetings = altGreetings.length > 0;
273
+ const alternateGreetingsCount = altGreetings.length;
274
+ const totalGreetingsCount = 1 + alternateGreetingsCount;
275
+ const characterBook = card.character_book;
276
+ const hasLorebook2 = !!characterBook && characterBook.entries.length > 0;
277
+ const lorebookEntriesCount = characterBook?.entries.length ?? 0;
278
+ const assets = isV3 ? card.assets ?? [] : [];
279
+ const imageAssetTypes = ["icon", "background", "emotion", "custom"];
280
+ const imageAssets = assets.filter(
281
+ (a) => imageAssetTypes.includes(a.type) || ["png", "jpg", "jpeg", "webp", "gif"].includes(a.ext.toLowerCase())
282
+ );
283
+ const hasGallery = imageAssets.length > 0;
284
+ const embeddedImageCount = countEmbeddedImages(card);
285
+ const hasEmbeddedImages = embeddedImageCount > 0;
286
+ const extensions = card.extensions ?? {};
287
+ const hasRisu = hasRisuExtensions(extensions);
288
+ const hasScripts = hasRisuScripts(extensions);
289
+ const hasDepth = hasDepthPrompt(extensions);
290
+ const hasVoxta = checkVoxtaAppearance(extensions);
291
+ const tokens = {
292
+ description: 0,
293
+ personality: 0,
294
+ scenario: 0,
295
+ firstMes: 0,
296
+ mesExample: 0,
297
+ systemPrompt: 0,
298
+ total: 0
299
+ };
300
+ return {
301
+ hasAlternateGreetings,
302
+ alternateGreetingsCount,
303
+ totalGreetingsCount,
304
+ hasLorebook: hasLorebook2,
305
+ lorebookEntriesCount,
306
+ hasEmbeddedImages,
307
+ embeddedImagesCount: embeddedImageCount,
308
+ hasGallery,
309
+ hasRisuExtensions: hasRisu,
310
+ hasRisuScripts: hasScripts,
311
+ hasDepthPrompt: hasDepth,
312
+ hasVoxtaAppearance: hasVoxta,
313
+ tokens
314
+ };
315
+ }
316
+ function countEmbeddedImages(card) {
317
+ const textFields = [
318
+ card.description,
319
+ card.personality,
320
+ card.scenario,
321
+ card.first_mes,
322
+ card.mes_example,
323
+ card.creator_notes,
324
+ card.system_prompt,
325
+ card.post_history_instructions,
326
+ ...card.alternate_greetings ?? []
327
+ ].filter((field) => typeof field === "string");
328
+ if ("group_only_greetings" in card) {
329
+ textFields.push(...card.group_only_greetings ?? []);
330
+ }
331
+ let count = 0;
332
+ const dataUrlPattern = /data:image\/[^;]+;base64,/g;
333
+ for (const text of textFields) {
334
+ const matches = text.match(dataUrlPattern);
335
+ if (matches) {
336
+ count += matches.length;
337
+ }
338
+ }
339
+ return count;
340
+ }
341
+ function checkVoxtaAppearance(extensions) {
342
+ if (!extensions.voxta) return false;
343
+ const voxta = extensions.voxta;
344
+ return !!voxta.appearance;
345
+ }
346
+ var V3_ONLY_FIELDS = ["group_only_greetings", "creation_date", "modification_date", "assets"];
347
+ function detectSpec(data) {
348
+ return detectSpecDetailed(data).spec;
349
+ }
350
+ function detectSpecDetailed(data) {
351
+ const result = {
352
+ spec: null,
353
+ confidence: "low",
354
+ indicators: [],
355
+ warnings: []
356
+ };
357
+ if (!data || typeof data !== "object") {
358
+ result.indicators.push("Input is not an object");
359
+ return result;
360
+ }
361
+ const obj = data;
362
+ const dataObj = obj.data && typeof obj.data === "object" ? obj.data : null;
363
+ if (obj.spec === "chara_card_v3") {
364
+ result.spec = "v3";
365
+ result.confidence = "high";
366
+ result.indicators.push('spec field is "chara_card_v3"');
367
+ if (obj.spec_version && obj.spec_version !== "3.0") {
368
+ result.warnings.push(`spec_version "${obj.spec_version}" inconsistent with v3 spec`);
369
+ }
370
+ return result;
371
+ }
372
+ if (obj.spec === "chara_card_v2") {
373
+ result.spec = "v2";
374
+ result.confidence = "high";
375
+ result.indicators.push('spec field is "chara_card_v2"');
376
+ if (dataObj) {
377
+ for (const field of V3_ONLY_FIELDS) {
378
+ if (field in dataObj) {
379
+ result.warnings.push(`V3-only field "${field}" found in V2 card`);
380
+ }
381
+ }
382
+ }
383
+ if (obj.spec_version && obj.spec_version !== "2.0") {
384
+ result.warnings.push(`spec_version "${obj.spec_version}" inconsistent with v2 spec`);
385
+ }
386
+ return result;
387
+ }
388
+ if (typeof obj.spec_version === "string") {
389
+ if (obj.spec_version.startsWith("3")) {
390
+ result.spec = "v3";
391
+ result.confidence = "high";
392
+ result.indicators.push(`spec_version "${obj.spec_version}" starts with "3"`);
393
+ return result;
394
+ }
395
+ if (obj.spec_version.startsWith("2")) {
396
+ result.spec = "v2";
397
+ result.confidence = "high";
398
+ result.indicators.push(`spec_version "${obj.spec_version}" starts with "2"`);
399
+ return result;
400
+ }
401
+ }
402
+ if (obj.spec_version === 2 || obj.spec_version === 2) {
403
+ result.spec = "v2";
404
+ result.confidence = "high";
405
+ result.indicators.push(`spec_version is numeric ${obj.spec_version}`);
406
+ return result;
407
+ }
408
+ if (dataObj) {
409
+ const v3Fields = [];
410
+ for (const field of V3_ONLY_FIELDS) {
411
+ if (field in dataObj) {
412
+ v3Fields.push(field);
413
+ }
414
+ }
415
+ if (v3Fields.length > 0) {
416
+ result.spec = "v3";
417
+ result.confidence = "medium";
418
+ result.indicators.push(`Has V3-only fields: ${v3Fields.join(", ")}`);
419
+ return result;
420
+ }
421
+ }
422
+ const rootV3Fields = [];
423
+ for (const field of V3_ONLY_FIELDS) {
424
+ if (field in obj) {
425
+ rootV3Fields.push(field);
426
+ }
427
+ }
428
+ if (rootV3Fields.length > 0) {
429
+ result.spec = "v3";
430
+ result.confidence = "medium";
431
+ result.indicators.push(`Has V3-only fields at root: ${rootV3Fields.join(", ")}`);
432
+ result.warnings.push("V3 fields found at root level instead of data object");
433
+ return result;
434
+ }
435
+ if (obj.spec && dataObj) {
436
+ const dataName = dataObj.name;
437
+ if (dataName && typeof dataName === "string") {
438
+ if (typeof obj.spec === "string") {
439
+ if (obj.spec.includes("v3") || obj.spec.includes("3")) {
440
+ result.spec = "v3";
441
+ result.confidence = "medium";
442
+ result.indicators.push(`spec field "${obj.spec}" contains "v3" or "3"`);
443
+ return result;
444
+ }
445
+ if (obj.spec.includes("v2") || obj.spec.includes("2")) {
446
+ result.spec = "v2";
447
+ result.confidence = "medium";
448
+ result.indicators.push(`spec field "${obj.spec}" contains "v2" or "2"`);
449
+ return result;
450
+ }
451
+ }
452
+ result.spec = "v3";
453
+ result.confidence = "medium";
454
+ result.indicators.push("Has wrapped format with spec and data.name");
455
+ return result;
456
+ }
457
+ }
458
+ if (obj.name && typeof obj.name === "string") {
459
+ if ("description" in obj || "personality" in obj || "scenario" in obj) {
460
+ result.spec = "v2";
461
+ result.confidence = "medium";
462
+ result.indicators.push("Unwrapped format with name, description/personality/scenario");
463
+ return result;
464
+ }
465
+ }
466
+ if (dataObj && typeof dataObj.name === "string") {
467
+ if ("description" in dataObj || "personality" in dataObj) {
468
+ result.spec = "v2";
469
+ result.confidence = "low";
470
+ result.indicators.push("Has data object with name and card fields, but no spec");
471
+ result.warnings.push("Missing spec field");
472
+ return result;
473
+ }
474
+ }
475
+ result.indicators.push("No card structure detected");
476
+ return result;
477
+ }
478
+ function hasLorebook(data) {
479
+ if (!data || typeof data !== "object") return false;
480
+ const obj = data;
481
+ const wrapped = obj.data;
482
+ if (wrapped?.character_book) {
483
+ const book = wrapped.character_book;
484
+ if (Array.isArray(book.entries) && book.entries.length > 0) return true;
485
+ }
486
+ if (obj.character_book) {
487
+ const book = obj.character_book;
488
+ if (Array.isArray(book.entries) && book.entries.length > 0) return true;
489
+ }
490
+ return false;
491
+ }
492
+ function looksLikeCard(data) {
493
+ if (!data || typeof data !== "object") return false;
494
+ const obj = data;
495
+ if (obj.spec === "chara_card_v2" || obj.spec === "chara_card_v3") {
496
+ return true;
497
+ }
498
+ if (obj.data && typeof obj.data === "object") {
499
+ const dataObj = obj.data;
500
+ if (typeof dataObj.name === "string" && dataObj.name.length > 0) {
501
+ return true;
502
+ }
503
+ }
504
+ if (typeof obj.name === "string" && obj.name.length > 0) {
505
+ if ("description" in obj || "personality" in obj || "first_mes" in obj) {
506
+ return true;
507
+ }
508
+ }
509
+ return false;
510
+ }
511
+ var POSITION_MAP = {
512
+ 0: "before_char",
513
+ 1: "after_char"
514
+ };
515
+ var V3_ONLY_ENTRY_FIELDS = [
516
+ "probability",
517
+ "depth",
518
+ "group",
519
+ "scan_frequency",
520
+ "use_regex",
521
+ "selective_logic",
522
+ "role",
523
+ "automation_id"
524
+ ];
525
+ var V2_REQUIRED_DEFAULTS = {
526
+ name: "",
527
+ description: "",
528
+ personality: "",
529
+ scenario: "",
530
+ first_mes: "",
531
+ mes_example: ""
532
+ };
533
+ var V3_REQUIRED_DEFAULTS = {
534
+ name: "",
535
+ description: "",
536
+ personality: "",
537
+ scenario: "",
538
+ first_mes: "",
539
+ mes_example: "",
540
+ creator: "",
541
+ character_version: "1.0",
542
+ tags: [],
543
+ group_only_greetings: []
544
+ };
545
+ var DATA_FIELDS = [
546
+ "name",
547
+ "description",
548
+ "personality",
549
+ "scenario",
550
+ "first_mes",
551
+ "mes_example",
552
+ "creator_notes",
553
+ "system_prompt",
554
+ "post_history_instructions",
555
+ "alternate_greetings",
556
+ "character_book",
557
+ "tags",
558
+ "creator",
559
+ "character_version",
560
+ "extensions",
561
+ "assets",
562
+ "nickname",
563
+ "creator_notes_multilingual",
564
+ "source",
565
+ "creation_date",
566
+ "modification_date",
567
+ "group_only_greetings"
568
+ ];
569
+ function deepClone(obj) {
570
+ if (obj === null || obj === void 0) {
571
+ return obj;
572
+ }
573
+ if (Array.isArray(obj)) {
574
+ return obj.map((item) => deepClone(item));
575
+ }
576
+ if (typeof obj === "object") {
577
+ const result = {};
578
+ for (const [key, value] of Object.entries(obj)) {
579
+ result[key] = deepClone(value);
580
+ }
581
+ return result;
582
+ }
583
+ return obj;
584
+ }
585
+ function isMilliseconds(timestamp) {
586
+ return timestamp > 1e10;
587
+ }
588
+ var CardNormalizer = {
589
+ /**
590
+ * Normalize card data to valid schema format.
591
+ *
592
+ * Handles:
593
+ * - Fixing spec/spec_version values
594
+ * - Moving misplaced fields to correct locations
595
+ * - Adding missing required fields with defaults
596
+ * - Handling hybrid formats (fields at root AND in data object)
597
+ *
598
+ * @param data - Raw card data (potentially malformed)
599
+ * @param spec - Target spec version
600
+ * @returns Normalized card data (does not mutate input)
601
+ */
602
+ normalize(data, spec) {
603
+ if (!data || typeof data !== "object") {
604
+ if (spec === "v3") {
605
+ return {
606
+ spec: "chara_card_v3",
607
+ spec_version: "3.0",
608
+ data: { ...V3_REQUIRED_DEFAULTS }
609
+ };
610
+ }
611
+ return {
612
+ spec: "chara_card_v2",
613
+ spec_version: "2.0",
614
+ data: { ...V2_REQUIRED_DEFAULTS }
615
+ };
616
+ }
617
+ const obj = data;
618
+ const result = {};
619
+ const existingData = obj.data && typeof obj.data === "object" ? obj.data : {};
620
+ const mergedData = {};
621
+ for (const [key, value] of Object.entries(existingData)) {
622
+ mergedData[key] = deepClone(value);
623
+ }
624
+ for (const field of DATA_FIELDS) {
625
+ if (field in obj && !(field in mergedData)) {
626
+ mergedData[field] = deepClone(obj[field]);
627
+ }
628
+ }
629
+ if (mergedData.character_book === null) {
630
+ delete mergedData.character_book;
631
+ }
632
+ if (mergedData.character_book && typeof mergedData.character_book === "object") {
633
+ mergedData.character_book = this.normalizeCharacterBook(
634
+ mergedData.character_book,
635
+ spec
636
+ );
637
+ }
638
+ const defaults = spec === "v3" ? V3_REQUIRED_DEFAULTS : V2_REQUIRED_DEFAULTS;
639
+ for (const [key, defaultValue] of Object.entries(defaults)) {
640
+ if (!(key in mergedData) || mergedData[key] === void 0) {
641
+ mergedData[key] = Array.isArray(defaultValue) ? [...defaultValue] : defaultValue;
642
+ }
643
+ }
644
+ if (mergedData.tags && !Array.isArray(mergedData.tags)) {
645
+ mergedData.tags = [];
646
+ }
647
+ if (mergedData.alternate_greetings && !Array.isArray(mergedData.alternate_greetings)) {
648
+ mergedData.alternate_greetings = [];
649
+ }
650
+ if (spec === "v3") {
651
+ if (mergedData.group_only_greetings && !Array.isArray(mergedData.group_only_greetings)) {
652
+ mergedData.group_only_greetings = [];
653
+ }
654
+ }
655
+ if (spec === "v3") {
656
+ result.spec = "chara_card_v3";
657
+ result.spec_version = "3.0";
658
+ result.data = this.fixTimestampsInner(mergedData);
659
+ } else {
660
+ result.spec = "chara_card_v2";
661
+ result.spec_version = "2.0";
662
+ result.data = mergedData;
663
+ }
664
+ return result;
665
+ },
666
+ /**
667
+ * Normalize a character book (lorebook).
668
+ *
669
+ * Handles:
670
+ * - Ensuring required fields exist
671
+ * - Converting numeric position values to string enums
672
+ * - Moving V3-only fields to extensions for V2 compatibility
673
+ *
674
+ * @param book - Raw character book data
675
+ * @param spec - Target spec version
676
+ * @returns Normalized character book
677
+ */
678
+ normalizeCharacterBook(book, spec) {
679
+ const result = {};
680
+ if (book.name !== void 0) result.name = book.name;
681
+ if (book.description !== void 0) result.description = book.description;
682
+ if (book.scan_depth !== void 0) result.scan_depth = book.scan_depth;
683
+ if (book.token_budget !== void 0) result.token_budget = book.token_budget;
684
+ if (book.recursive_scanning !== void 0)
685
+ result.recursive_scanning = book.recursive_scanning;
686
+ if (book.extensions !== void 0) result.extensions = deepClone(book.extensions);
687
+ const entries = Array.isArray(book.entries) ? book.entries : [];
688
+ result.entries = entries.map(
689
+ (entry) => this.normalizeEntry(entry, spec)
690
+ );
691
+ return result;
692
+ },
693
+ /**
694
+ * Normalize a single lorebook entry.
695
+ *
696
+ * Handles:
697
+ * - Converting numeric position to string enum
698
+ * - Moving V3-only fields to extensions for V2
699
+ * - Ensuring required fields exist
700
+ *
701
+ * @param entry - Raw entry data
702
+ * @param spec - Target spec version
703
+ * @returns Normalized entry
704
+ */
705
+ normalizeEntry(entry, spec) {
706
+ const result = {};
707
+ result.keys = Array.isArray(entry.keys) ? [...entry.keys] : [];
708
+ result.content = typeof entry.content === "string" ? entry.content : "";
709
+ result.enabled = entry.enabled !== false;
710
+ result.insertion_order = typeof entry.insertion_order === "number" ? entry.insertion_order : 0;
711
+ if (spec === "v2") {
712
+ result.extensions = entry.extensions && typeof entry.extensions === "object" ? deepClone(entry.extensions) : {};
713
+ }
714
+ if (entry.case_sensitive !== void 0) result.case_sensitive = entry.case_sensitive;
715
+ if (entry.name !== void 0) result.name = entry.name;
716
+ if (entry.priority !== void 0) result.priority = entry.priority;
717
+ if (entry.id !== void 0) result.id = entry.id;
718
+ if (entry.comment !== void 0) result.comment = entry.comment;
719
+ if (entry.selective !== void 0) result.selective = entry.selective;
720
+ if (entry.secondary_keys !== void 0) {
721
+ result.secondary_keys = Array.isArray(entry.secondary_keys) ? [...entry.secondary_keys] : [];
722
+ }
723
+ if (entry.constant !== void 0) result.constant = entry.constant;
724
+ if (entry.position !== void 0) {
725
+ if (typeof entry.position === "number") {
726
+ result.position = POSITION_MAP[entry.position] || "before_char";
727
+ } else if (entry.position === "before_char" || entry.position === "after_char") {
728
+ result.position = entry.position;
729
+ }
730
+ }
731
+ if (spec === "v3") {
732
+ if (entry.extensions !== void 0) result.extensions = deepClone(entry.extensions);
733
+ for (const field of V3_ONLY_ENTRY_FIELDS) {
734
+ if (entry[field] !== void 0) {
735
+ result[field] = entry[field];
736
+ }
737
+ }
738
+ } else {
739
+ const ext = result.extensions || {};
740
+ for (const field of V3_ONLY_ENTRY_FIELDS) {
741
+ if (entry[field] !== void 0) {
742
+ ext[field] = entry[field];
743
+ }
744
+ }
745
+ result.extensions = ext;
746
+ }
747
+ return result;
748
+ },
749
+ /**
750
+ * Fix CharacterTavern timestamp format (milliseconds -> seconds).
751
+ *
752
+ * CCv3 spec requires timestamps in seconds (Unix epoch).
753
+ * CharacterTavern exports timestamps in milliseconds.
754
+ *
755
+ * @param data - V3 card data
756
+ * @returns Card data with fixed timestamps (does not mutate input)
757
+ */
758
+ fixTimestamps(data) {
759
+ const result = deepClone(data);
760
+ result.data = this.fixTimestampsInner(
761
+ result.data
762
+ );
763
+ return result;
764
+ },
765
+ /**
766
+ * Internal: fix timestamps in data object
767
+ */
768
+ fixTimestampsInner(data) {
769
+ const result = { ...data };
770
+ if (typeof result.creation_date === "number" && isMilliseconds(result.creation_date)) {
771
+ result.creation_date = Math.floor(result.creation_date / 1e3);
772
+ }
773
+ if (typeof result.modification_date === "number" && isMilliseconds(result.modification_date)) {
774
+ result.modification_date = Math.floor(result.modification_date / 1e3);
775
+ }
776
+ return result;
777
+ },
778
+ /**
779
+ * Auto-detect spec and normalize.
780
+ *
781
+ * @param data - Raw card data
782
+ * @returns Normalized card data, or null if not a valid card
783
+ */
784
+ autoNormalize(data) {
785
+ const spec = detectSpec(data);
786
+ if (!spec) return null;
787
+ const targetSpec = spec === "v3" ? "v3" : "v2";
788
+ return this.normalize(data, targetSpec);
789
+ }
790
+ };
791
+ function zodErrorToMessage(zodError, context) {
792
+ const messages = zodError.errors.map((err) => {
793
+ const path = err.path.length > 0 ? `${err.path.join(".")}: ` : "";
794
+ return `${path}${err.message}`;
795
+ });
796
+ const message = messages.join("; ");
797
+ return context ? `${context} - ${message}` : message;
798
+ }
799
+ function getFirstErrorField(zodError) {
800
+ return zodError.errors[0]?.path[0]?.toString();
801
+ }
802
+ function safeParse(schema, data) {
803
+ const result = schema.safeParse(data);
804
+ if (result.success) {
805
+ return { success: true, data: result.data };
806
+ }
807
+ return {
808
+ success: false,
809
+ error: zodErrorToMessage(result.error),
810
+ field: getFirstErrorField(result.error)
811
+ };
812
+ }
813
+ export {
814
+ AssetDescriptorSchema,
815
+ AssetTypeSchema,
816
+ CCv2CharacterBookSchema,
817
+ CCv2DataSchema,
818
+ CCv2LorebookEntrySchema,
819
+ CCv2WrappedSchema,
820
+ CCv3CharacterBookSchema,
821
+ CCv3DataInnerSchema,
822
+ CCv3DataSchema,
823
+ CCv3LorebookEntrySchema,
824
+ CardNormalizer,
825
+ ExtractedAssetSchema,
826
+ ISO8601Schema,
827
+ OriginalShapeSchema,
828
+ SourceFormatSchema,
829
+ SpecSchema,
830
+ UUIDSchema,
831
+ createEmptyFeatures,
832
+ createEmptyNormalizedCard,
833
+ deriveFeatures,
834
+ detectSpec,
835
+ detectSpecDetailed,
836
+ getFirstErrorField,
837
+ getV2Data,
838
+ getV3Data,
839
+ hasDepthPrompt,
840
+ hasLorebook,
841
+ hasRisuExtensions,
842
+ hasRisuScripts,
843
+ isV2CardData,
844
+ isV3Card,
845
+ isWrappedV2,
846
+ looksLikeCard,
847
+ looksLikeV3Card,
848
+ looksLikeWrappedV2,
849
+ parseV2Data,
850
+ parseV3Card,
851
+ parseV3DataInner,
852
+ parseWrappedV2,
853
+ safeParse,
854
+ zodErrorToMessage
855
+ };
2
856
  //# sourceMappingURL=schemas.js.map