@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/index.cjs CHANGED
@@ -560,6 +560,58 @@ var import_zod = require("zod");
560
560
  var import_zod2 = require("zod");
561
561
  var import_zod3 = require("zod");
562
562
  var import_zod4 = require("zod");
563
+ function preprocessTimestamp(val) {
564
+ if (val === null || val === void 0) return void 0;
565
+ let num;
566
+ if (typeof val === "number") {
567
+ num = val;
568
+ } else if (typeof val === "string") {
569
+ const trimmed = val.trim();
570
+ if (!trimmed) return void 0;
571
+ const parsed = Number(trimmed);
572
+ if (!isNaN(parsed)) {
573
+ num = parsed;
574
+ } else {
575
+ const date = new Date(trimmed);
576
+ if (isNaN(date.getTime())) return void 0;
577
+ num = Math.floor(date.getTime() / 1e3);
578
+ }
579
+ } else {
580
+ return void 0;
581
+ }
582
+ if (num > 1e10) {
583
+ num = Math.floor(num / 1e3);
584
+ }
585
+ if (num < 0) return void 0;
586
+ return num;
587
+ }
588
+ function preprocessNumeric(val) {
589
+ if (val === null || val === void 0) return void 0;
590
+ if (typeof val === "number") {
591
+ return isNaN(val) ? void 0 : val;
592
+ }
593
+ if (typeof val === "string") {
594
+ const trimmed = val.trim();
595
+ if (!trimmed) return void 0;
596
+ const parsed = Number(trimmed);
597
+ return isNaN(parsed) ? void 0 : parsed;
598
+ }
599
+ return void 0;
600
+ }
601
+ var KNOWN_ASSET_TYPES = /* @__PURE__ */ new Set([
602
+ "icon",
603
+ "background",
604
+ "emotion",
605
+ "user_icon",
606
+ "sound",
607
+ "video",
608
+ "custom",
609
+ "x-risu-asset"
610
+ ]);
611
+ function preprocessAssetType(val) {
612
+ if (typeof val !== "string") return "custom";
613
+ return KNOWN_ASSET_TYPES.has(val) ? val : "custom";
614
+ }
563
615
  var ISO8601Schema = import_zod.z.string().datetime();
564
616
  var UUIDSchema = import_zod.z.string().uuid();
565
617
  var SpecSchema = import_zod.z.enum(["v2", "v3"]);
@@ -582,16 +634,19 @@ var SourceFormatSchema = import_zod.z.enum([
582
634
  // VoxPkg format
583
635
  ]);
584
636
  var OriginalShapeSchema = import_zod.z.enum(["wrapped", "unwrapped", "legacy"]);
585
- var AssetTypeSchema = import_zod.z.enum([
586
- "icon",
587
- "background",
588
- "emotion",
589
- "user_icon",
590
- "sound",
591
- "video",
592
- "custom",
593
- "x-risu-asset"
594
- ]);
637
+ var AssetTypeSchema = import_zod.z.preprocess(
638
+ preprocessAssetType,
639
+ import_zod.z.enum([
640
+ "icon",
641
+ "background",
642
+ "emotion",
643
+ "user_icon",
644
+ "sound",
645
+ "video",
646
+ "custom",
647
+ "x-risu-asset"
648
+ ])
649
+ );
595
650
  var AssetDescriptorSchema = import_zod.z.object({
596
651
  type: AssetTypeSchema,
597
652
  uri: import_zod.z.string(),
@@ -625,8 +680,8 @@ var CCv2LorebookEntrySchema = import_zod2.z.object({
625
680
  var CCv2CharacterBookSchema = import_zod2.z.object({
626
681
  name: import_zod2.z.string().optional(),
627
682
  description: import_zod2.z.string().optional(),
628
- scan_depth: import_zod2.z.number().int().nonnegative().optional(),
629
- token_budget: import_zod2.z.number().int().nonnegative().optional(),
683
+ scan_depth: import_zod2.z.preprocess(preprocessNumeric, import_zod2.z.number().int().nonnegative().optional()),
684
+ token_budget: import_zod2.z.preprocess(preprocessNumeric, import_zod2.z.number().int().nonnegative().optional()),
630
685
  recursive_scanning: import_zod2.z.boolean().optional(),
631
686
  extensions: import_zod2.z.record(import_zod2.z.unknown()).optional(),
632
687
  entries: import_zod2.z.array(CCv2LorebookEntrySchema)
@@ -700,8 +755,8 @@ var CCv3LorebookEntrySchema = import_zod3.z.object({
700
755
  var CCv3CharacterBookSchema = import_zod3.z.object({
701
756
  name: import_zod3.z.string().optional(),
702
757
  description: import_zod3.z.string().optional(),
703
- scan_depth: import_zod3.z.number().int().nonnegative().optional(),
704
- token_budget: import_zod3.z.number().int().nonnegative().optional(),
758
+ scan_depth: import_zod3.z.preprocess(preprocessNumeric, import_zod3.z.number().int().nonnegative().optional()),
759
+ token_budget: import_zod3.z.preprocess(preprocessNumeric, import_zod3.z.number().int().nonnegative().optional()),
705
760
  recursive_scanning: import_zod3.z.boolean().optional(),
706
761
  extensions: import_zod3.z.record(import_zod3.z.unknown()).optional(),
707
762
  entries: import_zod3.z.array(CCv3LorebookEntrySchema)
@@ -733,10 +788,9 @@ var CCv3DataInnerSchema = import_zod3.z.object({
733
788
  nickname: import_zod3.z.string().optional(),
734
789
  creator_notes_multilingual: import_zod3.z.record(import_zod3.z.string()).optional(),
735
790
  source: import_zod3.z.array(import_zod3.z.string()).optional(),
736
- creation_date: import_zod3.z.number().int().nonnegative().optional(),
737
- // Unix timestamp in seconds
738
- modification_date: import_zod3.z.number().int().nonnegative().optional()
739
- // Unix timestamp in seconds
791
+ // Unix timestamps - preprocess to handle ISO strings, numeric strings, milliseconds
792
+ creation_date: import_zod3.z.preprocess(preprocessTimestamp, import_zod3.z.number().int().nonnegative().optional()),
793
+ modification_date: import_zod3.z.preprocess(preprocessTimestamp, import_zod3.z.number().int().nonnegative().optional())
740
794
  });
741
795
  var CCv3DataSchema = import_zod3.z.object({
742
796
  spec: import_zod3.z.literal("chara_card_v3"),
@@ -1363,36 +1417,6 @@ var SAFE_ASSET_TYPES = /* @__PURE__ */ new Set([
1363
1417
  "data",
1364
1418
  "unknown"
1365
1419
  ]);
1366
- var SAFE_EXTENSIONS = /* @__PURE__ */ new Set([
1367
- // Images
1368
- "png",
1369
- "jpg",
1370
- "jpeg",
1371
- "webp",
1372
- "gif",
1373
- "avif",
1374
- "svg",
1375
- "bmp",
1376
- "ico",
1377
- // Audio
1378
- "mp3",
1379
- "wav",
1380
- "ogg",
1381
- "flac",
1382
- "m4a",
1383
- "aac",
1384
- "opus",
1385
- // Video
1386
- "mp4",
1387
- "webm",
1388
- "avi",
1389
- "mov",
1390
- "mkv",
1391
- // Data
1392
- "json",
1393
- "txt",
1394
- "bin"
1395
- ]);
1396
1420
  function getCharxCategory(mimetype) {
1397
1421
  if (mimetype.startsWith("image/")) return "images";
1398
1422
  if (mimetype.startsWith("audio/")) return "audio";
@@ -1408,11 +1432,20 @@ function sanitizeAssetType(type) {
1408
1432
  return sanitized || "custom";
1409
1433
  }
1410
1434
  function sanitizeExtension(ext) {
1411
- const normalized = ext.replace(/^\./, "").toLowerCase().replace(/[^a-z0-9]/g, "");
1412
- if (SAFE_EXTENSIONS.has(normalized)) {
1413
- return normalized;
1435
+ const normalized = ext.trim().replace(/^\./, "").toLowerCase();
1436
+ if (!normalized) {
1437
+ throw new Error("Invalid asset extension: empty extension");
1438
+ }
1439
+ if (normalized.length > 64) {
1440
+ throw new Error(`Invalid asset extension: too long (${normalized.length} chars)`);
1441
+ }
1442
+ if (normalized.includes("/") || normalized.includes("\\") || normalized.includes("\0")) {
1443
+ throw new Error("Invalid asset extension: path separators are not allowed");
1414
1444
  }
1415
- return "bin";
1445
+ if (!/^[a-z0-9][a-z0-9._-]*$/.test(normalized)) {
1446
+ throw new Error(`Invalid asset extension: "${ext}"`);
1447
+ }
1448
+ return normalized;
1416
1449
  }
1417
1450
  function sanitizeName(name, ext) {
1418
1451
  let safeName = name;
@@ -8194,6 +8227,22 @@ function sanitizeName2(name, ext) {
8194
8227
  if (!safeName) safeName = "asset";
8195
8228
  return safeName;
8196
8229
  }
8230
+ function sanitizeExtension2(ext) {
8231
+ const normalized = ext.trim().replace(/^\./, "").toLowerCase();
8232
+ if (!normalized) {
8233
+ throw new Error("Invalid asset extension: empty extension");
8234
+ }
8235
+ if (normalized.length > 64) {
8236
+ throw new Error(`Invalid asset extension: too long (${normalized.length} chars)`);
8237
+ }
8238
+ if (normalized.includes("/") || normalized.includes("\\") || normalized.includes("\0")) {
8239
+ throw new Error("Invalid asset extension: path separators are not allowed");
8240
+ }
8241
+ if (!/^[a-z0-9][a-z0-9._-]*$/.test(normalized)) {
8242
+ throw new Error(`Invalid asset extension: "${ext}"`);
8243
+ }
8244
+ return normalized;
8245
+ }
8197
8246
  function writeVoxta(card, assets, options = {}) {
8198
8247
  const { compressionLevel = 6, includePackageJson = false } = options;
8199
8248
  const cardData = card.data;
@@ -8282,8 +8331,9 @@ function writeVoxta(card, assets, options = {}) {
8282
8331
  let assetCount = 0;
8283
8332
  let mainThumbnail;
8284
8333
  for (const asset of assets) {
8285
- const safeName = sanitizeName2(asset.name, asset.ext);
8286
- const finalFilename = `${safeName}.${asset.ext}`;
8334
+ const safeExt = sanitizeExtension2(asset.ext);
8335
+ const safeName = sanitizeName2(asset.name, safeExt);
8336
+ const finalFilename = `${safeName}.${safeExt}`;
8287
8337
  let voxtaPath = "";
8288
8338
  const tags = asset.tags || [];
8289
8339
  const isMainIcon = asset.type === "icon" && (tags.includes("portrait-override") || asset.name === "main" || asset.isMain);