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