@character-foundry/character-foundry 0.4.2 → 0.4.3-dev.1766103111

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 (57) hide show
  1. package/dist/charx.cjs +87 -54
  2. package/dist/charx.cjs.map +1 -1
  3. package/dist/charx.d.cts +40 -31
  4. package/dist/charx.d.ts +40 -31
  5. package/dist/charx.js +87 -54
  6. package/dist/charx.js.map +1 -1
  7. package/dist/exporter.cjs +106 -56
  8. package/dist/exporter.cjs.map +1 -1
  9. package/dist/exporter.d.cts +37 -28
  10. package/dist/exporter.d.ts +37 -28
  11. package/dist/exporter.js +106 -56
  12. package/dist/exporter.js.map +1 -1
  13. package/dist/federation.cjs +104 -36
  14. package/dist/federation.cjs.map +1 -1
  15. package/dist/federation.d.cts +72 -28
  16. package/dist/federation.d.ts +72 -28
  17. package/dist/federation.js +104 -36
  18. package/dist/federation.js.map +1 -1
  19. package/dist/index.cjs +106 -56
  20. package/dist/index.cjs.map +1 -1
  21. package/dist/index.d.cts +71 -50
  22. package/dist/index.d.ts +71 -50
  23. package/dist/index.js +106 -56
  24. package/dist/index.js.map +1 -1
  25. package/dist/loader.cjs +173 -33
  26. package/dist/loader.cjs.map +1 -1
  27. package/dist/loader.d.cts +65 -37
  28. package/dist/loader.d.ts +65 -37
  29. package/dist/loader.js +173 -33
  30. package/dist/loader.js.map +1 -1
  31. package/dist/lorebook.d.cts +57 -40
  32. package/dist/lorebook.d.ts +57 -40
  33. package/dist/normalizer.cjs +74 -20
  34. package/dist/normalizer.cjs.map +1 -1
  35. package/dist/normalizer.d.cts +97 -67
  36. package/dist/normalizer.d.ts +97 -67
  37. package/dist/normalizer.js +74 -20
  38. package/dist/normalizer.js.map +1 -1
  39. package/dist/png.cjs +74 -20
  40. package/dist/png.cjs.map +1 -1
  41. package/dist/png.d.cts +57 -41
  42. package/dist/png.d.ts +57 -41
  43. package/dist/png.js +74 -20
  44. package/dist/png.js.map +1 -1
  45. package/dist/schemas.cjs +82 -25
  46. package/dist/schemas.cjs.map +1 -1
  47. package/dist/schemas.d.cts +181 -115
  48. package/dist/schemas.d.ts +181 -115
  49. package/dist/schemas.js +82 -25
  50. package/dist/schemas.js.map +1 -1
  51. package/dist/voxta.cjs +93 -22
  52. package/dist/voxta.cjs.map +1 -1
  53. package/dist/voxta.d.cts +51 -37
  54. package/dist/voxta.d.ts +51 -37
  55. package/dist/voxta.js +93 -22
  56. package/dist/voxta.js.map +1 -1
  57. package/package.json +5 -5
package/dist/exporter.cjs CHANGED
@@ -216,6 +216,58 @@ var import_zod = require("zod");
216
216
  var import_zod2 = require("zod");
217
217
  var import_zod3 = require("zod");
218
218
  var import_zod4 = require("zod");
219
+ function preprocessTimestamp(val) {
220
+ if (val === null || val === void 0) return void 0;
221
+ let num;
222
+ if (typeof val === "number") {
223
+ num = val;
224
+ } else if (typeof val === "string") {
225
+ const trimmed = val.trim();
226
+ if (!trimmed) return void 0;
227
+ const parsed = Number(trimmed);
228
+ if (!isNaN(parsed)) {
229
+ num = parsed;
230
+ } else {
231
+ const date = new Date(trimmed);
232
+ if (isNaN(date.getTime())) return void 0;
233
+ num = Math.floor(date.getTime() / 1e3);
234
+ }
235
+ } else {
236
+ return void 0;
237
+ }
238
+ if (num > 1e10) {
239
+ num = Math.floor(num / 1e3);
240
+ }
241
+ if (num < 0) return void 0;
242
+ return num;
243
+ }
244
+ function preprocessNumeric(val) {
245
+ if (val === null || val === void 0) return void 0;
246
+ if (typeof val === "number") {
247
+ return isNaN(val) ? void 0 : val;
248
+ }
249
+ if (typeof val === "string") {
250
+ const trimmed = val.trim();
251
+ if (!trimmed) return void 0;
252
+ const parsed = Number(trimmed);
253
+ return isNaN(parsed) ? void 0 : parsed;
254
+ }
255
+ return void 0;
256
+ }
257
+ var KNOWN_ASSET_TYPES = /* @__PURE__ */ new Set([
258
+ "icon",
259
+ "background",
260
+ "emotion",
261
+ "user_icon",
262
+ "sound",
263
+ "video",
264
+ "custom",
265
+ "x-risu-asset"
266
+ ]);
267
+ function preprocessAssetType(val) {
268
+ if (typeof val !== "string") return "custom";
269
+ return KNOWN_ASSET_TYPES.has(val) ? val : "custom";
270
+ }
219
271
  var ISO8601Schema = import_zod.z.string().datetime();
220
272
  var UUIDSchema = import_zod.z.string().uuid();
221
273
  var SpecSchema = import_zod.z.enum(["v2", "v3"]);
@@ -238,16 +290,19 @@ var SourceFormatSchema = import_zod.z.enum([
238
290
  // VoxPkg format
239
291
  ]);
240
292
  var OriginalShapeSchema = import_zod.z.enum(["wrapped", "unwrapped", "legacy"]);
241
- var AssetTypeSchema = import_zod.z.enum([
242
- "icon",
243
- "background",
244
- "emotion",
245
- "user_icon",
246
- "sound",
247
- "video",
248
- "custom",
249
- "x-risu-asset"
250
- ]);
293
+ var AssetTypeSchema = import_zod.z.preprocess(
294
+ preprocessAssetType,
295
+ import_zod.z.enum([
296
+ "icon",
297
+ "background",
298
+ "emotion",
299
+ "user_icon",
300
+ "sound",
301
+ "video",
302
+ "custom",
303
+ "x-risu-asset"
304
+ ])
305
+ );
251
306
  var AssetDescriptorSchema = import_zod.z.object({
252
307
  type: AssetTypeSchema,
253
308
  uri: import_zod.z.string(),
@@ -276,13 +331,13 @@ var CCv2LorebookEntrySchema = import_zod2.z.object({
276
331
  selective: import_zod2.z.boolean().nullable().optional(),
277
332
  secondary_keys: import_zod2.z.array(import_zod2.z.string()).nullable().optional(),
278
333
  constant: import_zod2.z.boolean().nullable().optional(),
279
- position: import_zod2.z.union([import_zod2.z.enum(["before_char", "after_char"]), import_zod2.z.number().int(), import_zod2.z.literal("")]).nullable().optional()
334
+ position: import_zod2.z.union([import_zod2.z.enum(["before_char", "after_char", "in_chat"]), import_zod2.z.number().int(), import_zod2.z.literal("")]).nullable().optional()
280
335
  }).passthrough();
281
336
  var CCv2CharacterBookSchema = import_zod2.z.object({
282
337
  name: import_zod2.z.string().optional(),
283
338
  description: import_zod2.z.string().optional(),
284
- scan_depth: import_zod2.z.number().int().nonnegative().optional(),
285
- token_budget: import_zod2.z.number().int().nonnegative().optional(),
339
+ scan_depth: import_zod2.z.preprocess(preprocessNumeric, import_zod2.z.number().int().nonnegative().optional()),
340
+ token_budget: import_zod2.z.preprocess(preprocessNumeric, import_zod2.z.number().int().nonnegative().optional()),
286
341
  recursive_scanning: import_zod2.z.boolean().optional(),
287
342
  extensions: import_zod2.z.record(import_zod2.z.unknown()).optional(),
288
343
  entries: import_zod2.z.array(CCv2LorebookEntrySchema)
@@ -329,7 +384,7 @@ var CCv3LorebookEntrySchema = import_zod3.z.object({
329
384
  selective: import_zod3.z.boolean().nullable().optional(),
330
385
  secondary_keys: import_zod3.z.array(import_zod3.z.string()).nullable().optional(),
331
386
  constant: import_zod3.z.boolean().nullable().optional(),
332
- position: import_zod3.z.union([import_zod3.z.enum(["before_char", "after_char"]), import_zod3.z.number().int(), import_zod3.z.literal("")]).nullable().optional(),
387
+ position: import_zod3.z.union([import_zod3.z.enum(["before_char", "after_char", "in_chat"]), import_zod3.z.number().int(), import_zod3.z.literal("")]).nullable().optional(),
333
388
  extensions: import_zod3.z.record(import_zod3.z.unknown()).optional(),
334
389
  // v3 specific - also lenient with types since SillyTavern uses numbers for enums
335
390
  automation_id: import_zod3.z.string().optional(),
@@ -345,8 +400,8 @@ var CCv3LorebookEntrySchema = import_zod3.z.object({
345
400
  var CCv3CharacterBookSchema = import_zod3.z.object({
346
401
  name: import_zod3.z.string().optional(),
347
402
  description: import_zod3.z.string().optional(),
348
- scan_depth: import_zod3.z.number().int().nonnegative().optional(),
349
- token_budget: import_zod3.z.number().int().nonnegative().optional(),
403
+ scan_depth: import_zod3.z.preprocess(preprocessNumeric, import_zod3.z.number().int().nonnegative().optional()),
404
+ token_budget: import_zod3.z.preprocess(preprocessNumeric, import_zod3.z.number().int().nonnegative().optional()),
350
405
  recursive_scanning: import_zod3.z.boolean().optional(),
351
406
  extensions: import_zod3.z.record(import_zod3.z.unknown()).optional(),
352
407
  entries: import_zod3.z.array(CCv3LorebookEntrySchema)
@@ -378,10 +433,9 @@ var CCv3DataInnerSchema = import_zod3.z.object({
378
433
  nickname: import_zod3.z.string().optional(),
379
434
  creator_notes_multilingual: import_zod3.z.record(import_zod3.z.string()).optional(),
380
435
  source: import_zod3.z.array(import_zod3.z.string()).optional(),
381
- creation_date: import_zod3.z.number().int().nonnegative().optional(),
382
- // Unix timestamp in seconds
383
- modification_date: import_zod3.z.number().int().nonnegative().optional()
384
- // Unix timestamp in seconds
436
+ // Unix timestamps - preprocess to handle ISO strings, numeric strings, milliseconds
437
+ creation_date: import_zod3.z.preprocess(preprocessTimestamp, import_zod3.z.number().int().nonnegative().optional()),
438
+ modification_date: import_zod3.z.preprocess(preprocessTimestamp, import_zod3.z.number().int().nonnegative().optional())
385
439
  });
386
440
  var CCv3DataSchema = import_zod3.z.object({
387
441
  spec: import_zod3.z.literal("chara_card_v3"),
@@ -546,36 +600,6 @@ var SAFE_ASSET_TYPES = /* @__PURE__ */ new Set([
546
600
  "data",
547
601
  "unknown"
548
602
  ]);
549
- var SAFE_EXTENSIONS = /* @__PURE__ */ new Set([
550
- // Images
551
- "png",
552
- "jpg",
553
- "jpeg",
554
- "webp",
555
- "gif",
556
- "avif",
557
- "svg",
558
- "bmp",
559
- "ico",
560
- // Audio
561
- "mp3",
562
- "wav",
563
- "ogg",
564
- "flac",
565
- "m4a",
566
- "aac",
567
- "opus",
568
- // Video
569
- "mp4",
570
- "webm",
571
- "avi",
572
- "mov",
573
- "mkv",
574
- // Data
575
- "json",
576
- "txt",
577
- "bin"
578
- ]);
579
603
  function getCharxCategory(mimetype) {
580
604
  if (mimetype.startsWith("image/")) return "images";
581
605
  if (mimetype.startsWith("audio/")) return "audio";
@@ -591,11 +615,20 @@ function sanitizeAssetType(type) {
591
615
  return sanitized || "custom";
592
616
  }
593
617
  function sanitizeExtension(ext) {
594
- const normalized = ext.replace(/^\./, "").toLowerCase().replace(/[^a-z0-9]/g, "");
595
- if (SAFE_EXTENSIONS.has(normalized)) {
596
- return normalized;
618
+ const normalized = ext.trim().replace(/^\./, "").toLowerCase();
619
+ if (!normalized) {
620
+ throw new Error("Invalid asset extension: empty extension");
621
+ }
622
+ if (normalized.length > 64) {
623
+ throw new Error(`Invalid asset extension: too long (${normalized.length} chars)`);
624
+ }
625
+ if (normalized.includes("/") || normalized.includes("\\") || normalized.includes("\0")) {
626
+ throw new Error("Invalid asset extension: path separators are not allowed");
597
627
  }
598
- return "bin";
628
+ if (!/^[a-z0-9][a-z0-9._-]*$/.test(normalized)) {
629
+ throw new Error(`Invalid asset extension: "${ext}"`);
630
+ }
631
+ return normalized;
599
632
  }
600
633
  function sanitizeName(name, ext) {
601
634
  let safeName = name;
@@ -7046,6 +7079,22 @@ function sanitizeName2(name, ext) {
7046
7079
  if (!safeName) safeName = "asset";
7047
7080
  return safeName;
7048
7081
  }
7082
+ function sanitizeExtension2(ext) {
7083
+ const normalized = ext.trim().replace(/^\./, "").toLowerCase();
7084
+ if (!normalized) {
7085
+ throw new Error("Invalid asset extension: empty extension");
7086
+ }
7087
+ if (normalized.length > 64) {
7088
+ throw new Error(`Invalid asset extension: too long (${normalized.length} chars)`);
7089
+ }
7090
+ if (normalized.includes("/") || normalized.includes("\\") || normalized.includes("\0")) {
7091
+ throw new Error("Invalid asset extension: path separators are not allowed");
7092
+ }
7093
+ if (!/^[a-z0-9][a-z0-9._-]*$/.test(normalized)) {
7094
+ throw new Error(`Invalid asset extension: "${ext}"`);
7095
+ }
7096
+ return normalized;
7097
+ }
7049
7098
  function writeVoxta(card, assets, options = {}) {
7050
7099
  const { compressionLevel = 6, includePackageJson = false } = options;
7051
7100
  const cardData = card.data;
@@ -7134,8 +7183,9 @@ function writeVoxta(card, assets, options = {}) {
7134
7183
  let assetCount = 0;
7135
7184
  let mainThumbnail;
7136
7185
  for (const asset of assets) {
7137
- const safeName = sanitizeName2(asset.name, asset.ext);
7138
- const finalFilename = `${safeName}.${asset.ext}`;
7186
+ const safeExt = sanitizeExtension2(asset.ext);
7187
+ const safeName = sanitizeName2(asset.name, safeExt);
7188
+ const finalFilename = `${safeName}.${safeExt}`;
7139
7189
  let voxtaPath = "";
7140
7190
  const tags = asset.tags || [];
7141
7191
  const isMainIcon = asset.type === "icon" && (tags.includes("portrait-override") || asset.name === "main" || asset.isMain);