@character-foundry/character-foundry 0.4.3 → 0.5.0

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 (63) hide show
  1. package/dist/app-framework.cjs +12 -3
  2. package/dist/app-framework.cjs.map +1 -1
  3. package/dist/app-framework.d.cts +9 -1
  4. package/dist/app-framework.d.ts +9 -1
  5. package/dist/app-framework.js +12 -3
  6. package/dist/app-framework.js.map +1 -1
  7. package/dist/charx.cjs +85 -52
  8. package/dist/charx.cjs.map +1 -1
  9. package/dist/charx.d.cts +22 -22
  10. package/dist/charx.d.ts +22 -22
  11. package/dist/charx.js +85 -52
  12. package/dist/charx.js.map +1 -1
  13. package/dist/exporter.cjs +104 -54
  14. package/dist/exporter.cjs.map +1 -1
  15. package/dist/exporter.d.cts +19 -19
  16. package/dist/exporter.d.ts +19 -19
  17. package/dist/exporter.js +104 -54
  18. package/dist/exporter.js.map +1 -1
  19. package/dist/federation.cjs +104 -36
  20. package/dist/federation.cjs.map +1 -1
  21. package/dist/federation.d.cts +54 -19
  22. package/dist/federation.d.ts +54 -19
  23. package/dist/federation.js +104 -36
  24. package/dist/federation.js.map +1 -1
  25. package/dist/index.cjs +104 -54
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/index.d.cts +29 -29
  28. package/dist/index.d.ts +29 -29
  29. package/dist/index.js +104 -54
  30. package/dist/index.js.map +1 -1
  31. package/dist/loader.cjs +171 -31
  32. package/dist/loader.cjs.map +1 -1
  33. package/dist/loader.d.cts +37 -23
  34. package/dist/loader.d.ts +37 -23
  35. package/dist/loader.js +171 -31
  36. package/dist/loader.js.map +1 -1
  37. package/dist/lorebook.d.cts +23 -23
  38. package/dist/lorebook.d.ts +23 -23
  39. package/dist/normalizer.cjs +72 -18
  40. package/dist/normalizer.cjs.map +1 -1
  41. package/dist/normalizer.d.cts +37 -37
  42. package/dist/normalizer.d.ts +37 -37
  43. package/dist/normalizer.js +72 -18
  44. package/dist/normalizer.js.map +1 -1
  45. package/dist/png.cjs +72 -18
  46. package/dist/png.cjs.map +1 -1
  47. package/dist/png.d.cts +25 -25
  48. package/dist/png.d.ts +25 -25
  49. package/dist/png.js +72 -18
  50. package/dist/png.js.map +1 -1
  51. package/dist/schemas.cjs +80 -23
  52. package/dist/schemas.cjs.map +1 -1
  53. package/dist/schemas.d.cts +85 -67
  54. package/dist/schemas.d.ts +85 -67
  55. package/dist/schemas.js +80 -23
  56. package/dist/schemas.js.map +1 -1
  57. package/dist/voxta.cjs +91 -20
  58. package/dist/voxta.cjs.map +1 -1
  59. package/dist/voxta.d.cts +23 -23
  60. package/dist/voxta.d.ts +23 -23
  61. package/dist/voxta.js +91 -20
  62. package/dist/voxta.js.map +1 -1
  63. package/package.json +24 -24
package/dist/loader.d.cts CHANGED
@@ -13,8 +13,8 @@ export type BinaryData = Uint8Array;
13
13
  declare const CCv3CharacterBookSchema: z.ZodObject<{
14
14
  name: z.ZodOptional<z.ZodString>;
15
15
  description: z.ZodOptional<z.ZodString>;
16
- scan_depth: z.ZodOptional<z.ZodNumber>;
17
- token_budget: z.ZodOptional<z.ZodNumber>;
16
+ scan_depth: z.ZodEffects<z.ZodOptional<z.ZodNumber>, number | undefined, unknown>;
17
+ token_budget: z.ZodEffects<z.ZodOptional<z.ZodNumber>, number | undefined, unknown>;
18
18
  recursive_scanning: z.ZodOptional<z.ZodBoolean>;
19
19
  extensions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
20
20
  entries: z.ZodArray<z.ZodObject<{
@@ -250,8 +250,8 @@ declare const CCv3CharacterBookSchema: z.ZodObject<{
250
250
  }, z.ZodTypeAny, "passthrough">[];
251
251
  name?: string | undefined;
252
252
  description?: string | undefined;
253
- scan_depth?: number | undefined;
254
- token_budget?: number | undefined;
253
+ scan_depth?: unknown;
254
+ token_budget?: unknown;
255
255
  recursive_scanning?: boolean | undefined;
256
256
  extensions?: Record<string, unknown> | undefined;
257
257
  }>;
@@ -276,8 +276,8 @@ declare const CCv3DataSchema: z.ZodObject<{
276
276
  character_book: z.ZodNullable<z.ZodOptional<z.ZodObject<{
277
277
  name: z.ZodOptional<z.ZodString>;
278
278
  description: z.ZodOptional<z.ZodString>;
279
- scan_depth: z.ZodOptional<z.ZodNumber>;
280
- token_budget: z.ZodOptional<z.ZodNumber>;
279
+ scan_depth: z.ZodEffects<z.ZodOptional<z.ZodNumber>, number | undefined, unknown>;
280
+ token_budget: z.ZodEffects<z.ZodOptional<z.ZodNumber>, number | undefined, unknown>;
281
281
  recursive_scanning: z.ZodOptional<z.ZodBoolean>;
282
282
  extensions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
283
283
  entries: z.ZodArray<z.ZodObject<{
@@ -513,14 +513,14 @@ declare const CCv3DataSchema: z.ZodObject<{
513
513
  }, z.ZodTypeAny, "passthrough">[];
514
514
  name?: string | undefined;
515
515
  description?: string | undefined;
516
- scan_depth?: number | undefined;
517
- token_budget?: number | undefined;
516
+ scan_depth?: unknown;
517
+ token_budget?: unknown;
518
518
  recursive_scanning?: boolean | undefined;
519
519
  extensions?: Record<string, unknown> | undefined;
520
520
  }>>>;
521
521
  extensions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
522
522
  assets: z.ZodOptional<z.ZodArray<z.ZodObject<{
523
- type: z.ZodEnum<[
523
+ type: z.ZodEffects<z.ZodEnum<[
524
524
  "icon",
525
525
  "background",
526
526
  "emotion",
@@ -529,7 +529,7 @@ declare const CCv3DataSchema: z.ZodObject<{
529
529
  "video",
530
530
  "custom",
531
531
  "x-risu-asset"
532
- ]>;
532
+ ]>, "custom" | "icon" | "background" | "emotion" | "user_icon" | "sound" | "video" | "x-risu-asset", unknown>;
533
533
  uri: z.ZodString;
534
534
  name: z.ZodString;
535
535
  ext: z.ZodString;
@@ -540,15 +540,15 @@ declare const CCv3DataSchema: z.ZodObject<{
540
540
  ext: string;
541
541
  }, {
542
542
  name: string;
543
- type: "custom" | "icon" | "background" | "emotion" | "user_icon" | "sound" | "video" | "x-risu-asset";
544
543
  uri: string;
545
544
  ext: string;
545
+ type?: unknown;
546
546
  }>, "many">>;
547
547
  nickname: z.ZodOptional<z.ZodString>;
548
548
  creator_notes_multilingual: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
549
549
  source: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
550
- creation_date: z.ZodOptional<z.ZodNumber>;
551
- modification_date: z.ZodOptional<z.ZodNumber>;
550
+ creation_date: z.ZodEffects<z.ZodOptional<z.ZodNumber>, number | undefined, unknown>;
551
+ modification_date: z.ZodEffects<z.ZodOptional<z.ZodNumber>, number | undefined, unknown>;
552
552
  }, "strip", z.ZodTypeAny, {
553
553
  name: string;
554
554
  description: string;
@@ -689,8 +689,8 @@ declare const CCv3DataSchema: z.ZodObject<{
689
689
  }, z.ZodTypeAny, "passthrough">[];
690
690
  name?: string | undefined;
691
691
  description?: string | undefined;
692
- scan_depth?: number | undefined;
693
- token_budget?: number | undefined;
692
+ scan_depth?: unknown;
693
+ token_budget?: unknown;
694
694
  recursive_scanning?: boolean | undefined;
695
695
  extensions?: Record<string, unknown> | undefined;
696
696
  } | null | undefined;
@@ -700,15 +700,15 @@ declare const CCv3DataSchema: z.ZodObject<{
700
700
  group_only_greetings?: string[] | undefined;
701
701
  assets?: {
702
702
  name: string;
703
- type: "custom" | "icon" | "background" | "emotion" | "user_icon" | "sound" | "video" | "x-risu-asset";
704
703
  uri: string;
705
704
  ext: string;
705
+ type?: unknown;
706
706
  }[] | undefined;
707
707
  nickname?: string | undefined;
708
708
  creator_notes_multilingual?: Record<string, string> | undefined;
709
709
  source?: string[] | undefined;
710
- creation_date?: number | undefined;
711
- modification_date?: number | undefined;
710
+ creation_date?: unknown;
711
+ modification_date?: unknown;
712
712
  }>;
713
713
  }, "strip", z.ZodTypeAny, {
714
714
  data: {
@@ -855,8 +855,8 @@ declare const CCv3DataSchema: z.ZodObject<{
855
855
  }, z.ZodTypeAny, "passthrough">[];
856
856
  name?: string | undefined;
857
857
  description?: string | undefined;
858
- scan_depth?: number | undefined;
859
- token_budget?: number | undefined;
858
+ scan_depth?: unknown;
859
+ token_budget?: unknown;
860
860
  recursive_scanning?: boolean | undefined;
861
861
  extensions?: Record<string, unknown> | undefined;
862
862
  } | null | undefined;
@@ -866,15 +866,15 @@ declare const CCv3DataSchema: z.ZodObject<{
866
866
  group_only_greetings?: string[] | undefined;
867
867
  assets?: {
868
868
  name: string;
869
- type: "custom" | "icon" | "background" | "emotion" | "user_icon" | "sound" | "video" | "x-risu-asset";
870
869
  uri: string;
871
870
  ext: string;
871
+ type?: unknown;
872
872
  }[] | undefined;
873
873
  nickname?: string | undefined;
874
874
  creator_notes_multilingual?: Record<string, string> | undefined;
875
875
  source?: string[] | undefined;
876
- creation_date?: number | undefined;
877
- modification_date?: number | undefined;
876
+ creation_date?: unknown;
877
+ modification_date?: unknown;
878
878
  };
879
879
  spec: "chara_card_v3";
880
880
  spec_version: "3.0";
@@ -1194,6 +1194,13 @@ export interface AuthoritativeMetadata {
1194
1194
  tokens: TokenCounts;
1195
1195
  /** Content hash computed server-side */
1196
1196
  contentHash: string;
1197
+ /**
1198
+ * Content hash v2 computed server-side.
1199
+ *
1200
+ * @remarks
1201
+ * v1 is preserved for backwards compatibility. Prefer v2 for new storage/deduplication.
1202
+ */
1203
+ contentHashV2?: string;
1197
1204
  /** Whether the card has a lorebook */
1198
1205
  hasLorebook: boolean;
1199
1206
  /** Number of lorebook entries */
@@ -1293,6 +1300,13 @@ export declare function validateClientMetadata(clientMetadata: ClientMetadata, p
1293
1300
  * @returns SHA-256 hash of canonical content
1294
1301
  */
1295
1302
  export declare function computeContentHash(card: CCv3Data): Promise<string>;
1303
+ /**
1304
+ * Compute content hash v2 for a card (standalone utility)
1305
+ *
1306
+ * @param card - CCv3 card data
1307
+ * @returns SHA-256 hash of canonical content v2
1308
+ */
1309
+ export declare function computeContentHashV2(card: CCv3Data): Promise<string>;
1296
1310
  /**
1297
1311
  * Options for synchronous metadata validation
1298
1312
  */
package/dist/loader.d.ts CHANGED
@@ -13,8 +13,8 @@ export type BinaryData = Uint8Array;
13
13
  declare const CCv3CharacterBookSchema: z.ZodObject<{
14
14
  name: z.ZodOptional<z.ZodString>;
15
15
  description: z.ZodOptional<z.ZodString>;
16
- scan_depth: z.ZodOptional<z.ZodNumber>;
17
- token_budget: z.ZodOptional<z.ZodNumber>;
16
+ scan_depth: z.ZodEffects<z.ZodOptional<z.ZodNumber>, number | undefined, unknown>;
17
+ token_budget: z.ZodEffects<z.ZodOptional<z.ZodNumber>, number | undefined, unknown>;
18
18
  recursive_scanning: z.ZodOptional<z.ZodBoolean>;
19
19
  extensions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
20
20
  entries: z.ZodArray<z.ZodObject<{
@@ -250,8 +250,8 @@ declare const CCv3CharacterBookSchema: z.ZodObject<{
250
250
  }, z.ZodTypeAny, "passthrough">[];
251
251
  name?: string | undefined;
252
252
  description?: string | undefined;
253
- scan_depth?: number | undefined;
254
- token_budget?: number | undefined;
253
+ scan_depth?: unknown;
254
+ token_budget?: unknown;
255
255
  recursive_scanning?: boolean | undefined;
256
256
  extensions?: Record<string, unknown> | undefined;
257
257
  }>;
@@ -276,8 +276,8 @@ declare const CCv3DataSchema: z.ZodObject<{
276
276
  character_book: z.ZodNullable<z.ZodOptional<z.ZodObject<{
277
277
  name: z.ZodOptional<z.ZodString>;
278
278
  description: z.ZodOptional<z.ZodString>;
279
- scan_depth: z.ZodOptional<z.ZodNumber>;
280
- token_budget: z.ZodOptional<z.ZodNumber>;
279
+ scan_depth: z.ZodEffects<z.ZodOptional<z.ZodNumber>, number | undefined, unknown>;
280
+ token_budget: z.ZodEffects<z.ZodOptional<z.ZodNumber>, number | undefined, unknown>;
281
281
  recursive_scanning: z.ZodOptional<z.ZodBoolean>;
282
282
  extensions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
283
283
  entries: z.ZodArray<z.ZodObject<{
@@ -513,14 +513,14 @@ declare const CCv3DataSchema: z.ZodObject<{
513
513
  }, z.ZodTypeAny, "passthrough">[];
514
514
  name?: string | undefined;
515
515
  description?: string | undefined;
516
- scan_depth?: number | undefined;
517
- token_budget?: number | undefined;
516
+ scan_depth?: unknown;
517
+ token_budget?: unknown;
518
518
  recursive_scanning?: boolean | undefined;
519
519
  extensions?: Record<string, unknown> | undefined;
520
520
  }>>>;
521
521
  extensions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
522
522
  assets: z.ZodOptional<z.ZodArray<z.ZodObject<{
523
- type: z.ZodEnum<[
523
+ type: z.ZodEffects<z.ZodEnum<[
524
524
  "icon",
525
525
  "background",
526
526
  "emotion",
@@ -529,7 +529,7 @@ declare const CCv3DataSchema: z.ZodObject<{
529
529
  "video",
530
530
  "custom",
531
531
  "x-risu-asset"
532
- ]>;
532
+ ]>, "custom" | "icon" | "background" | "emotion" | "user_icon" | "sound" | "video" | "x-risu-asset", unknown>;
533
533
  uri: z.ZodString;
534
534
  name: z.ZodString;
535
535
  ext: z.ZodString;
@@ -540,15 +540,15 @@ declare const CCv3DataSchema: z.ZodObject<{
540
540
  ext: string;
541
541
  }, {
542
542
  name: string;
543
- type: "custom" | "icon" | "background" | "emotion" | "user_icon" | "sound" | "video" | "x-risu-asset";
544
543
  uri: string;
545
544
  ext: string;
545
+ type?: unknown;
546
546
  }>, "many">>;
547
547
  nickname: z.ZodOptional<z.ZodString>;
548
548
  creator_notes_multilingual: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
549
549
  source: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
550
- creation_date: z.ZodOptional<z.ZodNumber>;
551
- modification_date: z.ZodOptional<z.ZodNumber>;
550
+ creation_date: z.ZodEffects<z.ZodOptional<z.ZodNumber>, number | undefined, unknown>;
551
+ modification_date: z.ZodEffects<z.ZodOptional<z.ZodNumber>, number | undefined, unknown>;
552
552
  }, "strip", z.ZodTypeAny, {
553
553
  name: string;
554
554
  description: string;
@@ -689,8 +689,8 @@ declare const CCv3DataSchema: z.ZodObject<{
689
689
  }, z.ZodTypeAny, "passthrough">[];
690
690
  name?: string | undefined;
691
691
  description?: string | undefined;
692
- scan_depth?: number | undefined;
693
- token_budget?: number | undefined;
692
+ scan_depth?: unknown;
693
+ token_budget?: unknown;
694
694
  recursive_scanning?: boolean | undefined;
695
695
  extensions?: Record<string, unknown> | undefined;
696
696
  } | null | undefined;
@@ -700,15 +700,15 @@ declare const CCv3DataSchema: z.ZodObject<{
700
700
  group_only_greetings?: string[] | undefined;
701
701
  assets?: {
702
702
  name: string;
703
- type: "custom" | "icon" | "background" | "emotion" | "user_icon" | "sound" | "video" | "x-risu-asset";
704
703
  uri: string;
705
704
  ext: string;
705
+ type?: unknown;
706
706
  }[] | undefined;
707
707
  nickname?: string | undefined;
708
708
  creator_notes_multilingual?: Record<string, string> | undefined;
709
709
  source?: string[] | undefined;
710
- creation_date?: number | undefined;
711
- modification_date?: number | undefined;
710
+ creation_date?: unknown;
711
+ modification_date?: unknown;
712
712
  }>;
713
713
  }, "strip", z.ZodTypeAny, {
714
714
  data: {
@@ -855,8 +855,8 @@ declare const CCv3DataSchema: z.ZodObject<{
855
855
  }, z.ZodTypeAny, "passthrough">[];
856
856
  name?: string | undefined;
857
857
  description?: string | undefined;
858
- scan_depth?: number | undefined;
859
- token_budget?: number | undefined;
858
+ scan_depth?: unknown;
859
+ token_budget?: unknown;
860
860
  recursive_scanning?: boolean | undefined;
861
861
  extensions?: Record<string, unknown> | undefined;
862
862
  } | null | undefined;
@@ -866,15 +866,15 @@ declare const CCv3DataSchema: z.ZodObject<{
866
866
  group_only_greetings?: string[] | undefined;
867
867
  assets?: {
868
868
  name: string;
869
- type: "custom" | "icon" | "background" | "emotion" | "user_icon" | "sound" | "video" | "x-risu-asset";
870
869
  uri: string;
871
870
  ext: string;
871
+ type?: unknown;
872
872
  }[] | undefined;
873
873
  nickname?: string | undefined;
874
874
  creator_notes_multilingual?: Record<string, string> | undefined;
875
875
  source?: string[] | undefined;
876
- creation_date?: number | undefined;
877
- modification_date?: number | undefined;
876
+ creation_date?: unknown;
877
+ modification_date?: unknown;
878
878
  };
879
879
  spec: "chara_card_v3";
880
880
  spec_version: "3.0";
@@ -1194,6 +1194,13 @@ export interface AuthoritativeMetadata {
1194
1194
  tokens: TokenCounts;
1195
1195
  /** Content hash computed server-side */
1196
1196
  contentHash: string;
1197
+ /**
1198
+ * Content hash v2 computed server-side.
1199
+ *
1200
+ * @remarks
1201
+ * v1 is preserved for backwards compatibility. Prefer v2 for new storage/deduplication.
1202
+ */
1203
+ contentHashV2?: string;
1197
1204
  /** Whether the card has a lorebook */
1198
1205
  hasLorebook: boolean;
1199
1206
  /** Number of lorebook entries */
@@ -1293,6 +1300,13 @@ export declare function validateClientMetadata(clientMetadata: ClientMetadata, p
1293
1300
  * @returns SHA-256 hash of canonical content
1294
1301
  */
1295
1302
  export declare function computeContentHash(card: CCv3Data): Promise<string>;
1303
+ /**
1304
+ * Compute content hash v2 for a card (standalone utility)
1305
+ *
1306
+ * @param card - CCv3 card data
1307
+ * @returns SHA-256 hash of canonical content v2
1308
+ */
1309
+ export declare function computeContentHashV2(card: CCv3Data): Promise<string>;
1296
1310
  /**
1297
1311
  * Options for synchronous metadata validation
1298
1312
  */
package/dist/loader.js CHANGED
@@ -413,6 +413,58 @@ import { z } from "zod";
413
413
  import { z as z2 } from "zod";
414
414
  import { z as z3 } from "zod";
415
415
  import "zod";
416
+ function preprocessTimestamp(val) {
417
+ if (val === null || val === void 0) return void 0;
418
+ let num;
419
+ if (typeof val === "number") {
420
+ num = val;
421
+ } else if (typeof val === "string") {
422
+ const trimmed = val.trim();
423
+ if (!trimmed) return void 0;
424
+ const parsed = Number(trimmed);
425
+ if (!isNaN(parsed)) {
426
+ num = parsed;
427
+ } else {
428
+ const date = new Date(trimmed);
429
+ if (isNaN(date.getTime())) return void 0;
430
+ num = Math.floor(date.getTime() / 1e3);
431
+ }
432
+ } else {
433
+ return void 0;
434
+ }
435
+ if (num > 1e10) {
436
+ num = Math.floor(num / 1e3);
437
+ }
438
+ if (num < 0) return void 0;
439
+ return num;
440
+ }
441
+ function preprocessNumeric(val) {
442
+ if (val === null || val === void 0) return void 0;
443
+ if (typeof val === "number") {
444
+ return isNaN(val) ? void 0 : val;
445
+ }
446
+ if (typeof val === "string") {
447
+ const trimmed = val.trim();
448
+ if (!trimmed) return void 0;
449
+ const parsed = Number(trimmed);
450
+ return isNaN(parsed) ? void 0 : parsed;
451
+ }
452
+ return void 0;
453
+ }
454
+ var KNOWN_ASSET_TYPES = /* @__PURE__ */ new Set([
455
+ "icon",
456
+ "background",
457
+ "emotion",
458
+ "user_icon",
459
+ "sound",
460
+ "video",
461
+ "custom",
462
+ "x-risu-asset"
463
+ ]);
464
+ function preprocessAssetType(val) {
465
+ if (typeof val !== "string") return "custom";
466
+ return KNOWN_ASSET_TYPES.has(val) ? val : "custom";
467
+ }
416
468
  var ISO8601Schema = z.string().datetime();
417
469
  var UUIDSchema = z.string().uuid();
418
470
  var SpecSchema = z.enum(["v2", "v3"]);
@@ -435,16 +487,19 @@ var SourceFormatSchema = z.enum([
435
487
  // VoxPkg format
436
488
  ]);
437
489
  var OriginalShapeSchema = z.enum(["wrapped", "unwrapped", "legacy"]);
438
- var AssetTypeSchema = z.enum([
439
- "icon",
440
- "background",
441
- "emotion",
442
- "user_icon",
443
- "sound",
444
- "video",
445
- "custom",
446
- "x-risu-asset"
447
- ]);
490
+ var AssetTypeSchema = z.preprocess(
491
+ preprocessAssetType,
492
+ z.enum([
493
+ "icon",
494
+ "background",
495
+ "emotion",
496
+ "user_icon",
497
+ "sound",
498
+ "video",
499
+ "custom",
500
+ "x-risu-asset"
501
+ ])
502
+ );
448
503
  var AssetDescriptorSchema = z.object({
449
504
  type: AssetTypeSchema,
450
505
  uri: z.string(),
@@ -478,8 +533,8 @@ var CCv2LorebookEntrySchema = z2.object({
478
533
  var CCv2CharacterBookSchema = z2.object({
479
534
  name: z2.string().optional(),
480
535
  description: z2.string().optional(),
481
- scan_depth: z2.number().int().nonnegative().optional(),
482
- token_budget: z2.number().int().nonnegative().optional(),
536
+ scan_depth: z2.preprocess(preprocessNumeric, z2.number().int().nonnegative().optional()),
537
+ token_budget: z2.preprocess(preprocessNumeric, z2.number().int().nonnegative().optional()),
483
538
  recursive_scanning: z2.boolean().optional(),
484
539
  extensions: z2.record(z2.unknown()).optional(),
485
540
  entries: z2.array(CCv2LorebookEntrySchema)
@@ -553,8 +608,8 @@ var CCv3LorebookEntrySchema = z3.object({
553
608
  var CCv3CharacterBookSchema = z3.object({
554
609
  name: z3.string().optional(),
555
610
  description: z3.string().optional(),
556
- scan_depth: z3.number().int().nonnegative().optional(),
557
- token_budget: z3.number().int().nonnegative().optional(),
611
+ scan_depth: z3.preprocess(preprocessNumeric, z3.number().int().nonnegative().optional()),
612
+ token_budget: z3.preprocess(preprocessNumeric, z3.number().int().nonnegative().optional()),
558
613
  recursive_scanning: z3.boolean().optional(),
559
614
  extensions: z3.record(z3.unknown()).optional(),
560
615
  entries: z3.array(CCv3LorebookEntrySchema)
@@ -586,10 +641,9 @@ var CCv3DataInnerSchema = z3.object({
586
641
  nickname: z3.string().optional(),
587
642
  creator_notes_multilingual: z3.record(z3.string()).optional(),
588
643
  source: z3.array(z3.string()).optional(),
589
- creation_date: z3.number().int().nonnegative().optional(),
590
- // Unix timestamp in seconds
591
- modification_date: z3.number().int().nonnegative().optional()
592
- // Unix timestamp in seconds
644
+ // Unix timestamps - preprocess to handle ISO strings, numeric strings, milliseconds
645
+ creation_date: z3.preprocess(preprocessTimestamp, z3.number().int().nonnegative().optional()),
646
+ modification_date: z3.preprocess(preprocessTimestamp, z3.number().int().nonnegative().optional())
593
647
  });
594
648
  var CCv3DataSchema = z3.object({
595
649
  spec: z3.literal("chara_card_v3"),
@@ -8697,7 +8751,7 @@ function defaultTokenCounter(_card) {
8697
8751
  total: 0
8698
8752
  };
8699
8753
  }
8700
- function getCanonicalContent(card) {
8754
+ function getCanonicalContentV1(card) {
8701
8755
  const normalized = {
8702
8756
  name: card.data.name,
8703
8757
  description: card.data.description || "",
@@ -8719,6 +8773,69 @@ function getCanonicalContent(card) {
8719
8773
  };
8720
8774
  return JSON.stringify(normalized, Object.keys(normalized).sort());
8721
8775
  }
8776
+ function stableStringify(value) {
8777
+ if (value === null) return "null";
8778
+ switch (typeof value) {
8779
+ case "string":
8780
+ return JSON.stringify(value);
8781
+ case "number":
8782
+ return Number.isFinite(value) ? String(value) : "null";
8783
+ case "boolean":
8784
+ return value ? "true" : "false";
8785
+ case "bigint":
8786
+ return JSON.stringify(value.toString());
8787
+ case "undefined":
8788
+ case "function":
8789
+ case "symbol":
8790
+ return "null";
8791
+ case "object": {
8792
+ if (Array.isArray(value)) {
8793
+ const parts2 = value.map((item) => {
8794
+ if (item === void 0 || typeof item === "function" || typeof item === "symbol") {
8795
+ return "null";
8796
+ }
8797
+ return stableStringify(item);
8798
+ });
8799
+ return `[${parts2.join(",")}]`;
8800
+ }
8801
+ const obj = value;
8802
+ const keys = Object.keys(obj).sort();
8803
+ const parts = [];
8804
+ for (const key of keys) {
8805
+ const v = obj[key];
8806
+ if (v === void 0 || typeof v === "function" || typeof v === "symbol") {
8807
+ continue;
8808
+ }
8809
+ parts.push(`${JSON.stringify(key)}:${stableStringify(v)}`);
8810
+ }
8811
+ return `{${parts.join(",")}}`;
8812
+ }
8813
+ default:
8814
+ return "null";
8815
+ }
8816
+ }
8817
+ function getCanonicalContentV2(card) {
8818
+ const normalized = {
8819
+ name: card.data.name,
8820
+ description: card.data.description || "",
8821
+ personality: card.data.personality || "",
8822
+ scenario: card.data.scenario || "",
8823
+ first_mes: card.data.first_mes || "",
8824
+ mes_example: card.data.mes_example || "",
8825
+ system_prompt: card.data.system_prompt || "",
8826
+ post_history_instructions: card.data.post_history_instructions || "",
8827
+ alternate_greetings: card.data.alternate_greetings || [],
8828
+ character_book: card.data.character_book ? {
8829
+ entries: (card.data.character_book.entries || []).map((e) => ({
8830
+ keys: e.keys,
8831
+ content: e.content,
8832
+ enabled: e.enabled
8833
+ }))
8834
+ } : null,
8835
+ creator_notes: card.data.creator_notes || ""
8836
+ };
8837
+ return stableStringify(normalized);
8838
+ }
8722
8839
  function isWithinTolerance(clientValue, computedValue, tolerance) {
8723
8840
  if (clientValue === void 0) return false;
8724
8841
  if (computedValue === 0) return clientValue === 0;
@@ -8739,15 +8856,18 @@ async function validateClientMetadata(clientMetadata, parseResult, options = {})
8739
8856
  const warnings = [];
8740
8857
  const errors = [];
8741
8858
  const computedTokens = countTokens(card);
8742
- const canonicalContent = getCanonicalContent(card);
8743
- const computedHash = await computeHash(canonicalContent);
8859
+ const canonicalContentV1 = getCanonicalContentV1(card);
8860
+ const canonicalContentV2 = getCanonicalContentV2(card);
8861
+ const computedHashV1 = await computeHash(canonicalContentV1);
8862
+ const computedHashV2 = await computeHash(canonicalContentV2);
8744
8863
  const entries = card.data.character_book?.entries || [];
8745
8864
  const computedHasLorebook = entries.length > 0;
8746
8865
  const computedLorebookCount = entries.length;
8747
8866
  const authoritative = {
8748
8867
  name: card.data.name,
8749
8868
  tokens: computedTokens,
8750
- contentHash: computedHash,
8869
+ contentHash: computedHashV1,
8870
+ contentHashV2: computedHashV2,
8751
8871
  hasLorebook: computedHasLorebook,
8752
8872
  lorebookEntriesCount: computedLorebookCount
8753
8873
  };
@@ -8759,21 +8879,27 @@ async function validateClientMetadata(clientMetadata, parseResult, options = {})
8759
8879
  withinTolerance: false
8760
8880
  });
8761
8881
  }
8762
- if (clientMetadata.contentHash !== computedHash) {
8882
+ const matchesV1 = clientMetadata.contentHash === computedHashV1;
8883
+ const matchesV2 = clientMetadata.contentHash === computedHashV2;
8884
+ if (!matchesV1 && !matchesV2) {
8763
8885
  const disc = {
8764
8886
  field: "contentHash",
8765
8887
  clientValue: clientMetadata.contentHash,
8766
- computedValue: computedHash,
8888
+ computedValue: computedHashV1,
8767
8889
  withinTolerance: false
8768
8890
  };
8769
8891
  discrepancies.push(disc);
8770
8892
  if (allowHashMismatch) {
8771
8893
  warnings.push(
8772
- `Content hash mismatch: client=${clientMetadata.contentHash.substring(0, 8)}..., server=${computedHash.substring(0, 8)}...`
8894
+ `Content hash mismatch: client=${clientMetadata.contentHash.substring(0, 8)}..., server(v1)=${computedHashV1.substring(0, 8)}..., server(v2)=${computedHashV2.substring(0, 8)}...`
8773
8895
  );
8774
8896
  } else {
8775
8897
  errors.push("Content hash mismatch - possible tampering or encoding difference");
8776
8898
  }
8899
+ } else if (matchesV1 && !matchesV2) {
8900
+ warnings.push(
8901
+ "Client contentHash matches legacy v1 canonicalization. Prefer authoritative.contentHashV2 for new storage."
8902
+ );
8777
8903
  }
8778
8904
  const tokenFields = [
8779
8905
  "description",
@@ -8854,7 +8980,11 @@ async function validateClientMetadata(clientMetadata, parseResult, options = {})
8854
8980
  };
8855
8981
  }
8856
8982
  async function computeContentHash(card) {
8857
- const content = getCanonicalContent(card);
8983
+ const content = getCanonicalContentV1(card);
8984
+ return sha256Hash(content);
8985
+ }
8986
+ async function computeContentHashV2(card) {
8987
+ const content = getCanonicalContentV2(card);
8858
8988
  return sha256Hash(content);
8859
8989
  }
8860
8990
  function validateClientMetadataSync(clientMetadata, parseResult, options) {
@@ -8870,15 +9000,18 @@ function validateClientMetadataSync(clientMetadata, parseResult, options) {
8870
9000
  const warnings = [];
8871
9001
  const errors = [];
8872
9002
  const computedTokens = countTokens(card);
8873
- const canonicalContent = getCanonicalContent(card);
8874
- const computedHash = computeHash(canonicalContent);
9003
+ const canonicalContentV1 = getCanonicalContentV1(card);
9004
+ const canonicalContentV2 = getCanonicalContentV2(card);
9005
+ const computedHashV1 = computeHash(canonicalContentV1);
9006
+ const computedHashV2 = computeHash(canonicalContentV2);
8875
9007
  const entries = card.data.character_book?.entries || [];
8876
9008
  const computedHasLorebook = entries.length > 0;
8877
9009
  const computedLorebookCount = entries.length;
8878
9010
  const authoritative = {
8879
9011
  name: card.data.name,
8880
9012
  tokens: computedTokens,
8881
- contentHash: computedHash,
9013
+ contentHash: computedHashV1,
9014
+ contentHashV2: computedHashV2,
8882
9015
  hasLorebook: computedHasLorebook,
8883
9016
  lorebookEntriesCount: computedLorebookCount
8884
9017
  };
@@ -8890,11 +9023,13 @@ function validateClientMetadataSync(clientMetadata, parseResult, options) {
8890
9023
  withinTolerance: false
8891
9024
  });
8892
9025
  }
8893
- if (clientMetadata.contentHash !== computedHash) {
9026
+ const matchesV1 = clientMetadata.contentHash === computedHashV1;
9027
+ const matchesV2 = clientMetadata.contentHash === computedHashV2;
9028
+ if (!matchesV1 && !matchesV2) {
8894
9029
  discrepancies.push({
8895
9030
  field: "contentHash",
8896
9031
  clientValue: clientMetadata.contentHash,
8897
- computedValue: computedHash,
9032
+ computedValue: computedHashV1,
8898
9033
  withinTolerance: false
8899
9034
  });
8900
9035
  if (allowHashMismatch) {
@@ -8902,6 +9037,10 @@ function validateClientMetadataSync(clientMetadata, parseResult, options) {
8902
9037
  } else {
8903
9038
  errors.push("Content hash mismatch");
8904
9039
  }
9040
+ } else if (matchesV1 && !matchesV2) {
9041
+ warnings.push(
9042
+ "Client contentHash matches legacy v1 canonicalization. Prefer authoritative.contentHashV2 for new storage."
9043
+ );
8905
9044
  }
8906
9045
  const tokenFields = [
8907
9046
  "description",
@@ -8970,6 +9109,7 @@ function validateClientMetadataSync(clientMetadata, parseResult, options) {
8970
9109
  }
8971
9110
  export {
8972
9111
  computeContentHash,
9112
+ computeContentHashV2,
8973
9113
  detectFormat,
8974
9114
  getContainerFormat,
8975
9115
  mightBeCard,