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

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 +52 -85
  2. package/dist/charx.cjs.map +1 -1
  3. package/dist/charx.d.cts +22 -22
  4. package/dist/charx.d.ts +22 -22
  5. package/dist/charx.js +52 -85
  6. package/dist/charx.js.map +1 -1
  7. package/dist/exporter.cjs +54 -104
  8. package/dist/exporter.cjs.map +1 -1
  9. package/dist/exporter.d.cts +19 -19
  10. package/dist/exporter.d.ts +19 -19
  11. package/dist/exporter.js +54 -104
  12. package/dist/exporter.js.map +1 -1
  13. package/dist/federation.cjs +36 -104
  14. package/dist/federation.cjs.map +1 -1
  15. package/dist/federation.d.cts +19 -54
  16. package/dist/federation.d.ts +19 -54
  17. package/dist/federation.js +36 -104
  18. package/dist/federation.js.map +1 -1
  19. package/dist/index.cjs +54 -104
  20. package/dist/index.cjs.map +1 -1
  21. package/dist/index.d.cts +29 -29
  22. package/dist/index.d.ts +29 -29
  23. package/dist/index.js +54 -104
  24. package/dist/index.js.map +1 -1
  25. package/dist/loader.cjs +31 -171
  26. package/dist/loader.cjs.map +1 -1
  27. package/dist/loader.d.cts +23 -37
  28. package/dist/loader.d.ts +23 -37
  29. package/dist/loader.js +31 -171
  30. package/dist/loader.js.map +1 -1
  31. package/dist/lorebook.d.cts +23 -23
  32. package/dist/lorebook.d.ts +23 -23
  33. package/dist/normalizer.cjs +18 -72
  34. package/dist/normalizer.cjs.map +1 -1
  35. package/dist/normalizer.d.cts +37 -37
  36. package/dist/normalizer.d.ts +37 -37
  37. package/dist/normalizer.js +18 -72
  38. package/dist/normalizer.js.map +1 -1
  39. package/dist/png.cjs +18 -72
  40. package/dist/png.cjs.map +1 -1
  41. package/dist/png.d.cts +25 -25
  42. package/dist/png.d.ts +25 -25
  43. package/dist/png.js +18 -72
  44. package/dist/png.js.map +1 -1
  45. package/dist/schemas.cjs +23 -80
  46. package/dist/schemas.cjs.map +1 -1
  47. package/dist/schemas.d.cts +67 -85
  48. package/dist/schemas.d.ts +67 -85
  49. package/dist/schemas.js +23 -80
  50. package/dist/schemas.js.map +1 -1
  51. package/dist/voxta.cjs +20 -91
  52. package/dist/voxta.cjs.map +1 -1
  53. package/dist/voxta.d.cts +23 -23
  54. package/dist/voxta.d.ts +23 -23
  55. package/dist/voxta.js +20 -91
  56. package/dist/voxta.js.map +1 -1
  57. package/package.json +5 -5
@@ -31,8 +31,8 @@ declare const CCv3DataSchema: z.ZodObject<{
31
31
  character_book: z.ZodNullable<z.ZodOptional<z.ZodObject<{
32
32
  name: z.ZodOptional<z.ZodString>;
33
33
  description: z.ZodOptional<z.ZodString>;
34
- scan_depth: z.ZodEffects<z.ZodOptional<z.ZodNumber>, number | undefined, unknown>;
35
- token_budget: z.ZodEffects<z.ZodOptional<z.ZodNumber>, number | undefined, unknown>;
34
+ scan_depth: z.ZodOptional<z.ZodNumber>;
35
+ token_budget: z.ZodOptional<z.ZodNumber>;
36
36
  recursive_scanning: z.ZodOptional<z.ZodBoolean>;
37
37
  extensions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
38
38
  entries: z.ZodArray<z.ZodObject<{
@@ -268,14 +268,14 @@ declare const CCv3DataSchema: z.ZodObject<{
268
268
  }, z.ZodTypeAny, "passthrough">[];
269
269
  name?: string | undefined;
270
270
  description?: string | undefined;
271
- scan_depth?: unknown;
272
- token_budget?: unknown;
271
+ scan_depth?: number | undefined;
272
+ token_budget?: number | undefined;
273
273
  recursive_scanning?: boolean | undefined;
274
274
  extensions?: Record<string, unknown> | undefined;
275
275
  }>>>;
276
276
  extensions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
277
277
  assets: z.ZodOptional<z.ZodArray<z.ZodObject<{
278
- type: z.ZodEffects<z.ZodEnum<[
278
+ type: z.ZodEnum<[
279
279
  "icon",
280
280
  "background",
281
281
  "emotion",
@@ -284,7 +284,7 @@ declare const CCv3DataSchema: z.ZodObject<{
284
284
  "video",
285
285
  "custom",
286
286
  "x-risu-asset"
287
- ]>, "custom" | "icon" | "background" | "emotion" | "user_icon" | "sound" | "video" | "x-risu-asset", unknown>;
287
+ ]>;
288
288
  uri: z.ZodString;
289
289
  name: z.ZodString;
290
290
  ext: z.ZodString;
@@ -295,15 +295,15 @@ declare const CCv3DataSchema: z.ZodObject<{
295
295
  ext: string;
296
296
  }, {
297
297
  name: string;
298
+ type: "custom" | "icon" | "background" | "emotion" | "user_icon" | "sound" | "video" | "x-risu-asset";
298
299
  uri: string;
299
300
  ext: string;
300
- type?: unknown;
301
301
  }>, "many">>;
302
302
  nickname: z.ZodOptional<z.ZodString>;
303
303
  creator_notes_multilingual: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
304
304
  source: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
305
- creation_date: z.ZodEffects<z.ZodOptional<z.ZodNumber>, number | undefined, unknown>;
306
- modification_date: z.ZodEffects<z.ZodOptional<z.ZodNumber>, number | undefined, unknown>;
305
+ creation_date: z.ZodOptional<z.ZodNumber>;
306
+ modification_date: z.ZodOptional<z.ZodNumber>;
307
307
  }, "strip", z.ZodTypeAny, {
308
308
  name: string;
309
309
  description: string;
@@ -444,8 +444,8 @@ declare const CCv3DataSchema: z.ZodObject<{
444
444
  }, z.ZodTypeAny, "passthrough">[];
445
445
  name?: string | undefined;
446
446
  description?: string | undefined;
447
- scan_depth?: unknown;
448
- token_budget?: unknown;
447
+ scan_depth?: number | undefined;
448
+ token_budget?: number | undefined;
449
449
  recursive_scanning?: boolean | undefined;
450
450
  extensions?: Record<string, unknown> | undefined;
451
451
  } | null | undefined;
@@ -455,15 +455,15 @@ declare const CCv3DataSchema: z.ZodObject<{
455
455
  group_only_greetings?: string[] | undefined;
456
456
  assets?: {
457
457
  name: string;
458
+ type: "custom" | "icon" | "background" | "emotion" | "user_icon" | "sound" | "video" | "x-risu-asset";
458
459
  uri: string;
459
460
  ext: string;
460
- type?: unknown;
461
461
  }[] | undefined;
462
462
  nickname?: string | undefined;
463
463
  creator_notes_multilingual?: Record<string, string> | undefined;
464
464
  source?: string[] | undefined;
465
- creation_date?: unknown;
466
- modification_date?: unknown;
465
+ creation_date?: number | undefined;
466
+ modification_date?: number | undefined;
467
467
  }>;
468
468
  }, "strip", z.ZodTypeAny, {
469
469
  data: {
@@ -610,8 +610,8 @@ declare const CCv3DataSchema: z.ZodObject<{
610
610
  }, z.ZodTypeAny, "passthrough">[];
611
611
  name?: string | undefined;
612
612
  description?: string | undefined;
613
- scan_depth?: unknown;
614
- token_budget?: unknown;
613
+ scan_depth?: number | undefined;
614
+ token_budget?: number | undefined;
615
615
  recursive_scanning?: boolean | undefined;
616
616
  extensions?: Record<string, unknown> | undefined;
617
617
  } | null | undefined;
@@ -621,15 +621,15 @@ declare const CCv3DataSchema: z.ZodObject<{
621
621
  group_only_greetings?: string[] | undefined;
622
622
  assets?: {
623
623
  name: string;
624
+ type: "custom" | "icon" | "background" | "emotion" | "user_icon" | "sound" | "video" | "x-risu-asset";
624
625
  uri: string;
625
626
  ext: string;
626
- type?: unknown;
627
627
  }[] | undefined;
628
628
  nickname?: string | undefined;
629
629
  creator_notes_multilingual?: Record<string, string> | undefined;
630
630
  source?: string[] | undefined;
631
- creation_date?: unknown;
632
- modification_date?: unknown;
631
+ creation_date?: number | undefined;
632
+ modification_date?: number | undefined;
633
633
  };
634
634
  spec: "chara_card_v3";
635
635
  spec_version: "3.0";
@@ -31,8 +31,8 @@ declare const CCv3DataSchema: z.ZodObject<{
31
31
  character_book: z.ZodNullable<z.ZodOptional<z.ZodObject<{
32
32
  name: z.ZodOptional<z.ZodString>;
33
33
  description: z.ZodOptional<z.ZodString>;
34
- scan_depth: z.ZodEffects<z.ZodOptional<z.ZodNumber>, number | undefined, unknown>;
35
- token_budget: z.ZodEffects<z.ZodOptional<z.ZodNumber>, number | undefined, unknown>;
34
+ scan_depth: z.ZodOptional<z.ZodNumber>;
35
+ token_budget: z.ZodOptional<z.ZodNumber>;
36
36
  recursive_scanning: z.ZodOptional<z.ZodBoolean>;
37
37
  extensions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
38
38
  entries: z.ZodArray<z.ZodObject<{
@@ -268,14 +268,14 @@ declare const CCv3DataSchema: z.ZodObject<{
268
268
  }, z.ZodTypeAny, "passthrough">[];
269
269
  name?: string | undefined;
270
270
  description?: string | undefined;
271
- scan_depth?: unknown;
272
- token_budget?: unknown;
271
+ scan_depth?: number | undefined;
272
+ token_budget?: number | undefined;
273
273
  recursive_scanning?: boolean | undefined;
274
274
  extensions?: Record<string, unknown> | undefined;
275
275
  }>>>;
276
276
  extensions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
277
277
  assets: z.ZodOptional<z.ZodArray<z.ZodObject<{
278
- type: z.ZodEffects<z.ZodEnum<[
278
+ type: z.ZodEnum<[
279
279
  "icon",
280
280
  "background",
281
281
  "emotion",
@@ -284,7 +284,7 @@ declare const CCv3DataSchema: z.ZodObject<{
284
284
  "video",
285
285
  "custom",
286
286
  "x-risu-asset"
287
- ]>, "custom" | "icon" | "background" | "emotion" | "user_icon" | "sound" | "video" | "x-risu-asset", unknown>;
287
+ ]>;
288
288
  uri: z.ZodString;
289
289
  name: z.ZodString;
290
290
  ext: z.ZodString;
@@ -295,15 +295,15 @@ declare const CCv3DataSchema: z.ZodObject<{
295
295
  ext: string;
296
296
  }, {
297
297
  name: string;
298
+ type: "custom" | "icon" | "background" | "emotion" | "user_icon" | "sound" | "video" | "x-risu-asset";
298
299
  uri: string;
299
300
  ext: string;
300
- type?: unknown;
301
301
  }>, "many">>;
302
302
  nickname: z.ZodOptional<z.ZodString>;
303
303
  creator_notes_multilingual: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
304
304
  source: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
305
- creation_date: z.ZodEffects<z.ZodOptional<z.ZodNumber>, number | undefined, unknown>;
306
- modification_date: z.ZodEffects<z.ZodOptional<z.ZodNumber>, number | undefined, unknown>;
305
+ creation_date: z.ZodOptional<z.ZodNumber>;
306
+ modification_date: z.ZodOptional<z.ZodNumber>;
307
307
  }, "strip", z.ZodTypeAny, {
308
308
  name: string;
309
309
  description: string;
@@ -444,8 +444,8 @@ declare const CCv3DataSchema: z.ZodObject<{
444
444
  }, z.ZodTypeAny, "passthrough">[];
445
445
  name?: string | undefined;
446
446
  description?: string | undefined;
447
- scan_depth?: unknown;
448
- token_budget?: unknown;
447
+ scan_depth?: number | undefined;
448
+ token_budget?: number | undefined;
449
449
  recursive_scanning?: boolean | undefined;
450
450
  extensions?: Record<string, unknown> | undefined;
451
451
  } | null | undefined;
@@ -455,15 +455,15 @@ declare const CCv3DataSchema: z.ZodObject<{
455
455
  group_only_greetings?: string[] | undefined;
456
456
  assets?: {
457
457
  name: string;
458
+ type: "custom" | "icon" | "background" | "emotion" | "user_icon" | "sound" | "video" | "x-risu-asset";
458
459
  uri: string;
459
460
  ext: string;
460
- type?: unknown;
461
461
  }[] | undefined;
462
462
  nickname?: string | undefined;
463
463
  creator_notes_multilingual?: Record<string, string> | undefined;
464
464
  source?: string[] | undefined;
465
- creation_date?: unknown;
466
- modification_date?: unknown;
465
+ creation_date?: number | undefined;
466
+ modification_date?: number | undefined;
467
467
  }>;
468
468
  }, "strip", z.ZodTypeAny, {
469
469
  data: {
@@ -610,8 +610,8 @@ declare const CCv3DataSchema: z.ZodObject<{
610
610
  }, z.ZodTypeAny, "passthrough">[];
611
611
  name?: string | undefined;
612
612
  description?: string | undefined;
613
- scan_depth?: unknown;
614
- token_budget?: unknown;
613
+ scan_depth?: number | undefined;
614
+ token_budget?: number | undefined;
615
615
  recursive_scanning?: boolean | undefined;
616
616
  extensions?: Record<string, unknown> | undefined;
617
617
  } | null | undefined;
@@ -621,15 +621,15 @@ declare const CCv3DataSchema: z.ZodObject<{
621
621
  group_only_greetings?: string[] | undefined;
622
622
  assets?: {
623
623
  name: string;
624
+ type: "custom" | "icon" | "background" | "emotion" | "user_icon" | "sound" | "video" | "x-risu-asset";
624
625
  uri: string;
625
626
  ext: string;
626
- type?: unknown;
627
627
  }[] | undefined;
628
628
  nickname?: string | undefined;
629
629
  creator_notes_multilingual?: Record<string, string> | undefined;
630
630
  source?: string[] | undefined;
631
- creation_date?: unknown;
632
- modification_date?: unknown;
631
+ creation_date?: number | undefined;
632
+ modification_date?: number | undefined;
633
633
  };
634
634
  spec: "chara_card_v3";
635
635
  spec_version: "3.0";
package/dist/exporter.js CHANGED
@@ -184,58 +184,6 @@ import { z } from "zod";
184
184
  import { z as z2 } from "zod";
185
185
  import { z as z3 } from "zod";
186
186
  import "zod";
187
- function preprocessTimestamp(val) {
188
- if (val === null || val === void 0) return void 0;
189
- let num;
190
- if (typeof val === "number") {
191
- num = val;
192
- } else if (typeof val === "string") {
193
- const trimmed = val.trim();
194
- if (!trimmed) return void 0;
195
- const parsed = Number(trimmed);
196
- if (!isNaN(parsed)) {
197
- num = parsed;
198
- } else {
199
- const date = new Date(trimmed);
200
- if (isNaN(date.getTime())) return void 0;
201
- num = Math.floor(date.getTime() / 1e3);
202
- }
203
- } else {
204
- return void 0;
205
- }
206
- if (num > 1e10) {
207
- num = Math.floor(num / 1e3);
208
- }
209
- if (num < 0) return void 0;
210
- return num;
211
- }
212
- function preprocessNumeric(val) {
213
- if (val === null || val === void 0) return void 0;
214
- if (typeof val === "number") {
215
- return isNaN(val) ? void 0 : val;
216
- }
217
- if (typeof val === "string") {
218
- const trimmed = val.trim();
219
- if (!trimmed) return void 0;
220
- const parsed = Number(trimmed);
221
- return isNaN(parsed) ? void 0 : parsed;
222
- }
223
- return void 0;
224
- }
225
- var KNOWN_ASSET_TYPES = /* @__PURE__ */ new Set([
226
- "icon",
227
- "background",
228
- "emotion",
229
- "user_icon",
230
- "sound",
231
- "video",
232
- "custom",
233
- "x-risu-asset"
234
- ]);
235
- function preprocessAssetType(val) {
236
- if (typeof val !== "string") return "custom";
237
- return KNOWN_ASSET_TYPES.has(val) ? val : "custom";
238
- }
239
187
  var ISO8601Schema = z.string().datetime();
240
188
  var UUIDSchema = z.string().uuid();
241
189
  var SpecSchema = z.enum(["v2", "v3"]);
@@ -258,19 +206,16 @@ var SourceFormatSchema = z.enum([
258
206
  // VoxPkg format
259
207
  ]);
260
208
  var OriginalShapeSchema = z.enum(["wrapped", "unwrapped", "legacy"]);
261
- var AssetTypeSchema = z.preprocess(
262
- preprocessAssetType,
263
- z.enum([
264
- "icon",
265
- "background",
266
- "emotion",
267
- "user_icon",
268
- "sound",
269
- "video",
270
- "custom",
271
- "x-risu-asset"
272
- ])
273
- );
209
+ var AssetTypeSchema = z.enum([
210
+ "icon",
211
+ "background",
212
+ "emotion",
213
+ "user_icon",
214
+ "sound",
215
+ "video",
216
+ "custom",
217
+ "x-risu-asset"
218
+ ]);
274
219
  var AssetDescriptorSchema = z.object({
275
220
  type: AssetTypeSchema,
276
221
  uri: z.string(),
@@ -304,8 +249,8 @@ var CCv2LorebookEntrySchema = z2.object({
304
249
  var CCv2CharacterBookSchema = z2.object({
305
250
  name: z2.string().optional(),
306
251
  description: z2.string().optional(),
307
- scan_depth: z2.preprocess(preprocessNumeric, z2.number().int().nonnegative().optional()),
308
- token_budget: z2.preprocess(preprocessNumeric, z2.number().int().nonnegative().optional()),
252
+ scan_depth: z2.number().int().nonnegative().optional(),
253
+ token_budget: z2.number().int().nonnegative().optional(),
309
254
  recursive_scanning: z2.boolean().optional(),
310
255
  extensions: z2.record(z2.unknown()).optional(),
311
256
  entries: z2.array(CCv2LorebookEntrySchema)
@@ -368,8 +313,8 @@ var CCv3LorebookEntrySchema = z3.object({
368
313
  var CCv3CharacterBookSchema = z3.object({
369
314
  name: z3.string().optional(),
370
315
  description: z3.string().optional(),
371
- scan_depth: z3.preprocess(preprocessNumeric, z3.number().int().nonnegative().optional()),
372
- token_budget: z3.preprocess(preprocessNumeric, z3.number().int().nonnegative().optional()),
316
+ scan_depth: z3.number().int().nonnegative().optional(),
317
+ token_budget: z3.number().int().nonnegative().optional(),
373
318
  recursive_scanning: z3.boolean().optional(),
374
319
  extensions: z3.record(z3.unknown()).optional(),
375
320
  entries: z3.array(CCv3LorebookEntrySchema)
@@ -401,9 +346,10 @@ var CCv3DataInnerSchema = z3.object({
401
346
  nickname: z3.string().optional(),
402
347
  creator_notes_multilingual: z3.record(z3.string()).optional(),
403
348
  source: z3.array(z3.string()).optional(),
404
- // Unix timestamps - preprocess to handle ISO strings, numeric strings, milliseconds
405
- creation_date: z3.preprocess(preprocessTimestamp, z3.number().int().nonnegative().optional()),
406
- modification_date: z3.preprocess(preprocessTimestamp, z3.number().int().nonnegative().optional())
349
+ creation_date: z3.number().int().nonnegative().optional(),
350
+ // Unix timestamp in seconds
351
+ modification_date: z3.number().int().nonnegative().optional()
352
+ // Unix timestamp in seconds
407
353
  });
408
354
  var CCv3DataSchema = z3.object({
409
355
  spec: z3.literal("chara_card_v3"),
@@ -568,6 +514,36 @@ var SAFE_ASSET_TYPES = /* @__PURE__ */ new Set([
568
514
  "data",
569
515
  "unknown"
570
516
  ]);
517
+ var SAFE_EXTENSIONS = /* @__PURE__ */ new Set([
518
+ // Images
519
+ "png",
520
+ "jpg",
521
+ "jpeg",
522
+ "webp",
523
+ "gif",
524
+ "avif",
525
+ "svg",
526
+ "bmp",
527
+ "ico",
528
+ // Audio
529
+ "mp3",
530
+ "wav",
531
+ "ogg",
532
+ "flac",
533
+ "m4a",
534
+ "aac",
535
+ "opus",
536
+ // Video
537
+ "mp4",
538
+ "webm",
539
+ "avi",
540
+ "mov",
541
+ "mkv",
542
+ // Data
543
+ "json",
544
+ "txt",
545
+ "bin"
546
+ ]);
571
547
  function getCharxCategory(mimetype) {
572
548
  if (mimetype.startsWith("image/")) return "images";
573
549
  if (mimetype.startsWith("audio/")) return "audio";
@@ -583,20 +559,11 @@ function sanitizeAssetType(type) {
583
559
  return sanitized || "custom";
584
560
  }
585
561
  function sanitizeExtension(ext) {
586
- const normalized = ext.trim().replace(/^\./, "").toLowerCase();
587
- if (!normalized) {
588
- throw new Error("Invalid asset extension: empty extension");
589
- }
590
- if (normalized.length > 64) {
591
- throw new Error(`Invalid asset extension: too long (${normalized.length} chars)`);
592
- }
593
- if (normalized.includes("/") || normalized.includes("\\") || normalized.includes("\0")) {
594
- throw new Error("Invalid asset extension: path separators are not allowed");
595
- }
596
- if (!/^[a-z0-9][a-z0-9._-]*$/.test(normalized)) {
597
- throw new Error(`Invalid asset extension: "${ext}"`);
562
+ const normalized = ext.replace(/^\./, "").toLowerCase().replace(/[^a-z0-9]/g, "");
563
+ if (SAFE_EXTENSIONS.has(normalized)) {
564
+ return normalized;
598
565
  }
599
- return normalized;
566
+ return "bin";
600
567
  }
601
568
  function sanitizeName(name, ext) {
602
569
  let safeName = name;
@@ -7047,22 +7014,6 @@ function sanitizeName2(name, ext) {
7047
7014
  if (!safeName) safeName = "asset";
7048
7015
  return safeName;
7049
7016
  }
7050
- function sanitizeExtension2(ext) {
7051
- const normalized = ext.trim().replace(/^\./, "").toLowerCase();
7052
- if (!normalized) {
7053
- throw new Error("Invalid asset extension: empty extension");
7054
- }
7055
- if (normalized.length > 64) {
7056
- throw new Error(`Invalid asset extension: too long (${normalized.length} chars)`);
7057
- }
7058
- if (normalized.includes("/") || normalized.includes("\\") || normalized.includes("\0")) {
7059
- throw new Error("Invalid asset extension: path separators are not allowed");
7060
- }
7061
- if (!/^[a-z0-9][a-z0-9._-]*$/.test(normalized)) {
7062
- throw new Error(`Invalid asset extension: "${ext}"`);
7063
- }
7064
- return normalized;
7065
- }
7066
7017
  function writeVoxta(card, assets, options = {}) {
7067
7018
  const { compressionLevel = 6, includePackageJson = false } = options;
7068
7019
  const cardData = card.data;
@@ -7151,9 +7102,8 @@ function writeVoxta(card, assets, options = {}) {
7151
7102
  let assetCount = 0;
7152
7103
  let mainThumbnail;
7153
7104
  for (const asset of assets) {
7154
- const safeExt = sanitizeExtension2(asset.ext);
7155
- const safeName = sanitizeName2(asset.name, safeExt);
7156
- const finalFilename = `${safeName}.${safeExt}`;
7105
+ const safeName = sanitizeName2(asset.name, asset.ext);
7106
+ const finalFilename = `${safeName}.${asset.ext}`;
7157
7107
  let voxtaPath = "";
7158
7108
  const tags = asset.tags || [];
7159
7109
  const isMainIcon = asset.type === "icon" && (tags.includes("portrait-override") || asset.name === "main" || asset.isMain);