@character-foundry/character-foundry 0.4.0 → 0.4.1
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.
- package/dist/charx.cjs +12 -12
- package/dist/charx.cjs.map +1 -1
- package/dist/charx.d.cts +63 -54
- package/dist/charx.d.ts +63 -54
- package/dist/charx.js +12 -12
- package/dist/charx.js.map +1 -1
- package/dist/exporter.cjs +12 -12
- package/dist/exporter.cjs.map +1 -1
- package/dist/exporter.d.cts +63 -54
- package/dist/exporter.d.ts +63 -54
- package/dist/exporter.js +12 -12
- package/dist/exporter.js.map +1 -1
- package/dist/federation.d.cts +63 -54
- package/dist/federation.d.ts +63 -54
- package/dist/index.cjs +13 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +147 -126
- package/dist/index.d.ts +147 -126
- package/dist/index.js +13 -13
- package/dist/index.js.map +1 -1
- package/dist/loader.cjs +13 -13
- package/dist/loader.cjs.map +1 -1
- package/dist/loader.d.cts +98 -84
- package/dist/loader.d.ts +98 -84
- package/dist/loader.js +13 -13
- package/dist/loader.js.map +1 -1
- package/dist/lorebook.cjs +2 -2
- package/dist/lorebook.cjs.map +1 -1
- package/dist/lorebook.d.cts +119 -102
- package/dist/lorebook.d.ts +119 -102
- package/dist/lorebook.js +2 -2
- package/dist/lorebook.js.map +1 -1
- package/dist/normalizer.cjs +13 -13
- package/dist/normalizer.cjs.map +1 -1
- package/dist/normalizer.d.cts +210 -180
- package/dist/normalizer.d.ts +210 -180
- package/dist/normalizer.js +13 -13
- package/dist/normalizer.js.map +1 -1
- package/dist/png.cjs +12 -12
- package/dist/png.cjs.map +1 -1
- package/dist/png.d.cts +112 -96
- package/dist/png.d.ts +112 -96
- package/dist/png.js +12 -12
- package/dist/png.js.map +1 -1
- package/dist/schemas.cjs +12 -12
- package/dist/schemas.cjs.map +1 -1
- package/dist/schemas.d.cts +336 -288
- package/dist/schemas.d.ts +336 -288
- package/dist/schemas.js +12 -12
- package/dist/schemas.js.map +1 -1
- package/dist/voxta.cjs +12 -12
- package/dist/voxta.cjs.map +1 -1
- package/dist/voxta.d.cts +98 -84
- package/dist/voxta.d.ts +98 -84
- package/dist/voxta.js +12 -12
- package/dist/voxta.js.map +1 -1
- package/package.json +6 -6
package/dist/schemas.js
CHANGED
|
@@ -52,18 +52,18 @@ var CCv2LorebookEntrySchema = z2.object({
|
|
|
52
52
|
content: z2.string(),
|
|
53
53
|
enabled: z2.boolean().default(true),
|
|
54
54
|
// Default to enabled if missing
|
|
55
|
-
insertion_order: z2.number().int().default(0),
|
|
55
|
+
insertion_order: z2.number().int().nullable().default(0),
|
|
56
56
|
// Optional fields - be lenient with nulls since wild data has them
|
|
57
57
|
extensions: z2.record(z2.unknown()).optional(),
|
|
58
58
|
case_sensitive: z2.boolean().nullable().optional(),
|
|
59
59
|
name: z2.string().optional(),
|
|
60
|
-
priority: z2.number().int().optional(),
|
|
61
|
-
id: z2.number().int().optional(),
|
|
62
|
-
comment: z2.string().optional(),
|
|
60
|
+
priority: z2.number().int().nullable().optional(),
|
|
61
|
+
id: z2.number().int().nullable().optional(),
|
|
62
|
+
comment: z2.string().nullable().optional(),
|
|
63
63
|
selective: z2.boolean().nullable().optional(),
|
|
64
|
-
secondary_keys: z2.array(z2.string()).optional(),
|
|
64
|
+
secondary_keys: z2.array(z2.string()).nullable().optional(),
|
|
65
65
|
constant: z2.boolean().nullable().optional(),
|
|
66
|
-
position: z2.union([z2.enum(["before_char", "after_char"]), z2.number().int()]).nullable().optional()
|
|
66
|
+
position: z2.union([z2.enum(["before_char", "after_char"]), z2.number().int(), z2.literal("")]).nullable().optional()
|
|
67
67
|
}).passthrough();
|
|
68
68
|
var CCv2CharacterBookSchema = z2.object({
|
|
69
69
|
name: z2.string().optional(),
|
|
@@ -129,17 +129,17 @@ var CCv3LorebookEntrySchema = z3.object({
|
|
|
129
129
|
content: z3.string(),
|
|
130
130
|
enabled: z3.boolean().default(true),
|
|
131
131
|
// Default to enabled if missing
|
|
132
|
-
insertion_order: z3.number().int().default(0),
|
|
132
|
+
insertion_order: z3.number().int().nullable().default(0),
|
|
133
133
|
// Optional fields - be lenient with nulls since wild data has them
|
|
134
134
|
case_sensitive: z3.boolean().nullable().optional(),
|
|
135
135
|
name: z3.string().optional(),
|
|
136
|
-
priority: z3.number().int().optional(),
|
|
137
|
-
id: z3.number().int().optional(),
|
|
138
|
-
comment: z3.string().optional(),
|
|
136
|
+
priority: z3.number().int().nullable().optional(),
|
|
137
|
+
id: z3.number().int().nullable().optional(),
|
|
138
|
+
comment: z3.string().nullable().optional(),
|
|
139
139
|
selective: z3.boolean().nullable().optional(),
|
|
140
|
-
secondary_keys: z3.array(z3.string()).optional(),
|
|
140
|
+
secondary_keys: z3.array(z3.string()).nullable().optional(),
|
|
141
141
|
constant: z3.boolean().nullable().optional(),
|
|
142
|
-
position: z3.union([z3.enum(["before_char", "after_char"]), z3.number().int()]).nullable().optional(),
|
|
142
|
+
position: z3.union([z3.enum(["before_char", "after_char"]), z3.number().int(), z3.literal("")]).nullable().optional(),
|
|
143
143
|
extensions: z3.record(z3.unknown()).optional(),
|
|
144
144
|
// v3 specific - also lenient with types since SillyTavern uses numbers for enums
|
|
145
145
|
automation_id: z3.string().optional(),
|
package/dist/schemas.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../schemas/src/common.ts","../../schemas/src/ccv2.ts","../../schemas/src/ccv3.ts","../../schemas/src/risu.ts","../../schemas/src/normalized.ts","../../schemas/src/feature-deriver.ts","../../schemas/src/detection.ts","../../schemas/src/normalizer.ts","../../schemas/src/validation.ts"],"sourcesContent":["/**\n * Common Types\n *\n * Shared types used across all card formats.\n */\n\nimport { z } from 'zod';\n\n// ============================================================================\n// Zod Schemas\n// ============================================================================\n\n/**\n * ISO 8601 date string schema\n */\nexport const ISO8601Schema = z.string().datetime();\n\n/**\n * UUID string schema\n */\nexport const UUIDSchema = z.string().uuid();\n\n/**\n * Card specification version schema\n */\nexport const SpecSchema = z.enum(['v2', 'v3']);\n\n/**\n * Source format identifier schema\n */\nexport const SourceFormatSchema = z.enum([\n 'png_v2', // PNG with 'chara' chunk (v2)\n 'png_v3', // PNG with 'ccv3' chunk (v3)\n 'json_v2', // Raw JSON v2\n 'json_v3', // Raw JSON v3\n 'charx', // ZIP with card.json (v3 spec)\n 'charx_risu', // ZIP with card.json + module.risum\n 'charx_jpeg', // JPEG with appended ZIP (read-only)\n 'voxta', // VoxPkg format\n]);\n\n/**\n * Original JSON shape schema\n */\nexport const OriginalShapeSchema = z.enum(['wrapped', 'unwrapped', 'legacy']);\n\n/**\n * Asset type identifier schema\n */\nexport const AssetTypeSchema = z.enum([\n 'icon',\n 'background',\n 'emotion',\n 'user_icon',\n 'sound',\n 'video',\n 'custom',\n 'x-risu-asset',\n]);\n\n/**\n * Asset descriptor schema (v3 spec)\n */\nexport const AssetDescriptorSchema = z.object({\n type: AssetTypeSchema,\n uri: z.string(),\n name: z.string(),\n ext: z.string(),\n});\n\n/**\n * Extracted asset with binary data schema\n */\nexport const ExtractedAssetSchema = z.object({\n descriptor: AssetDescriptorSchema,\n data: z.instanceof(Uint8Array),\n mimeType: z.string(),\n});\n\n// ============================================================================\n// TypeScript Types (inferred from Zod schemas)\n// ============================================================================\n\n/**\n * ISO 8601 date string\n */\nexport type ISO8601 = z.infer<typeof ISO8601Schema>;\n\n/**\n * UUID string\n */\nexport type UUID = z.infer<typeof UUIDSchema>;\n\n/**\n * Card specification version\n */\nexport type Spec = z.infer<typeof SpecSchema>;\n\n/**\n * Source format identifier\n */\nexport type SourceFormat = z.infer<typeof SourceFormatSchema>;\n\n/**\n * Original JSON shape\n */\nexport type OriginalShape = z.infer<typeof OriginalShapeSchema>;\n\n/**\n * Asset type identifier\n */\nexport type AssetType = z.infer<typeof AssetTypeSchema>;\n\n/**\n * Asset descriptor (v3 spec)\n */\nexport type AssetDescriptor = z.infer<typeof AssetDescriptorSchema>;\n\n/**\n * Extracted asset with binary data\n */\nexport type ExtractedAsset = z.infer<typeof ExtractedAssetSchema>;\n","/**\n * Character Card v2 Types\n *\n * Based on: https://github.com/malfoyslastname/character-card-spec-v2\n */\n\nimport { z } from 'zod';\n\n// ============================================================================\n// Zod Schemas\n// ============================================================================\n\n/**\n * Lorebook entry schema for v2 cards\n */\nexport const CCv2LorebookEntrySchema = z.object({\n keys: z.array(z.string()).optional(), // Some tools use 'key' instead\n content: z.string(),\n enabled: z.boolean().default(true), // Default to enabled if missing\n insertion_order: z.number().int().default(0),\n // Optional fields - be lenient with nulls since wild data has them\n extensions: z.record(z.unknown()).optional(),\n case_sensitive: z.boolean().nullable().optional(),\n name: z.string().optional(),\n priority: z.number().int().optional(),\n id: z.number().int().optional(),\n comment: z.string().optional(),\n selective: z.boolean().nullable().optional(),\n secondary_keys: z.array(z.string()).optional(),\n constant: z.boolean().nullable().optional(),\n position: z.union([z.enum(['before_char', 'after_char']), z.number().int()]).nullable().optional(),\n}).passthrough(); // Allow SillyTavern extensions like depth, probability, etc.\n\n/**\n * Character book (lorebook) schema for v2 cards\n */\nexport const CCv2CharacterBookSchema = z.object({\n name: z.string().optional(),\n description: z.string().optional(),\n scan_depth: z.number().int().nonnegative().optional(),\n token_budget: z.number().int().nonnegative().optional(),\n recursive_scanning: z.boolean().optional(),\n extensions: z.record(z.unknown()).optional(),\n entries: z.array(CCv2LorebookEntrySchema),\n});\n\n/**\n * Character Card v2 data structure schema\n */\nexport const CCv2DataSchema = z.object({\n // Core fields - use .default('') to handle missing fields in malformed cards\n name: z.string().default(''),\n description: z.string().default(''),\n personality: z.string().nullable().default(''), // Can be null in wild (141 cards)\n scenario: z.string().default(''),\n first_mes: z.string().default(''),\n mes_example: z.string().nullable().default(''), // Can be null in wild (186 cards)\n // Optional fields\n creator_notes: z.string().optional(),\n system_prompt: z.string().optional(),\n post_history_instructions: z.string().optional(),\n alternate_greetings: z.array(z.string()).optional(),\n character_book: CCv2CharacterBookSchema.optional().nullable(),\n tags: z.array(z.string()).optional(),\n creator: z.string().optional(),\n character_version: z.string().optional(),\n extensions: z.record(z.unknown()).optional(),\n});\n\n/**\n * Wrapped v2 card format schema (modern tools)\n */\nexport const CCv2WrappedSchema = z.object({\n spec: z.literal('chara_card_v2'),\n spec_version: z.literal('2.0'),\n data: CCv2DataSchema,\n});\n\n// ============================================================================\n// TypeScript Types (inferred from Zod schemas)\n// ============================================================================\n\n/**\n * Lorebook entry for v2 cards\n */\nexport type CCv2LorebookEntry = z.infer<typeof CCv2LorebookEntrySchema>;\n\n/**\n * Character book (lorebook) for v2 cards\n */\nexport type CCv2CharacterBook = z.infer<typeof CCv2CharacterBookSchema>;\n\n/**\n * Character Card v2 data structure\n */\nexport type CCv2Data = z.infer<typeof CCv2DataSchema>;\n\n/**\n * Wrapped v2 card format (modern tools)\n */\nexport type CCv2Wrapped = z.infer<typeof CCv2WrappedSchema>;\n\n// ============================================================================\n// Type Guards & Parsers\n// ============================================================================\n\n/**\n * Check if data is a wrapped v2 card\n */\nexport function isWrappedV2(data: unknown): data is CCv2Wrapped {\n return CCv2WrappedSchema.safeParse(data).success;\n}\n\n/**\n * Check if data looks like v2 card data (wrapped or unwrapped)\n */\nexport function isV2CardData(data: unknown): data is CCv2Data | CCv2Wrapped {\n return (\n CCv2WrappedSchema.safeParse(data).success ||\n CCv2DataSchema.safeParse(data).success\n );\n}\n\n/**\n * Parse and validate a wrapped v2 card\n */\nexport function parseWrappedV2(data: unknown): CCv2Wrapped {\n return CCv2WrappedSchema.parse(data);\n}\n\n/**\n * Parse and validate v2 card data\n */\nexport function parseV2Data(data: unknown): CCv2Data {\n return CCv2DataSchema.parse(data);\n}\n\n/**\n * Check if data looks like a wrapped V2 card structurally (without strict validation).\n * This is more lenient than isWrappedV2 - it just checks structure, not full schema validity.\n */\nexport function looksLikeWrappedV2(data: unknown): data is { spec: string; data: Record<string, unknown> } {\n if (!data || typeof data !== 'object') return false;\n const obj = data as Record<string, unknown>;\n return (\n obj.spec === 'chara_card_v2' &&\n obj.data !== null &&\n typeof obj.data === 'object'\n );\n}\n\n/**\n * Get v2 card data from wrapped or unwrapped format.\n *\n * Uses structural check instead of strict Zod validation to handle\n * malformed cards that have the right structure but missing/invalid fields.\n * The caller (e.g., ccv2ToCCv3) handles defaulting missing fields.\n */\nexport function getV2Data(card: CCv2Data | CCv2Wrapped): CCv2Data {\n // Use structural check - more lenient than isWrappedV2 schema validation\n if (looksLikeWrappedV2(card)) {\n return card.data as CCv2Data;\n }\n return card;\n}\n","/**\n * Character Card v3 Types\n *\n * Based on: https://github.com/kwaroran/character-card-spec-v3\n */\n\nimport { z } from 'zod';\nimport { AssetDescriptorSchema } from './common.js';\n\n// ============================================================================\n// Zod Schemas\n// ============================================================================\n\n/**\n * Lorebook entry schema for v3 cards\n */\nexport const CCv3LorebookEntrySchema = z.object({\n keys: z.array(z.string()).optional(), // Some tools use 'key' instead\n content: z.string(),\n enabled: z.boolean().default(true), // Default to enabled if missing\n insertion_order: z.number().int().default(0),\n // Optional fields - be lenient with nulls since wild data has them\n case_sensitive: z.boolean().nullable().optional(),\n name: z.string().optional(),\n priority: z.number().int().optional(),\n id: z.number().int().optional(),\n comment: z.string().optional(),\n selective: z.boolean().nullable().optional(),\n secondary_keys: z.array(z.string()).optional(),\n constant: z.boolean().nullable().optional(),\n position: z.union([z.enum(['before_char', 'after_char']), z.number().int()]).nullable().optional(),\n extensions: z.record(z.unknown()).optional(),\n // v3 specific - also lenient with types since SillyTavern uses numbers for enums\n automation_id: z.string().optional(),\n role: z.union([z.enum(['system', 'user', 'assistant']), z.number().int()]).nullable().optional(),\n group: z.string().optional(),\n scan_frequency: z.number().int().nonnegative().optional(),\n probability: z.number().min(0).max(100).optional(), // Some tools use 0-100 instead of 0-1\n use_regex: z.boolean().optional(),\n depth: z.number().int().nonnegative().optional(),\n selective_logic: z.union([z.enum(['AND', 'NOT']), z.number().int()]).optional(),\n}).passthrough(); // Allow tool-specific extensions\n\n/**\n * Character book (lorebook) schema for v3 cards\n */\nexport const CCv3CharacterBookSchema = z.object({\n name: z.string().optional(),\n description: z.string().optional(),\n scan_depth: z.number().int().nonnegative().optional(),\n token_budget: z.number().int().nonnegative().optional(),\n recursive_scanning: z.boolean().optional(),\n extensions: z.record(z.unknown()).optional(),\n entries: z.array(CCv3LorebookEntrySchema),\n});\n\n/**\n * Character Card v3 inner data structure schema.\n *\n * Note: Fields like group_only_greetings, creator, character_version, and tags\n * are technically \"required\" per V3 spec but rarely present in wild cards.\n * We use .default() to make parsing lenient while still producing valid output.\n */\nexport const CCv3DataInnerSchema = z.object({\n // Core fields - use .default('') to handle missing fields in malformed cards\n name: z.string().default(''),\n description: z.string().default(''),\n personality: z.string().nullable().default(''), // Can be null in wild (141 cards)\n scenario: z.string().default(''),\n first_mes: z.string().default(''),\n mes_example: z.string().nullable().default(''), // Can be null in wild (186 cards)\n // \"Required\" per spec but often missing in wild - use defaults for leniency\n creator: z.string().default(''),\n character_version: z.string().default(''),\n tags: z.array(z.string()).default([]),\n group_only_greetings: z.array(z.string()).default([]),\n // Optional fields\n creator_notes: z.string().optional(),\n system_prompt: z.string().optional(),\n post_history_instructions: z.string().optional(),\n alternate_greetings: z.array(z.string()).optional(),\n character_book: CCv3CharacterBookSchema.optional().nullable(),\n extensions: z.record(z.unknown()).optional(),\n // v3 specific\n assets: z.array(AssetDescriptorSchema).optional(),\n nickname: z.string().optional(),\n creator_notes_multilingual: z.record(z.string()).optional(),\n source: z.array(z.string()).optional(),\n creation_date: z.number().int().nonnegative().optional(), // Unix timestamp in seconds\n modification_date: z.number().int().nonnegative().optional(), // Unix timestamp in seconds\n});\n\n/**\n * Character Card v3 full structure schema\n */\nexport const CCv3DataSchema = z.object({\n spec: z.literal('chara_card_v3'),\n spec_version: z.literal('3.0'),\n data: CCv3DataInnerSchema,\n});\n\n// ============================================================================\n// TypeScript Types (inferred from Zod schemas)\n// ============================================================================\n\n/**\n * Lorebook entry for v3 cards\n */\nexport type CCv3LorebookEntry = z.infer<typeof CCv3LorebookEntrySchema>;\n\n/**\n * Character book (lorebook) for v3 cards\n */\nexport type CCv3CharacterBook = z.infer<typeof CCv3CharacterBookSchema>;\n\n/**\n * Character Card v3 inner data structure\n */\nexport type CCv3DataInner = z.infer<typeof CCv3DataInnerSchema>;\n\n/**\n * Character Card v3 full structure\n */\nexport type CCv3Data = z.infer<typeof CCv3DataSchema>;\n\n// ============================================================================\n// Type Guards & Parsers\n// ============================================================================\n\n/**\n * Check if data is a v3 card\n */\nexport function isV3Card(data: unknown): data is CCv3Data {\n return CCv3DataSchema.safeParse(data).success;\n}\n\n/**\n * Parse and validate a v3 card\n */\nexport function parseV3Card(data: unknown): CCv3Data {\n return CCv3DataSchema.parse(data);\n}\n\n/**\n * Parse and validate v3 card inner data\n */\nexport function parseV3DataInner(data: unknown): CCv3DataInner {\n return CCv3DataInnerSchema.parse(data);\n}\n\n/**\n * Get v3 card inner data\n */\nexport function getV3Data(card: CCv3Data): CCv3DataInner {\n return card.data;\n}\n\n/**\n * Check if data looks like a V3 card structurally (without strict validation).\n * More lenient than isV3Card - just checks structure, not full schema validity.\n */\nexport function looksLikeV3Card(data: unknown): data is { spec: string; data: Record<string, unknown> } {\n if (!data || typeof data !== 'object') return false;\n const obj = data as Record<string, unknown>;\n return (\n obj.spec === 'chara_card_v3' &&\n obj.data !== null &&\n typeof obj.data === 'object'\n );\n}\n","/**\n * RisuAI Extension Types\n *\n * These extensions are preserved as opaque blobs.\n * We do NOT interpret or transform the script contents.\n */\n\n/**\n * Risu emotions mapping (v2 style)\n * Format: [name, uri][]\n */\nexport type RisuEmotions = [string, string][];\n\n/**\n * Risu additional assets (v3 style)\n * Format: [name, uri, type][]\n */\nexport type RisuAdditionalAssets = [string, string, string][];\n\n/**\n * Risu depth prompt configuration\n */\nexport interface RisuDepthPrompt {\n depth: number;\n prompt: string;\n}\n\n/**\n * Risu extensions in card.extensions.risuai\n * Preserved as opaque - we don't interpret script contents\n */\nexport interface RisuExtensions {\n // Emotion assets\n emotions?: RisuEmotions;\n additionalAssets?: RisuAdditionalAssets;\n\n // Script data - OPAQUE, do not parse\n triggerscript?: unknown;\n customScripts?: unknown;\n\n // Voice/TTS settings\n vits?: Record<string, string>;\n\n // Depth prompt\n depth_prompt?: RisuDepthPrompt;\n\n // Other Risu-specific fields\n [key: string]: unknown;\n}\n\n/**\n * CharX x_meta entry (PNG chunk metadata preservation)\n */\nexport interface CharxMetaEntry {\n type?: string; // e.g., 'WEBP', 'PNG', 'JPEG'\n [key: string]: unknown;\n}\n\n/**\n * Check if card has Risu extensions\n */\nexport function hasRisuExtensions(extensions?: Record<string, unknown>): boolean {\n if (!extensions) return false;\n return 'risuai' in extensions || 'risu' in extensions;\n}\n\n/**\n * Check if card has Risu scripts (triggerscript or customScripts)\n */\nexport function hasRisuScripts(extensions?: Record<string, unknown>): boolean {\n if (!extensions) return false;\n const risu = extensions.risuai as RisuExtensions | undefined;\n if (!risu) return false;\n return !!risu.triggerscript || !!risu.customScripts;\n}\n\n/**\n * Check if card has depth prompt\n * Checks both SillyTavern style (extensions.depth_prompt) and Risu style (extensions.risuai.depth_prompt)\n */\nexport function hasDepthPrompt(extensions?: Record<string, unknown>): boolean {\n if (!extensions) return false;\n // SillyTavern top-level depth_prompt\n if ('depth_prompt' in extensions && extensions.depth_prompt) return true;\n // Risu-style depth_prompt\n const risu = extensions.risuai as RisuExtensions | undefined;\n return !!risu?.depth_prompt;\n}\n","/**\n * Normalized Card Types\n *\n * Unified view of card data regardless of source format.\n * This is a computed/virtual representation, not stored.\n */\n\nimport type { CCv3CharacterBook } from './ccv3.js';\n\n/**\n * Normalized card representation\n * Provides unified access to card data from any format\n */\nexport interface NormalizedCard {\n // Core fields (always present)\n name: string;\n description: string;\n personality: string;\n scenario: string;\n firstMes: string;\n mesExample: string;\n\n // Optional prompts\n systemPrompt?: string;\n postHistoryInstructions?: string;\n\n // Arrays\n alternateGreetings: string[];\n groupOnlyGreetings: string[];\n tags: string[];\n\n // Metadata\n creator?: string;\n creatorNotes?: string;\n characterVersion?: string;\n\n // Character book (v3 format)\n characterBook?: CCv3CharacterBook;\n\n // Extensions (preserved as-is)\n extensions: Record<string, unknown>;\n}\n\n/**\n * Create empty normalized card with defaults\n */\nexport function createEmptyNormalizedCard(): NormalizedCard {\n return {\n name: '',\n description: '',\n personality: '',\n scenario: '',\n firstMes: '',\n mesExample: '',\n alternateGreetings: [],\n groupOnlyGreetings: [],\n tags: [],\n extensions: {},\n };\n}\n\n/**\n * Derived features extracted from card (not stored in card)\n */\nexport interface DerivedFeatures {\n // Content flags\n hasAlternateGreetings: boolean;\n alternateGreetingsCount: number;\n /** Total greetings = first_mes (1) + alternate_greetings */\n totalGreetingsCount: number;\n hasLorebook: boolean;\n lorebookEntriesCount: number;\n hasEmbeddedImages: boolean;\n embeddedImagesCount: number;\n hasGallery: boolean;\n\n // Format-specific\n hasRisuExtensions: boolean;\n hasRisuScripts: boolean;\n hasDepthPrompt: boolean;\n hasVoxtaAppearance: boolean;\n\n // Token counts (estimated)\n tokens: {\n description: number;\n personality: number;\n scenario: number;\n firstMes: number;\n mesExample: number;\n systemPrompt: number;\n total: number;\n };\n}\n\n/**\n * Create empty derived features\n */\nexport function createEmptyFeatures(): DerivedFeatures {\n return {\n hasAlternateGreetings: false,\n alternateGreetingsCount: 0,\n totalGreetingsCount: 1, // first_mes always counts as 1\n hasLorebook: false,\n lorebookEntriesCount: 0,\n hasEmbeddedImages: false,\n embeddedImagesCount: 0,\n hasGallery: false,\n hasRisuExtensions: false,\n hasRisuScripts: false,\n hasDepthPrompt: false,\n hasVoxtaAppearance: false,\n tokens: {\n description: 0,\n personality: 0,\n scenario: 0,\n firstMes: 0,\n mesExample: 0,\n systemPrompt: 0,\n total: 0,\n },\n };\n}\n","/**\n * Feature Derivation\n *\n * Canonical feature extraction from character cards.\n * Eliminates duplicate implementations across Archive, Federation, and Architect.\n */\n\nimport type { CCv2Data } from './ccv2.js';\nimport type { CCv3DataInner } from './ccv3.js';\nimport type { DerivedFeatures } from './normalized.js';\nimport { hasRisuExtensions, hasRisuScripts, hasDepthPrompt } from './risu.js';\n\n/**\n * Derive features from a character card (V2 or V3 format).\n *\n * This is the canonical implementation - all apps should use this\n * rather than implementing their own feature detection.\n *\n * @param card - Either CCv2Data or CCv3DataInner (unwrapped)\n * @returns DerivedFeatures with all feature flags populated\n *\n * @example\n * ```typescript\n * import { deriveFeatures, parseV3Card } from '@character-foundry/schemas';\n *\n * const card = parseV3Card(data);\n * const features = deriveFeatures(card.data);\n *\n * if (features.hasLorebook) {\n * console.log(`Found ${features.lorebookEntriesCount} lorebook entries`);\n * }\n * ```\n */\nexport function deriveFeatures(card: CCv2Data | CCv3DataInner): DerivedFeatures {\n // Detect format by checking for V3-specific field\n const isV3 = 'assets' in card;\n\n // Alternate greetings\n const altGreetings = card.alternate_greetings ?? [];\n const hasAlternateGreetings = altGreetings.length > 0;\n const alternateGreetingsCount = altGreetings.length;\n // Total = first_mes (1) + alternate_greetings\n const totalGreetingsCount = 1 + alternateGreetingsCount;\n\n // Lorebook\n const characterBook = card.character_book;\n const hasLorebook = !!characterBook && characterBook.entries.length > 0;\n const lorebookEntriesCount = characterBook?.entries.length ?? 0;\n\n // Assets (V3 only) - check for visual asset types\n const assets = isV3 ? (card as CCv3DataInner).assets ?? [] : [];\n const imageAssetTypes = ['icon', 'background', 'emotion', 'custom'];\n const imageAssets = assets.filter(\n (a) =>\n imageAssetTypes.includes(a.type) ||\n ['png', 'jpg', 'jpeg', 'webp', 'gif'].includes(a.ext.toLowerCase()),\n );\n const hasGallery = imageAssets.length > 0;\n\n // Embedded images - check for data URLs in text fields\n const embeddedImageCount = countEmbeddedImages(card);\n const hasEmbeddedImages = embeddedImageCount > 0;\n\n // Extensions\n const extensions = card.extensions ?? {};\n const hasRisu = hasRisuExtensions(extensions);\n const hasScripts = hasRisuScripts(extensions);\n const hasDepth = hasDepthPrompt(extensions);\n const hasVoxta = checkVoxtaAppearance(extensions);\n\n // Token counts - initialize to zero (actual counting happens in tokenizers package)\n const tokens = {\n description: 0,\n personality: 0,\n scenario: 0,\n firstMes: 0,\n mesExample: 0,\n systemPrompt: 0,\n total: 0,\n };\n\n return {\n hasAlternateGreetings,\n alternateGreetingsCount,\n totalGreetingsCount,\n hasLorebook,\n lorebookEntriesCount,\n hasEmbeddedImages,\n embeddedImagesCount: embeddedImageCount,\n hasGallery,\n hasRisuExtensions: hasRisu,\n hasRisuScripts: hasScripts,\n hasDepthPrompt: hasDepth,\n hasVoxtaAppearance: hasVoxta,\n tokens,\n };\n}\n\n/**\n * Count embedded images (data URLs) in card text fields.\n * Looks for base64-encoded images in description, personality, scenario, etc.\n */\nfunction countEmbeddedImages(card: CCv2Data | CCv3DataInner): number {\n const textFields = [\n card.description,\n card.personality,\n card.scenario,\n card.first_mes,\n card.mes_example,\n card.creator_notes,\n card.system_prompt,\n card.post_history_instructions,\n ...(card.alternate_greetings ?? []),\n ].filter((field): field is string => typeof field === 'string');\n\n // Add group_only_greetings if V3\n if ('group_only_greetings' in card) {\n textFields.push(...(card.group_only_greetings ?? []));\n }\n\n let count = 0;\n const dataUrlPattern = /data:image\\/[^;]+;base64,/g;\n\n for (const text of textFields) {\n const matches = text.match(dataUrlPattern);\n if (matches) {\n count += matches.length;\n }\n }\n\n return count;\n}\n\n/**\n * Check if card has Voxta appearance data.\n * Voxta stores appearance in extensions.voxta.appearance\n */\nfunction checkVoxtaAppearance(extensions: Record<string, unknown>): boolean {\n if (!extensions.voxta) return false;\n const voxta = extensions.voxta as Record<string, unknown>;\n return !!voxta.appearance;\n}\n","/**\n * Format Detection\n *\n * Detect card specification version from JSON data.\n */\n\nimport type { Spec } from './common.js';\n\n/**\n * V3-only fields that indicate a V3 card\n */\nconst V3_ONLY_FIELDS = ['group_only_greetings', 'creation_date', 'modification_date', 'assets'] as const;\n\n/**\n * Result from detailed spec detection\n */\nexport interface SpecDetectionResult {\n /** Detected spec version */\n spec: Spec | null;\n /** Confidence level of detection */\n confidence: 'high' | 'medium' | 'low';\n /** What fields/values indicated this spec */\n indicators: string[];\n /** Anomalies or inconsistencies detected */\n warnings: string[];\n}\n\n/**\n * Detect card spec version from parsed JSON\n * Returns 'v2', 'v3', or null if not recognized\n */\nexport function detectSpec(data: unknown): Spec | null {\n return detectSpecDetailed(data).spec;\n}\n\n/**\n * Detailed spec detection with confidence and reasoning.\n * Useful for debugging and logging.\n */\nexport function detectSpecDetailed(data: unknown): SpecDetectionResult {\n const result: SpecDetectionResult = {\n spec: null,\n confidence: 'low',\n indicators: [],\n warnings: [],\n };\n\n if (!data || typeof data !== 'object') {\n result.indicators.push('Input is not an object');\n return result;\n }\n\n const obj = data as Record<string, unknown>;\n const dataObj = (obj.data && typeof obj.data === 'object' ? obj.data : null) as Record<\n string,\n unknown\n > | null;\n\n // Check for explicit spec markers (HIGH confidence)\n\n // Explicit v3 spec marker\n if (obj.spec === 'chara_card_v3') {\n result.spec = 'v3';\n result.confidence = 'high';\n result.indicators.push('spec field is \"chara_card_v3\"');\n\n // Check for inconsistencies\n if (obj.spec_version && obj.spec_version !== '3.0') {\n result.warnings.push(`spec_version \"${obj.spec_version}\" inconsistent with v3 spec`);\n }\n\n return result;\n }\n\n // Explicit v2 spec marker\n if (obj.spec === 'chara_card_v2') {\n result.spec = 'v2';\n result.confidence = 'high';\n result.indicators.push('spec field is \"chara_card_v2\"');\n\n // Check for inconsistencies - V3-only fields in V2 card\n if (dataObj) {\n for (const field of V3_ONLY_FIELDS) {\n if (field in dataObj) {\n result.warnings.push(`V3-only field \"${field}\" found in V2 card`);\n }\n }\n }\n\n if (obj.spec_version && obj.spec_version !== '2.0') {\n result.warnings.push(`spec_version \"${obj.spec_version}\" inconsistent with v2 spec`);\n }\n\n return result;\n }\n\n // Check spec_version field (HIGH confidence)\n if (typeof obj.spec_version === 'string') {\n if (obj.spec_version.startsWith('3')) {\n result.spec = 'v3';\n result.confidence = 'high';\n result.indicators.push(`spec_version \"${obj.spec_version}\" starts with \"3\"`);\n return result;\n }\n if (obj.spec_version.startsWith('2')) {\n result.spec = 'v2';\n result.confidence = 'high';\n result.indicators.push(`spec_version \"${obj.spec_version}\" starts with \"2\"`);\n return result;\n }\n }\n\n if (obj.spec_version === 2.0 || obj.spec_version === 2) {\n result.spec = 'v2';\n result.confidence = 'high';\n result.indicators.push(`spec_version is numeric ${obj.spec_version}`);\n return result;\n }\n\n // Check for V3-only fields (MEDIUM confidence)\n if (dataObj) {\n const v3Fields: string[] = [];\n for (const field of V3_ONLY_FIELDS) {\n if (field in dataObj) {\n v3Fields.push(field);\n }\n }\n\n if (v3Fields.length > 0) {\n result.spec = 'v3';\n result.confidence = 'medium';\n result.indicators.push(`Has V3-only fields: ${v3Fields.join(', ')}`);\n return result;\n }\n }\n\n // Check root level for V3-only fields (also MEDIUM confidence)\n const rootV3Fields: string[] = [];\n for (const field of V3_ONLY_FIELDS) {\n if (field in obj) {\n rootV3Fields.push(field);\n }\n }\n if (rootV3Fields.length > 0) {\n result.spec = 'v3';\n result.confidence = 'medium';\n result.indicators.push(`Has V3-only fields at root: ${rootV3Fields.join(', ')}`);\n result.warnings.push('V3 fields found at root level instead of data object');\n return result;\n }\n\n // Wrapped format with data object (MEDIUM confidence)\n if (obj.spec && dataObj) {\n const dataName = dataObj.name;\n if (dataName && typeof dataName === 'string') {\n // Infer from spec string\n if (typeof obj.spec === 'string') {\n if (obj.spec.includes('v3') || obj.spec.includes('3')) {\n result.spec = 'v3';\n result.confidence = 'medium';\n result.indicators.push(`spec field \"${obj.spec}\" contains \"v3\" or \"3\"`);\n return result;\n }\n if (obj.spec.includes('v2') || obj.spec.includes('2')) {\n result.spec = 'v2';\n result.confidence = 'medium';\n result.indicators.push(`spec field \"${obj.spec}\" contains \"v2\" or \"2\"`);\n return result;\n }\n }\n // Default wrapped format to v3 (modern)\n result.spec = 'v3';\n result.confidence = 'medium';\n result.indicators.push('Has wrapped format with spec and data.name');\n return result;\n }\n }\n\n // Unwrapped format - V1/V2 like structure (MEDIUM confidence)\n if (obj.name && typeof obj.name === 'string') {\n if ('description' in obj || 'personality' in obj || 'scenario' in obj) {\n result.spec = 'v2';\n result.confidence = 'medium';\n result.indicators.push('Unwrapped format with name, description/personality/scenario');\n return result;\n }\n }\n\n // Check if data object has card-like structure without spec (LOW confidence)\n if (dataObj && typeof dataObj.name === 'string') {\n if ('description' in dataObj || 'personality' in dataObj) {\n result.spec = 'v2';\n result.confidence = 'low';\n result.indicators.push('Has data object with name and card fields, but no spec');\n result.warnings.push('Missing spec field');\n return result;\n }\n }\n\n result.indicators.push('No card structure detected');\n return result;\n}\n\n/**\n * Check if card has a lorebook\n */\nexport function hasLorebook(data: unknown): boolean {\n if (!data || typeof data !== 'object') return false;\n const obj = data as Record<string, unknown>;\n\n // Check wrapped format\n const wrapped = obj.data as Record<string, unknown> | undefined;\n if (wrapped?.character_book) {\n const book = wrapped.character_book as Record<string, unknown>;\n if (Array.isArray(book.entries) && book.entries.length > 0) return true;\n }\n\n // Check unwrapped format\n if (obj.character_book) {\n const book = obj.character_book as Record<string, unknown>;\n if (Array.isArray(book.entries) && book.entries.length > 0) return true;\n }\n\n return false;\n}\n\n/**\n * Check if data looks like a valid card structure\n */\nexport function looksLikeCard(data: unknown): boolean {\n if (!data || typeof data !== 'object') return false;\n const obj = data as Record<string, unknown>;\n\n // Has explicit spec marker\n if (obj.spec === 'chara_card_v2' || obj.spec === 'chara_card_v3') {\n return true;\n }\n\n // Has wrapped data with name\n if (obj.data && typeof obj.data === 'object') {\n const dataObj = obj.data as Record<string, unknown>;\n if (typeof dataObj.name === 'string' && dataObj.name.length > 0) {\n return true;\n }\n }\n\n // Has unwrapped card-like structure\n if (typeof obj.name === 'string' && obj.name.length > 0) {\n if ('description' in obj || 'personality' in obj || 'first_mes' in obj) {\n return true;\n }\n }\n\n return false;\n}\n","/**\n * Card Normalizer\n *\n * Handles normalization of malformed card data from various sources.\n * Fixes common issues like wrong spec values, misplaced fields, missing required fields.\n */\n\nimport type { CCv2Data, CCv2Wrapped, CCv2CharacterBook, CCv2LorebookEntry } from './ccv2.js';\nimport type { CCv3Data, CCv3CharacterBook, CCv3LorebookEntry } from './ccv3.js';\nimport { detectSpec } from './detection.js';\n\n/**\n * Position values as numbers (non-standard) and their string equivalents\n */\nconst POSITION_MAP: Record<number, 'before_char' | 'after_char'> = {\n 0: 'before_char',\n 1: 'after_char',\n};\n\n/**\n * V3-only lorebook entry fields that should be moved to extensions for V2\n */\nconst V3_ONLY_ENTRY_FIELDS = [\n 'probability',\n 'depth',\n 'group',\n 'scan_frequency',\n 'use_regex',\n 'selective_logic',\n 'role',\n 'automation_id',\n] as const;\n\n/**\n * Required V2 card fields with their defaults\n */\nconst V2_REQUIRED_DEFAULTS: Partial<CCv2Data> = {\n name: '',\n description: '',\n personality: '',\n scenario: '',\n first_mes: '',\n mes_example: '',\n};\n\n/**\n * Required V3 card fields with their defaults\n */\nconst V3_REQUIRED_DEFAULTS: Partial<CCv3Data['data']> = {\n name: '',\n description: '',\n personality: '',\n scenario: '',\n first_mes: '',\n mes_example: '',\n creator: '',\n character_version: '1.0',\n tags: [],\n group_only_greetings: [],\n};\n\n/**\n * Fields that belong at root level for wrapped format\n */\nconst _ROOT_FIELDS = ['spec', 'spec_version', 'data'] as const;\n\n/**\n * Fields that belong in the data object\n */\nconst DATA_FIELDS = [\n 'name',\n 'description',\n 'personality',\n 'scenario',\n 'first_mes',\n 'mes_example',\n 'creator_notes',\n 'system_prompt',\n 'post_history_instructions',\n 'alternate_greetings',\n 'character_book',\n 'tags',\n 'creator',\n 'character_version',\n 'extensions',\n 'assets',\n 'nickname',\n 'creator_notes_multilingual',\n 'source',\n 'creation_date',\n 'modification_date',\n 'group_only_greetings',\n] as const;\n\n/**\n * Deep clone an object without mutating the original\n */\nfunction deepClone<T>(obj: T): T {\n if (obj === null || obj === undefined) {\n return obj;\n }\n if (Array.isArray(obj)) {\n return obj.map((item) => deepClone(item)) as T;\n }\n if (typeof obj === 'object') {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {\n result[key] = deepClone(value);\n }\n return result as T;\n }\n return obj;\n}\n\n/**\n * Check if a timestamp is in milliseconds (13+ digits)\n */\nfunction isMilliseconds(timestamp: number): boolean {\n // Timestamps before year 2001 in seconds: < 1000000000\n // Timestamps in milliseconds are typically 13 digits: 1000000000000+\n return timestamp > 10000000000;\n}\n\n/**\n * CardNormalizer - handles normalization of malformed card data\n */\nexport const CardNormalizer = {\n /**\n * Normalize card data to valid schema format.\n *\n * Handles:\n * - Fixing spec/spec_version values\n * - Moving misplaced fields to correct locations\n * - Adding missing required fields with defaults\n * - Handling hybrid formats (fields at root AND in data object)\n *\n * @param data - Raw card data (potentially malformed)\n * @param spec - Target spec version\n * @returns Normalized card data (does not mutate input)\n */\n normalize(data: unknown, spec: 'v2' | 'v3'): CCv2Wrapped | CCv3Data {\n if (!data || typeof data !== 'object') {\n // Return minimal valid card\n if (spec === 'v3') {\n return {\n spec: 'chara_card_v3',\n spec_version: '3.0',\n data: { ...V3_REQUIRED_DEFAULTS } as CCv3Data['data'],\n };\n }\n return {\n spec: 'chara_card_v2',\n spec_version: '2.0',\n data: { ...V2_REQUIRED_DEFAULTS } as CCv2Data,\n };\n }\n\n const obj = data as Record<string, unknown>;\n const result: Record<string, unknown> = {};\n\n // Build merged data object from root fields + existing data object\n const existingData = (obj.data && typeof obj.data === 'object' ? obj.data : {}) as Record<\n string,\n unknown\n >;\n const mergedData: Record<string, unknown> = {};\n\n // Copy existing data first\n for (const [key, value] of Object.entries(existingData)) {\n mergedData[key] = deepClone(value);\n }\n\n // Move any misplaced root-level data fields into data object\n // (ChubAI hybrid format fix)\n for (const field of DATA_FIELDS) {\n if (field in obj && !(field in mergedData)) {\n mergedData[field] = deepClone(obj[field]);\n }\n }\n\n // Handle character_book: null -> remove entirely\n if (mergedData.character_book === null) {\n delete mergedData.character_book;\n }\n\n // Normalize character_book if present\n if (mergedData.character_book && typeof mergedData.character_book === 'object') {\n mergedData.character_book = this.normalizeCharacterBook(\n mergedData.character_book as Record<string, unknown>,\n spec\n );\n }\n\n // Apply defaults for required fields\n const defaults = spec === 'v3' ? V3_REQUIRED_DEFAULTS : V2_REQUIRED_DEFAULTS;\n for (const [key, defaultValue] of Object.entries(defaults)) {\n if (!(key in mergedData) || mergedData[key] === undefined) {\n mergedData[key] = Array.isArray(defaultValue) ? [...defaultValue] : defaultValue;\n }\n }\n\n // Ensure arrays are actually arrays\n if (mergedData.tags && !Array.isArray(mergedData.tags)) {\n mergedData.tags = [];\n }\n if (mergedData.alternate_greetings && !Array.isArray(mergedData.alternate_greetings)) {\n mergedData.alternate_greetings = [];\n }\n if (spec === 'v3') {\n if (\n mergedData.group_only_greetings &&\n !Array.isArray(mergedData.group_only_greetings)\n ) {\n mergedData.group_only_greetings = [];\n }\n }\n\n // Build result with correct spec\n if (spec === 'v3') {\n result.spec = 'chara_card_v3';\n result.spec_version = '3.0';\n result.data = this.fixTimestampsInner(mergedData);\n } else {\n result.spec = 'chara_card_v2';\n result.spec_version = '2.0';\n result.data = mergedData;\n }\n\n return result as unknown as CCv2Wrapped | CCv3Data;\n },\n\n /**\n * Normalize a character book (lorebook).\n *\n * Handles:\n * - Ensuring required fields exist\n * - Converting numeric position values to string enums\n * - Moving V3-only fields to extensions for V2 compatibility\n *\n * @param book - Raw character book data\n * @param spec - Target spec version\n * @returns Normalized character book\n */\n normalizeCharacterBook(\n book: Record<string, unknown>,\n spec: 'v2' | 'v3'\n ): CCv2CharacterBook | CCv3CharacterBook {\n const result: Record<string, unknown> = {};\n\n // Copy book-level fields\n if (book.name !== undefined) result.name = book.name;\n if (book.description !== undefined) result.description = book.description;\n if (book.scan_depth !== undefined) result.scan_depth = book.scan_depth;\n if (book.token_budget !== undefined) result.token_budget = book.token_budget;\n if (book.recursive_scanning !== undefined)\n result.recursive_scanning = book.recursive_scanning;\n if (book.extensions !== undefined) result.extensions = deepClone(book.extensions);\n\n // Normalize entries\n const entries = Array.isArray(book.entries) ? book.entries : [];\n result.entries = entries.map((entry) =>\n this.normalizeEntry(entry as Record<string, unknown>, spec)\n );\n\n return result as unknown as CCv2CharacterBook | CCv3CharacterBook;\n },\n\n /**\n * Normalize a single lorebook entry.\n *\n * Handles:\n * - Converting numeric position to string enum\n * - Moving V3-only fields to extensions for V2\n * - Ensuring required fields exist\n *\n * @param entry - Raw entry data\n * @param spec - Target spec version\n * @returns Normalized entry\n */\n normalizeEntry(\n entry: Record<string, unknown>,\n spec: 'v2' | 'v3'\n ): CCv2LorebookEntry | CCv3LorebookEntry {\n const result: Record<string, unknown> = {};\n\n // Required fields with defaults\n result.keys = Array.isArray(entry.keys) ? [...entry.keys] : [];\n result.content = typeof entry.content === 'string' ? entry.content : '';\n result.enabled = entry.enabled !== false; // default true\n result.insertion_order =\n typeof entry.insertion_order === 'number' ? entry.insertion_order : 0;\n\n // For V2, extensions is required\n if (spec === 'v2') {\n result.extensions =\n entry.extensions && typeof entry.extensions === 'object'\n ? deepClone(entry.extensions)\n : {};\n }\n\n // Optional fields\n if (entry.case_sensitive !== undefined) result.case_sensitive = entry.case_sensitive;\n if (entry.name !== undefined) result.name = entry.name;\n if (entry.priority !== undefined) result.priority = entry.priority;\n if (entry.id !== undefined) result.id = entry.id;\n if (entry.comment !== undefined) result.comment = entry.comment;\n if (entry.selective !== undefined) result.selective = entry.selective;\n if (entry.secondary_keys !== undefined) {\n result.secondary_keys = Array.isArray(entry.secondary_keys)\n ? [...entry.secondary_keys]\n : [];\n }\n if (entry.constant !== undefined) result.constant = entry.constant;\n\n // Position: convert numeric to string enum\n if (entry.position !== undefined) {\n if (typeof entry.position === 'number') {\n result.position = POSITION_MAP[entry.position] || 'before_char';\n } else if (entry.position === 'before_char' || entry.position === 'after_char') {\n result.position = entry.position;\n }\n }\n\n // Handle V3-only fields\n if (spec === 'v3') {\n // Copy V3 fields directly\n if (entry.extensions !== undefined) result.extensions = deepClone(entry.extensions);\n for (const field of V3_ONLY_ENTRY_FIELDS) {\n if (entry[field] !== undefined) {\n result[field] = entry[field];\n }\n }\n } else {\n // V2: Move V3-only fields to extensions\n const ext = (result.extensions || {}) as Record<string, unknown>;\n for (const field of V3_ONLY_ENTRY_FIELDS) {\n if (entry[field] !== undefined) {\n ext[field] = entry[field];\n }\n }\n result.extensions = ext;\n }\n\n return result as unknown as CCv2LorebookEntry | CCv3LorebookEntry;\n },\n\n /**\n * Fix CharacterTavern timestamp format (milliseconds -> seconds).\n *\n * CCv3 spec requires timestamps in seconds (Unix epoch).\n * CharacterTavern exports timestamps in milliseconds.\n *\n * @param data - V3 card data\n * @returns Card data with fixed timestamps (does not mutate input)\n */\n fixTimestamps(data: CCv3Data): CCv3Data {\n const result = deepClone(data);\n result.data = this.fixTimestampsInner(\n result.data as unknown as Record<string, unknown>\n ) as unknown as CCv3Data['data'];\n return result;\n },\n\n /**\n * Internal: fix timestamps in data object\n */\n fixTimestampsInner(data: Record<string, unknown>): Record<string, unknown> {\n const result = { ...data };\n\n if (typeof result.creation_date === 'number') {\n if (isMilliseconds(result.creation_date)) {\n result.creation_date = Math.floor(result.creation_date / 1000);\n }\n // Sanitize negative timestamps (.NET default dates like 0001-01-01)\n if ((result.creation_date as number) < 0) {\n delete result.creation_date;\n }\n }\n\n if (typeof result.modification_date === 'number') {\n if (isMilliseconds(result.modification_date)) {\n result.modification_date = Math.floor(result.modification_date / 1000);\n }\n // Sanitize negative timestamps (.NET default dates like 0001-01-01)\n if ((result.modification_date as number) < 0) {\n delete result.modification_date;\n }\n }\n\n return result;\n },\n\n /**\n * Auto-detect spec and normalize.\n *\n * @param data - Raw card data\n * @returns Normalized card data, or null if not a valid card\n */\n autoNormalize(data: unknown): CCv2Wrapped | CCv3Data | null {\n const spec = detectSpec(data);\n if (!spec) return null;\n\n // V1 cards get upgraded to V2\n const targetSpec = spec === 'v3' ? 'v3' : 'v2';\n return this.normalize(data, targetSpec);\n },\n};\n\nexport type { CCv2Wrapped, CCv3Data };\n","/**\n * Validation Utilities\n *\n * Helper functions for Zod validation with Foundry error integration.\n */\n\nimport { z } from 'zod';\n\n/**\n * Convert Zod error to human-readable message\n */\nexport function zodErrorToMessage(zodError: z.ZodError, context?: string): string {\n const messages = zodError.errors.map((err) => {\n const path = err.path.length > 0 ? `${err.path.join('.')}: ` : '';\n return `${path}${err.message}`;\n });\n\n const message = messages.join('; ');\n return context ? `${context} - ${message}` : message;\n}\n\n/**\n * Get the first error field from Zod error\n */\nexport function getFirstErrorField(zodError: z.ZodError): string | undefined {\n return zodError.errors[0]?.path[0]?.toString();\n}\n\n/**\n * Safe parse with detailed error information\n */\nexport function safeParse<T>(\n schema: z.ZodSchema<T>,\n data: unknown\n): { success: true; data: T } | { success: false; error: string; field?: string } {\n const result = schema.safeParse(data);\n\n if (result.success) {\n return { success: true, data: result.data };\n }\n\n return {\n success: false,\n error: zodErrorToMessage(result.error),\n field: getFirstErrorField(result.error),\n };\n}\n"],"mappings":"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","names":["z","hasLorebook"]}
|
|
1
|
+
{"version":3,"sources":["../../schemas/src/common.ts","../../schemas/src/ccv2.ts","../../schemas/src/ccv3.ts","../../schemas/src/risu.ts","../../schemas/src/normalized.ts","../../schemas/src/feature-deriver.ts","../../schemas/src/detection.ts","../../schemas/src/normalizer.ts","../../schemas/src/validation.ts"],"sourcesContent":["/**\n * Common Types\n *\n * Shared types used across all card formats.\n */\n\nimport { z } from 'zod';\n\n// ============================================================================\n// Zod Schemas\n// ============================================================================\n\n/**\n * ISO 8601 date string schema\n */\nexport const ISO8601Schema = z.string().datetime();\n\n/**\n * UUID string schema\n */\nexport const UUIDSchema = z.string().uuid();\n\n/**\n * Card specification version schema\n */\nexport const SpecSchema = z.enum(['v2', 'v3']);\n\n/**\n * Source format identifier schema\n */\nexport const SourceFormatSchema = z.enum([\n 'png_v2', // PNG with 'chara' chunk (v2)\n 'png_v3', // PNG with 'ccv3' chunk (v3)\n 'json_v2', // Raw JSON v2\n 'json_v3', // Raw JSON v3\n 'charx', // ZIP with card.json (v3 spec)\n 'charx_risu', // ZIP with card.json + module.risum\n 'charx_jpeg', // JPEG with appended ZIP (read-only)\n 'voxta', // VoxPkg format\n]);\n\n/**\n * Original JSON shape schema\n */\nexport const OriginalShapeSchema = z.enum(['wrapped', 'unwrapped', 'legacy']);\n\n/**\n * Asset type identifier schema\n */\nexport const AssetTypeSchema = z.enum([\n 'icon',\n 'background',\n 'emotion',\n 'user_icon',\n 'sound',\n 'video',\n 'custom',\n 'x-risu-asset',\n]);\n\n/**\n * Asset descriptor schema (v3 spec)\n */\nexport const AssetDescriptorSchema = z.object({\n type: AssetTypeSchema,\n uri: z.string(),\n name: z.string(),\n ext: z.string(),\n});\n\n/**\n * Extracted asset with binary data schema\n */\nexport const ExtractedAssetSchema = z.object({\n descriptor: AssetDescriptorSchema,\n data: z.instanceof(Uint8Array),\n mimeType: z.string(),\n});\n\n// ============================================================================\n// TypeScript Types (inferred from Zod schemas)\n// ============================================================================\n\n/**\n * ISO 8601 date string\n */\nexport type ISO8601 = z.infer<typeof ISO8601Schema>;\n\n/**\n * UUID string\n */\nexport type UUID = z.infer<typeof UUIDSchema>;\n\n/**\n * Card specification version\n */\nexport type Spec = z.infer<typeof SpecSchema>;\n\n/**\n * Source format identifier\n */\nexport type SourceFormat = z.infer<typeof SourceFormatSchema>;\n\n/**\n * Original JSON shape\n */\nexport type OriginalShape = z.infer<typeof OriginalShapeSchema>;\n\n/**\n * Asset type identifier\n */\nexport type AssetType = z.infer<typeof AssetTypeSchema>;\n\n/**\n * Asset descriptor (v3 spec)\n */\nexport type AssetDescriptor = z.infer<typeof AssetDescriptorSchema>;\n\n/**\n * Extracted asset with binary data\n */\nexport type ExtractedAsset = z.infer<typeof ExtractedAssetSchema>;\n","/**\n * Character Card v2 Types\n *\n * Based on: https://github.com/malfoyslastname/character-card-spec-v2\n */\n\nimport { z } from 'zod';\n\n// ============================================================================\n// Zod Schemas\n// ============================================================================\n\n/**\n * Lorebook entry schema for v2 cards\n */\nexport const CCv2LorebookEntrySchema = z.object({\n keys: z.array(z.string()).optional(), // Some tools use 'key' instead\n content: z.string(),\n enabled: z.boolean().default(true), // Default to enabled if missing\n insertion_order: z.number().int().nullable().default(0),\n // Optional fields - be lenient with nulls since wild data has them\n extensions: z.record(z.unknown()).optional(),\n case_sensitive: z.boolean().nullable().optional(),\n name: z.string().optional(),\n priority: z.number().int().nullable().optional(),\n id: z.number().int().nullable().optional(),\n comment: z.string().nullable().optional(),\n selective: z.boolean().nullable().optional(),\n secondary_keys: z.array(z.string()).nullable().optional(),\n constant: z.boolean().nullable().optional(),\n position: z.union([z.enum(['before_char', 'after_char']), z.number().int(), z.literal('')]).nullable().optional(),\n}).passthrough(); // Allow SillyTavern extensions like depth, probability, etc.\n\n/**\n * Character book (lorebook) schema for v2 cards\n */\nexport const CCv2CharacterBookSchema = z.object({\n name: z.string().optional(),\n description: z.string().optional(),\n scan_depth: z.number().int().nonnegative().optional(),\n token_budget: z.number().int().nonnegative().optional(),\n recursive_scanning: z.boolean().optional(),\n extensions: z.record(z.unknown()).optional(),\n entries: z.array(CCv2LorebookEntrySchema),\n});\n\n/**\n * Character Card v2 data structure schema\n */\nexport const CCv2DataSchema = z.object({\n // Core fields - use .default('') to handle missing fields in malformed cards\n name: z.string().default(''),\n description: z.string().default(''),\n personality: z.string().nullable().default(''), // Can be null in wild (141 cards)\n scenario: z.string().default(''),\n first_mes: z.string().default(''),\n mes_example: z.string().nullable().default(''), // Can be null in wild (186 cards)\n // Optional fields\n creator_notes: z.string().optional(),\n system_prompt: z.string().optional(),\n post_history_instructions: z.string().optional(),\n alternate_greetings: z.array(z.string()).optional(),\n character_book: CCv2CharacterBookSchema.optional().nullable(),\n tags: z.array(z.string()).optional(),\n creator: z.string().optional(),\n character_version: z.string().optional(),\n extensions: z.record(z.unknown()).optional(),\n});\n\n/**\n * Wrapped v2 card format schema (modern tools)\n */\nexport const CCv2WrappedSchema = z.object({\n spec: z.literal('chara_card_v2'),\n spec_version: z.literal('2.0'),\n data: CCv2DataSchema,\n});\n\n// ============================================================================\n// TypeScript Types (inferred from Zod schemas)\n// ============================================================================\n\n/**\n * Lorebook entry for v2 cards\n */\nexport type CCv2LorebookEntry = z.infer<typeof CCv2LorebookEntrySchema>;\n\n/**\n * Character book (lorebook) for v2 cards\n */\nexport type CCv2CharacterBook = z.infer<typeof CCv2CharacterBookSchema>;\n\n/**\n * Character Card v2 data structure\n */\nexport type CCv2Data = z.infer<typeof CCv2DataSchema>;\n\n/**\n * Wrapped v2 card format (modern tools)\n */\nexport type CCv2Wrapped = z.infer<typeof CCv2WrappedSchema>;\n\n// ============================================================================\n// Type Guards & Parsers\n// ============================================================================\n\n/**\n * Check if data is a wrapped v2 card\n */\nexport function isWrappedV2(data: unknown): data is CCv2Wrapped {\n return CCv2WrappedSchema.safeParse(data).success;\n}\n\n/**\n * Check if data looks like v2 card data (wrapped or unwrapped)\n */\nexport function isV2CardData(data: unknown): data is CCv2Data | CCv2Wrapped {\n return (\n CCv2WrappedSchema.safeParse(data).success ||\n CCv2DataSchema.safeParse(data).success\n );\n}\n\n/**\n * Parse and validate a wrapped v2 card\n */\nexport function parseWrappedV2(data: unknown): CCv2Wrapped {\n return CCv2WrappedSchema.parse(data);\n}\n\n/**\n * Parse and validate v2 card data\n */\nexport function parseV2Data(data: unknown): CCv2Data {\n return CCv2DataSchema.parse(data);\n}\n\n/**\n * Check if data looks like a wrapped V2 card structurally (without strict validation).\n * This is more lenient than isWrappedV2 - it just checks structure, not full schema validity.\n */\nexport function looksLikeWrappedV2(data: unknown): data is { spec: string; data: Record<string, unknown> } {\n if (!data || typeof data !== 'object') return false;\n const obj = data as Record<string, unknown>;\n return (\n obj.spec === 'chara_card_v2' &&\n obj.data !== null &&\n typeof obj.data === 'object'\n );\n}\n\n/**\n * Get v2 card data from wrapped or unwrapped format.\n *\n * Uses structural check instead of strict Zod validation to handle\n * malformed cards that have the right structure but missing/invalid fields.\n * The caller (e.g., ccv2ToCCv3) handles defaulting missing fields.\n */\nexport function getV2Data(card: CCv2Data | CCv2Wrapped): CCv2Data {\n // Use structural check - more lenient than isWrappedV2 schema validation\n if (looksLikeWrappedV2(card)) {\n return card.data as CCv2Data;\n }\n return card;\n}\n","/**\n * Character Card v3 Types\n *\n * Based on: https://github.com/kwaroran/character-card-spec-v3\n */\n\nimport { z } from 'zod';\nimport { AssetDescriptorSchema } from './common.js';\n\n// ============================================================================\n// Zod Schemas\n// ============================================================================\n\n/**\n * Lorebook entry schema for v3 cards\n */\nexport const CCv3LorebookEntrySchema = z.object({\n keys: z.array(z.string()).optional(), // Some tools use 'key' instead\n content: z.string(),\n enabled: z.boolean().default(true), // Default to enabled if missing\n insertion_order: z.number().int().nullable().default(0),\n // Optional fields - be lenient with nulls since wild data has them\n case_sensitive: z.boolean().nullable().optional(),\n name: z.string().optional(),\n priority: z.number().int().nullable().optional(),\n id: z.number().int().nullable().optional(),\n comment: z.string().nullable().optional(),\n selective: z.boolean().nullable().optional(),\n secondary_keys: z.array(z.string()).nullable().optional(),\n constant: z.boolean().nullable().optional(),\n position: z.union([z.enum(['before_char', 'after_char']), z.number().int(), z.literal('')]).nullable().optional(),\n extensions: z.record(z.unknown()).optional(),\n // v3 specific - also lenient with types since SillyTavern uses numbers for enums\n automation_id: z.string().optional(),\n role: z.union([z.enum(['system', 'user', 'assistant']), z.number().int()]).nullable().optional(),\n group: z.string().optional(),\n scan_frequency: z.number().int().nonnegative().optional(),\n probability: z.number().min(0).max(100).optional(), // Some tools use 0-100 instead of 0-1\n use_regex: z.boolean().optional(),\n depth: z.number().int().nonnegative().optional(),\n selective_logic: z.union([z.enum(['AND', 'NOT']), z.number().int()]).optional(),\n}).passthrough(); // Allow tool-specific extensions\n\n/**\n * Character book (lorebook) schema for v3 cards\n */\nexport const CCv3CharacterBookSchema = z.object({\n name: z.string().optional(),\n description: z.string().optional(),\n scan_depth: z.number().int().nonnegative().optional(),\n token_budget: z.number().int().nonnegative().optional(),\n recursive_scanning: z.boolean().optional(),\n extensions: z.record(z.unknown()).optional(),\n entries: z.array(CCv3LorebookEntrySchema),\n});\n\n/**\n * Character Card v3 inner data structure schema.\n *\n * Note: Fields like group_only_greetings, creator, character_version, and tags\n * are technically \"required\" per V3 spec but rarely present in wild cards.\n * We use .default() to make parsing lenient while still producing valid output.\n */\nexport const CCv3DataInnerSchema = z.object({\n // Core fields - use .default('') to handle missing fields in malformed cards\n name: z.string().default(''),\n description: z.string().default(''),\n personality: z.string().nullable().default(''), // Can be null in wild (141 cards)\n scenario: z.string().default(''),\n first_mes: z.string().default(''),\n mes_example: z.string().nullable().default(''), // Can be null in wild (186 cards)\n // \"Required\" per spec but often missing in wild - use defaults for leniency\n creator: z.string().default(''),\n character_version: z.string().default(''),\n tags: z.array(z.string()).default([]),\n group_only_greetings: z.array(z.string()).default([]),\n // Optional fields\n creator_notes: z.string().optional(),\n system_prompt: z.string().optional(),\n post_history_instructions: z.string().optional(),\n alternate_greetings: z.array(z.string()).optional(),\n character_book: CCv3CharacterBookSchema.optional().nullable(),\n extensions: z.record(z.unknown()).optional(),\n // v3 specific\n assets: z.array(AssetDescriptorSchema).optional(),\n nickname: z.string().optional(),\n creator_notes_multilingual: z.record(z.string()).optional(),\n source: z.array(z.string()).optional(),\n creation_date: z.number().int().nonnegative().optional(), // Unix timestamp in seconds\n modification_date: z.number().int().nonnegative().optional(), // Unix timestamp in seconds\n});\n\n/**\n * Character Card v3 full structure schema\n */\nexport const CCv3DataSchema = z.object({\n spec: z.literal('chara_card_v3'),\n spec_version: z.literal('3.0'),\n data: CCv3DataInnerSchema,\n});\n\n// ============================================================================\n// TypeScript Types (inferred from Zod schemas)\n// ============================================================================\n\n/**\n * Lorebook entry for v3 cards\n */\nexport type CCv3LorebookEntry = z.infer<typeof CCv3LorebookEntrySchema>;\n\n/**\n * Character book (lorebook) for v3 cards\n */\nexport type CCv3CharacterBook = z.infer<typeof CCv3CharacterBookSchema>;\n\n/**\n * Character Card v3 inner data structure\n */\nexport type CCv3DataInner = z.infer<typeof CCv3DataInnerSchema>;\n\n/**\n * Character Card v3 full structure\n */\nexport type CCv3Data = z.infer<typeof CCv3DataSchema>;\n\n// ============================================================================\n// Type Guards & Parsers\n// ============================================================================\n\n/**\n * Check if data is a v3 card\n */\nexport function isV3Card(data: unknown): data is CCv3Data {\n return CCv3DataSchema.safeParse(data).success;\n}\n\n/**\n * Parse and validate a v3 card\n */\nexport function parseV3Card(data: unknown): CCv3Data {\n return CCv3DataSchema.parse(data);\n}\n\n/**\n * Parse and validate v3 card inner data\n */\nexport function parseV3DataInner(data: unknown): CCv3DataInner {\n return CCv3DataInnerSchema.parse(data);\n}\n\n/**\n * Get v3 card inner data\n */\nexport function getV3Data(card: CCv3Data): CCv3DataInner {\n return card.data;\n}\n\n/**\n * Check if data looks like a V3 card structurally (without strict validation).\n * More lenient than isV3Card - just checks structure, not full schema validity.\n */\nexport function looksLikeV3Card(data: unknown): data is { spec: string; data: Record<string, unknown> } {\n if (!data || typeof data !== 'object') return false;\n const obj = data as Record<string, unknown>;\n return (\n obj.spec === 'chara_card_v3' &&\n obj.data !== null &&\n typeof obj.data === 'object'\n );\n}\n","/**\n * RisuAI Extension Types\n *\n * These extensions are preserved as opaque blobs.\n * We do NOT interpret or transform the script contents.\n */\n\n/**\n * Risu emotions mapping (v2 style)\n * Format: [name, uri][]\n */\nexport type RisuEmotions = [string, string][];\n\n/**\n * Risu additional assets (v3 style)\n * Format: [name, uri, type][]\n */\nexport type RisuAdditionalAssets = [string, string, string][];\n\n/**\n * Risu depth prompt configuration\n */\nexport interface RisuDepthPrompt {\n depth: number;\n prompt: string;\n}\n\n/**\n * Risu extensions in card.extensions.risuai\n * Preserved as opaque - we don't interpret script contents\n */\nexport interface RisuExtensions {\n // Emotion assets\n emotions?: RisuEmotions;\n additionalAssets?: RisuAdditionalAssets;\n\n // Script data - OPAQUE, do not parse\n triggerscript?: unknown;\n customScripts?: unknown;\n\n // Voice/TTS settings\n vits?: Record<string, string>;\n\n // Depth prompt\n depth_prompt?: RisuDepthPrompt;\n\n // Other Risu-specific fields\n [key: string]: unknown;\n}\n\n/**\n * CharX x_meta entry (PNG chunk metadata preservation)\n */\nexport interface CharxMetaEntry {\n type?: string; // e.g., 'WEBP', 'PNG', 'JPEG'\n [key: string]: unknown;\n}\n\n/**\n * Check if card has Risu extensions\n */\nexport function hasRisuExtensions(extensions?: Record<string, unknown>): boolean {\n if (!extensions) return false;\n return 'risuai' in extensions || 'risu' in extensions;\n}\n\n/**\n * Check if card has Risu scripts (triggerscript or customScripts)\n */\nexport function hasRisuScripts(extensions?: Record<string, unknown>): boolean {\n if (!extensions) return false;\n const risu = extensions.risuai as RisuExtensions | undefined;\n if (!risu) return false;\n return !!risu.triggerscript || !!risu.customScripts;\n}\n\n/**\n * Check if card has depth prompt\n * Checks both SillyTavern style (extensions.depth_prompt) and Risu style (extensions.risuai.depth_prompt)\n */\nexport function hasDepthPrompt(extensions?: Record<string, unknown>): boolean {\n if (!extensions) return false;\n // SillyTavern top-level depth_prompt\n if ('depth_prompt' in extensions && extensions.depth_prompt) return true;\n // Risu-style depth_prompt\n const risu = extensions.risuai as RisuExtensions | undefined;\n return !!risu?.depth_prompt;\n}\n","/**\n * Normalized Card Types\n *\n * Unified view of card data regardless of source format.\n * This is a computed/virtual representation, not stored.\n */\n\nimport type { CCv3CharacterBook } from './ccv3.js';\n\n/**\n * Normalized card representation\n * Provides unified access to card data from any format\n */\nexport interface NormalizedCard {\n // Core fields (always present)\n name: string;\n description: string;\n personality: string;\n scenario: string;\n firstMes: string;\n mesExample: string;\n\n // Optional prompts\n systemPrompt?: string;\n postHistoryInstructions?: string;\n\n // Arrays\n alternateGreetings: string[];\n groupOnlyGreetings: string[];\n tags: string[];\n\n // Metadata\n creator?: string;\n creatorNotes?: string;\n characterVersion?: string;\n\n // Character book (v3 format)\n characterBook?: CCv3CharacterBook;\n\n // Extensions (preserved as-is)\n extensions: Record<string, unknown>;\n}\n\n/**\n * Create empty normalized card with defaults\n */\nexport function createEmptyNormalizedCard(): NormalizedCard {\n return {\n name: '',\n description: '',\n personality: '',\n scenario: '',\n firstMes: '',\n mesExample: '',\n alternateGreetings: [],\n groupOnlyGreetings: [],\n tags: [],\n extensions: {},\n };\n}\n\n/**\n * Derived features extracted from card (not stored in card)\n */\nexport interface DerivedFeatures {\n // Content flags\n hasAlternateGreetings: boolean;\n alternateGreetingsCount: number;\n /** Total greetings = first_mes (1) + alternate_greetings */\n totalGreetingsCount: number;\n hasLorebook: boolean;\n lorebookEntriesCount: number;\n hasEmbeddedImages: boolean;\n embeddedImagesCount: number;\n hasGallery: boolean;\n\n // Format-specific\n hasRisuExtensions: boolean;\n hasRisuScripts: boolean;\n hasDepthPrompt: boolean;\n hasVoxtaAppearance: boolean;\n\n // Token counts (estimated)\n tokens: {\n description: number;\n personality: number;\n scenario: number;\n firstMes: number;\n mesExample: number;\n systemPrompt: number;\n total: number;\n };\n}\n\n/**\n * Create empty derived features\n */\nexport function createEmptyFeatures(): DerivedFeatures {\n return {\n hasAlternateGreetings: false,\n alternateGreetingsCount: 0,\n totalGreetingsCount: 1, // first_mes always counts as 1\n hasLorebook: false,\n lorebookEntriesCount: 0,\n hasEmbeddedImages: false,\n embeddedImagesCount: 0,\n hasGallery: false,\n hasRisuExtensions: false,\n hasRisuScripts: false,\n hasDepthPrompt: false,\n hasVoxtaAppearance: false,\n tokens: {\n description: 0,\n personality: 0,\n scenario: 0,\n firstMes: 0,\n mesExample: 0,\n systemPrompt: 0,\n total: 0,\n },\n };\n}\n","/**\n * Feature Derivation\n *\n * Canonical feature extraction from character cards.\n * Eliminates duplicate implementations across Archive, Federation, and Architect.\n */\n\nimport type { CCv2Data } from './ccv2.js';\nimport type { CCv3DataInner } from './ccv3.js';\nimport type { DerivedFeatures } from './normalized.js';\nimport { hasRisuExtensions, hasRisuScripts, hasDepthPrompt } from './risu.js';\n\n/**\n * Derive features from a character card (V2 or V3 format).\n *\n * This is the canonical implementation - all apps should use this\n * rather than implementing their own feature detection.\n *\n * @param card - Either CCv2Data or CCv3DataInner (unwrapped)\n * @returns DerivedFeatures with all feature flags populated\n *\n * @example\n * ```typescript\n * import { deriveFeatures, parseV3Card } from '@character-foundry/schemas';\n *\n * const card = parseV3Card(data);\n * const features = deriveFeatures(card.data);\n *\n * if (features.hasLorebook) {\n * console.log(`Found ${features.lorebookEntriesCount} lorebook entries`);\n * }\n * ```\n */\nexport function deriveFeatures(card: CCv2Data | CCv3DataInner): DerivedFeatures {\n // Detect format by checking for V3-specific field\n const isV3 = 'assets' in card;\n\n // Alternate greetings\n const altGreetings = card.alternate_greetings ?? [];\n const hasAlternateGreetings = altGreetings.length > 0;\n const alternateGreetingsCount = altGreetings.length;\n // Total = first_mes (1) + alternate_greetings\n const totalGreetingsCount = 1 + alternateGreetingsCount;\n\n // Lorebook\n const characterBook = card.character_book;\n const hasLorebook = !!characterBook && characterBook.entries.length > 0;\n const lorebookEntriesCount = characterBook?.entries.length ?? 0;\n\n // Assets (V3 only) - check for visual asset types\n const assets = isV3 ? (card as CCv3DataInner).assets ?? [] : [];\n const imageAssetTypes = ['icon', 'background', 'emotion', 'custom'];\n const imageAssets = assets.filter(\n (a) =>\n imageAssetTypes.includes(a.type) ||\n ['png', 'jpg', 'jpeg', 'webp', 'gif'].includes(a.ext.toLowerCase()),\n );\n const hasGallery = imageAssets.length > 0;\n\n // Embedded images - check for data URLs in text fields\n const embeddedImageCount = countEmbeddedImages(card);\n const hasEmbeddedImages = embeddedImageCount > 0;\n\n // Extensions\n const extensions = card.extensions ?? {};\n const hasRisu = hasRisuExtensions(extensions);\n const hasScripts = hasRisuScripts(extensions);\n const hasDepth = hasDepthPrompt(extensions);\n const hasVoxta = checkVoxtaAppearance(extensions);\n\n // Token counts - initialize to zero (actual counting happens in tokenizers package)\n const tokens = {\n description: 0,\n personality: 0,\n scenario: 0,\n firstMes: 0,\n mesExample: 0,\n systemPrompt: 0,\n total: 0,\n };\n\n return {\n hasAlternateGreetings,\n alternateGreetingsCount,\n totalGreetingsCount,\n hasLorebook,\n lorebookEntriesCount,\n hasEmbeddedImages,\n embeddedImagesCount: embeddedImageCount,\n hasGallery,\n hasRisuExtensions: hasRisu,\n hasRisuScripts: hasScripts,\n hasDepthPrompt: hasDepth,\n hasVoxtaAppearance: hasVoxta,\n tokens,\n };\n}\n\n/**\n * Count embedded images (data URLs) in card text fields.\n * Looks for base64-encoded images in description, personality, scenario, etc.\n */\nfunction countEmbeddedImages(card: CCv2Data | CCv3DataInner): number {\n const textFields = [\n card.description,\n card.personality,\n card.scenario,\n card.first_mes,\n card.mes_example,\n card.creator_notes,\n card.system_prompt,\n card.post_history_instructions,\n ...(card.alternate_greetings ?? []),\n ].filter((field): field is string => typeof field === 'string');\n\n // Add group_only_greetings if V3\n if ('group_only_greetings' in card) {\n textFields.push(...(card.group_only_greetings ?? []));\n }\n\n let count = 0;\n const dataUrlPattern = /data:image\\/[^;]+;base64,/g;\n\n for (const text of textFields) {\n const matches = text.match(dataUrlPattern);\n if (matches) {\n count += matches.length;\n }\n }\n\n return count;\n}\n\n/**\n * Check if card has Voxta appearance data.\n * Voxta stores appearance in extensions.voxta.appearance\n */\nfunction checkVoxtaAppearance(extensions: Record<string, unknown>): boolean {\n if (!extensions.voxta) return false;\n const voxta = extensions.voxta as Record<string, unknown>;\n return !!voxta.appearance;\n}\n","/**\n * Format Detection\n *\n * Detect card specification version from JSON data.\n */\n\nimport type { Spec } from './common.js';\n\n/**\n * V3-only fields that indicate a V3 card\n */\nconst V3_ONLY_FIELDS = ['group_only_greetings', 'creation_date', 'modification_date', 'assets'] as const;\n\n/**\n * Result from detailed spec detection\n */\nexport interface SpecDetectionResult {\n /** Detected spec version */\n spec: Spec | null;\n /** Confidence level of detection */\n confidence: 'high' | 'medium' | 'low';\n /** What fields/values indicated this spec */\n indicators: string[];\n /** Anomalies or inconsistencies detected */\n warnings: string[];\n}\n\n/**\n * Detect card spec version from parsed JSON\n * Returns 'v2', 'v3', or null if not recognized\n */\nexport function detectSpec(data: unknown): Spec | null {\n return detectSpecDetailed(data).spec;\n}\n\n/**\n * Detailed spec detection with confidence and reasoning.\n * Useful for debugging and logging.\n */\nexport function detectSpecDetailed(data: unknown): SpecDetectionResult {\n const result: SpecDetectionResult = {\n spec: null,\n confidence: 'low',\n indicators: [],\n warnings: [],\n };\n\n if (!data || typeof data !== 'object') {\n result.indicators.push('Input is not an object');\n return result;\n }\n\n const obj = data as Record<string, unknown>;\n const dataObj = (obj.data && typeof obj.data === 'object' ? obj.data : null) as Record<\n string,\n unknown\n > | null;\n\n // Check for explicit spec markers (HIGH confidence)\n\n // Explicit v3 spec marker\n if (obj.spec === 'chara_card_v3') {\n result.spec = 'v3';\n result.confidence = 'high';\n result.indicators.push('spec field is \"chara_card_v3\"');\n\n // Check for inconsistencies\n if (obj.spec_version && obj.spec_version !== '3.0') {\n result.warnings.push(`spec_version \"${obj.spec_version}\" inconsistent with v3 spec`);\n }\n\n return result;\n }\n\n // Explicit v2 spec marker\n if (obj.spec === 'chara_card_v2') {\n result.spec = 'v2';\n result.confidence = 'high';\n result.indicators.push('spec field is \"chara_card_v2\"');\n\n // Check for inconsistencies - V3-only fields in V2 card\n if (dataObj) {\n for (const field of V3_ONLY_FIELDS) {\n if (field in dataObj) {\n result.warnings.push(`V3-only field \"${field}\" found in V2 card`);\n }\n }\n }\n\n if (obj.spec_version && obj.spec_version !== '2.0') {\n result.warnings.push(`spec_version \"${obj.spec_version}\" inconsistent with v2 spec`);\n }\n\n return result;\n }\n\n // Check spec_version field (HIGH confidence)\n if (typeof obj.spec_version === 'string') {\n if (obj.spec_version.startsWith('3')) {\n result.spec = 'v3';\n result.confidence = 'high';\n result.indicators.push(`spec_version \"${obj.spec_version}\" starts with \"3\"`);\n return result;\n }\n if (obj.spec_version.startsWith('2')) {\n result.spec = 'v2';\n result.confidence = 'high';\n result.indicators.push(`spec_version \"${obj.spec_version}\" starts with \"2\"`);\n return result;\n }\n }\n\n if (obj.spec_version === 2.0 || obj.spec_version === 2) {\n result.spec = 'v2';\n result.confidence = 'high';\n result.indicators.push(`spec_version is numeric ${obj.spec_version}`);\n return result;\n }\n\n // Check for V3-only fields (MEDIUM confidence)\n if (dataObj) {\n const v3Fields: string[] = [];\n for (const field of V3_ONLY_FIELDS) {\n if (field in dataObj) {\n v3Fields.push(field);\n }\n }\n\n if (v3Fields.length > 0) {\n result.spec = 'v3';\n result.confidence = 'medium';\n result.indicators.push(`Has V3-only fields: ${v3Fields.join(', ')}`);\n return result;\n }\n }\n\n // Check root level for V3-only fields (also MEDIUM confidence)\n const rootV3Fields: string[] = [];\n for (const field of V3_ONLY_FIELDS) {\n if (field in obj) {\n rootV3Fields.push(field);\n }\n }\n if (rootV3Fields.length > 0) {\n result.spec = 'v3';\n result.confidence = 'medium';\n result.indicators.push(`Has V3-only fields at root: ${rootV3Fields.join(', ')}`);\n result.warnings.push('V3 fields found at root level instead of data object');\n return result;\n }\n\n // Wrapped format with data object (MEDIUM confidence)\n if (obj.spec && dataObj) {\n const dataName = dataObj.name;\n if (dataName && typeof dataName === 'string') {\n // Infer from spec string\n if (typeof obj.spec === 'string') {\n if (obj.spec.includes('v3') || obj.spec.includes('3')) {\n result.spec = 'v3';\n result.confidence = 'medium';\n result.indicators.push(`spec field \"${obj.spec}\" contains \"v3\" or \"3\"`);\n return result;\n }\n if (obj.spec.includes('v2') || obj.spec.includes('2')) {\n result.spec = 'v2';\n result.confidence = 'medium';\n result.indicators.push(`spec field \"${obj.spec}\" contains \"v2\" or \"2\"`);\n return result;\n }\n }\n // Default wrapped format to v3 (modern)\n result.spec = 'v3';\n result.confidence = 'medium';\n result.indicators.push('Has wrapped format with spec and data.name');\n return result;\n }\n }\n\n // Unwrapped format - V1/V2 like structure (MEDIUM confidence)\n if (obj.name && typeof obj.name === 'string') {\n if ('description' in obj || 'personality' in obj || 'scenario' in obj) {\n result.spec = 'v2';\n result.confidence = 'medium';\n result.indicators.push('Unwrapped format with name, description/personality/scenario');\n return result;\n }\n }\n\n // Check if data object has card-like structure without spec (LOW confidence)\n if (dataObj && typeof dataObj.name === 'string') {\n if ('description' in dataObj || 'personality' in dataObj) {\n result.spec = 'v2';\n result.confidence = 'low';\n result.indicators.push('Has data object with name and card fields, but no spec');\n result.warnings.push('Missing spec field');\n return result;\n }\n }\n\n result.indicators.push('No card structure detected');\n return result;\n}\n\n/**\n * Check if card has a lorebook\n */\nexport function hasLorebook(data: unknown): boolean {\n if (!data || typeof data !== 'object') return false;\n const obj = data as Record<string, unknown>;\n\n // Check wrapped format\n const wrapped = obj.data as Record<string, unknown> | undefined;\n if (wrapped?.character_book) {\n const book = wrapped.character_book as Record<string, unknown>;\n if (Array.isArray(book.entries) && book.entries.length > 0) return true;\n }\n\n // Check unwrapped format\n if (obj.character_book) {\n const book = obj.character_book as Record<string, unknown>;\n if (Array.isArray(book.entries) && book.entries.length > 0) return true;\n }\n\n return false;\n}\n\n/**\n * Check if data looks like a valid card structure\n */\nexport function looksLikeCard(data: unknown): boolean {\n if (!data || typeof data !== 'object') return false;\n const obj = data as Record<string, unknown>;\n\n // Has explicit spec marker\n if (obj.spec === 'chara_card_v2' || obj.spec === 'chara_card_v3') {\n return true;\n }\n\n // Has wrapped data with name\n if (obj.data && typeof obj.data === 'object') {\n const dataObj = obj.data as Record<string, unknown>;\n if (typeof dataObj.name === 'string' && dataObj.name.length > 0) {\n return true;\n }\n }\n\n // Has unwrapped card-like structure\n if (typeof obj.name === 'string' && obj.name.length > 0) {\n if ('description' in obj || 'personality' in obj || 'first_mes' in obj) {\n return true;\n }\n }\n\n return false;\n}\n","/**\n * Card Normalizer\n *\n * Handles normalization of malformed card data from various sources.\n * Fixes common issues like wrong spec values, misplaced fields, missing required fields.\n */\n\nimport type { CCv2Data, CCv2Wrapped, CCv2CharacterBook, CCv2LorebookEntry } from './ccv2.js';\nimport type { CCv3Data, CCv3CharacterBook, CCv3LorebookEntry } from './ccv3.js';\nimport { detectSpec } from './detection.js';\n\n/**\n * Position values as numbers (non-standard) and their string equivalents\n */\nconst POSITION_MAP: Record<number, 'before_char' | 'after_char'> = {\n 0: 'before_char',\n 1: 'after_char',\n};\n\n/**\n * V3-only lorebook entry fields that should be moved to extensions for V2\n */\nconst V3_ONLY_ENTRY_FIELDS = [\n 'probability',\n 'depth',\n 'group',\n 'scan_frequency',\n 'use_regex',\n 'selective_logic',\n 'role',\n 'automation_id',\n] as const;\n\n/**\n * Required V2 card fields with their defaults\n */\nconst V2_REQUIRED_DEFAULTS: Partial<CCv2Data> = {\n name: '',\n description: '',\n personality: '',\n scenario: '',\n first_mes: '',\n mes_example: '',\n};\n\n/**\n * Required V3 card fields with their defaults\n */\nconst V3_REQUIRED_DEFAULTS: Partial<CCv3Data['data']> = {\n name: '',\n description: '',\n personality: '',\n scenario: '',\n first_mes: '',\n mes_example: '',\n creator: '',\n character_version: '1.0',\n tags: [],\n group_only_greetings: [],\n};\n\n/**\n * Fields that belong at root level for wrapped format\n */\nconst _ROOT_FIELDS = ['spec', 'spec_version', 'data'] as const;\n\n/**\n * Fields that belong in the data object\n */\nconst DATA_FIELDS = [\n 'name',\n 'description',\n 'personality',\n 'scenario',\n 'first_mes',\n 'mes_example',\n 'creator_notes',\n 'system_prompt',\n 'post_history_instructions',\n 'alternate_greetings',\n 'character_book',\n 'tags',\n 'creator',\n 'character_version',\n 'extensions',\n 'assets',\n 'nickname',\n 'creator_notes_multilingual',\n 'source',\n 'creation_date',\n 'modification_date',\n 'group_only_greetings',\n] as const;\n\n/**\n * Deep clone an object without mutating the original\n */\nfunction deepClone<T>(obj: T): T {\n if (obj === null || obj === undefined) {\n return obj;\n }\n if (Array.isArray(obj)) {\n return obj.map((item) => deepClone(item)) as T;\n }\n if (typeof obj === 'object') {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {\n result[key] = deepClone(value);\n }\n return result as T;\n }\n return obj;\n}\n\n/**\n * Check if a timestamp is in milliseconds (13+ digits)\n */\nfunction isMilliseconds(timestamp: number): boolean {\n // Timestamps before year 2001 in seconds: < 1000000000\n // Timestamps in milliseconds are typically 13 digits: 1000000000000+\n return timestamp > 10000000000;\n}\n\n/**\n * CardNormalizer - handles normalization of malformed card data\n */\nexport const CardNormalizer = {\n /**\n * Normalize card data to valid schema format.\n *\n * Handles:\n * - Fixing spec/spec_version values\n * - Moving misplaced fields to correct locations\n * - Adding missing required fields with defaults\n * - Handling hybrid formats (fields at root AND in data object)\n *\n * @param data - Raw card data (potentially malformed)\n * @param spec - Target spec version\n * @returns Normalized card data (does not mutate input)\n */\n normalize(data: unknown, spec: 'v2' | 'v3'): CCv2Wrapped | CCv3Data {\n if (!data || typeof data !== 'object') {\n // Return minimal valid card\n if (spec === 'v3') {\n return {\n spec: 'chara_card_v3',\n spec_version: '3.0',\n data: { ...V3_REQUIRED_DEFAULTS } as CCv3Data['data'],\n };\n }\n return {\n spec: 'chara_card_v2',\n spec_version: '2.0',\n data: { ...V2_REQUIRED_DEFAULTS } as CCv2Data,\n };\n }\n\n const obj = data as Record<string, unknown>;\n const result: Record<string, unknown> = {};\n\n // Build merged data object from root fields + existing data object\n const existingData = (obj.data && typeof obj.data === 'object' ? obj.data : {}) as Record<\n string,\n unknown\n >;\n const mergedData: Record<string, unknown> = {};\n\n // Copy existing data first\n for (const [key, value] of Object.entries(existingData)) {\n mergedData[key] = deepClone(value);\n }\n\n // Move any misplaced root-level data fields into data object\n // (ChubAI hybrid format fix)\n for (const field of DATA_FIELDS) {\n if (field in obj && !(field in mergedData)) {\n mergedData[field] = deepClone(obj[field]);\n }\n }\n\n // Handle character_book: null -> remove entirely\n if (mergedData.character_book === null) {\n delete mergedData.character_book;\n }\n\n // Normalize character_book if present\n if (mergedData.character_book && typeof mergedData.character_book === 'object') {\n mergedData.character_book = this.normalizeCharacterBook(\n mergedData.character_book as Record<string, unknown>,\n spec\n );\n }\n\n // Apply defaults for required fields\n const defaults = spec === 'v3' ? V3_REQUIRED_DEFAULTS : V2_REQUIRED_DEFAULTS;\n for (const [key, defaultValue] of Object.entries(defaults)) {\n if (!(key in mergedData) || mergedData[key] === undefined) {\n mergedData[key] = Array.isArray(defaultValue) ? [...defaultValue] : defaultValue;\n }\n }\n\n // Ensure arrays are actually arrays\n if (mergedData.tags && !Array.isArray(mergedData.tags)) {\n mergedData.tags = [];\n }\n if (mergedData.alternate_greetings && !Array.isArray(mergedData.alternate_greetings)) {\n mergedData.alternate_greetings = [];\n }\n if (spec === 'v3') {\n if (\n mergedData.group_only_greetings &&\n !Array.isArray(mergedData.group_only_greetings)\n ) {\n mergedData.group_only_greetings = [];\n }\n }\n\n // Build result with correct spec\n if (spec === 'v3') {\n result.spec = 'chara_card_v3';\n result.spec_version = '3.0';\n result.data = this.fixTimestampsInner(mergedData);\n } else {\n result.spec = 'chara_card_v2';\n result.spec_version = '2.0';\n result.data = mergedData;\n }\n\n return result as unknown as CCv2Wrapped | CCv3Data;\n },\n\n /**\n * Normalize a character book (lorebook).\n *\n * Handles:\n * - Ensuring required fields exist\n * - Converting numeric position values to string enums\n * - Moving V3-only fields to extensions for V2 compatibility\n *\n * @param book - Raw character book data\n * @param spec - Target spec version\n * @returns Normalized character book\n */\n normalizeCharacterBook(\n book: Record<string, unknown>,\n spec: 'v2' | 'v3'\n ): CCv2CharacterBook | CCv3CharacterBook {\n const result: Record<string, unknown> = {};\n\n // Copy book-level fields\n if (book.name !== undefined) result.name = book.name;\n if (book.description !== undefined) result.description = book.description;\n if (book.scan_depth !== undefined) result.scan_depth = book.scan_depth;\n if (book.token_budget !== undefined) result.token_budget = book.token_budget;\n if (book.recursive_scanning !== undefined)\n result.recursive_scanning = book.recursive_scanning;\n if (book.extensions !== undefined) result.extensions = deepClone(book.extensions);\n\n // Normalize entries\n const entries = Array.isArray(book.entries) ? book.entries : [];\n result.entries = entries.map((entry) =>\n this.normalizeEntry(entry as Record<string, unknown>, spec)\n );\n\n return result as unknown as CCv2CharacterBook | CCv3CharacterBook;\n },\n\n /**\n * Normalize a single lorebook entry.\n *\n * Handles:\n * - Converting numeric position to string enum\n * - Moving V3-only fields to extensions for V2\n * - Ensuring required fields exist\n *\n * @param entry - Raw entry data\n * @param spec - Target spec version\n * @returns Normalized entry\n */\n normalizeEntry(\n entry: Record<string, unknown>,\n spec: 'v2' | 'v3'\n ): CCv2LorebookEntry | CCv3LorebookEntry {\n const result: Record<string, unknown> = {};\n\n // Required fields with defaults\n result.keys = Array.isArray(entry.keys) ? [...entry.keys] : [];\n result.content = typeof entry.content === 'string' ? entry.content : '';\n result.enabled = entry.enabled !== false; // default true\n result.insertion_order =\n typeof entry.insertion_order === 'number' ? entry.insertion_order : 0;\n\n // For V2, extensions is required\n if (spec === 'v2') {\n result.extensions =\n entry.extensions && typeof entry.extensions === 'object'\n ? deepClone(entry.extensions)\n : {};\n }\n\n // Optional fields\n if (entry.case_sensitive !== undefined) result.case_sensitive = entry.case_sensitive;\n if (entry.name !== undefined) result.name = entry.name;\n if (entry.priority !== undefined) result.priority = entry.priority;\n if (entry.id !== undefined) result.id = entry.id;\n if (entry.comment !== undefined) result.comment = entry.comment;\n if (entry.selective !== undefined) result.selective = entry.selective;\n if (entry.secondary_keys !== undefined) {\n result.secondary_keys = Array.isArray(entry.secondary_keys)\n ? [...entry.secondary_keys]\n : [];\n }\n if (entry.constant !== undefined) result.constant = entry.constant;\n\n // Position: convert numeric to string enum\n if (entry.position !== undefined) {\n if (typeof entry.position === 'number') {\n result.position = POSITION_MAP[entry.position] || 'before_char';\n } else if (entry.position === 'before_char' || entry.position === 'after_char') {\n result.position = entry.position;\n }\n }\n\n // Handle V3-only fields\n if (spec === 'v3') {\n // Copy V3 fields directly\n if (entry.extensions !== undefined) result.extensions = deepClone(entry.extensions);\n for (const field of V3_ONLY_ENTRY_FIELDS) {\n if (entry[field] !== undefined) {\n result[field] = entry[field];\n }\n }\n } else {\n // V2: Move V3-only fields to extensions\n const ext = (result.extensions || {}) as Record<string, unknown>;\n for (const field of V3_ONLY_ENTRY_FIELDS) {\n if (entry[field] !== undefined) {\n ext[field] = entry[field];\n }\n }\n result.extensions = ext;\n }\n\n return result as unknown as CCv2LorebookEntry | CCv3LorebookEntry;\n },\n\n /**\n * Fix CharacterTavern timestamp format (milliseconds -> seconds).\n *\n * CCv3 spec requires timestamps in seconds (Unix epoch).\n * CharacterTavern exports timestamps in milliseconds.\n *\n * @param data - V3 card data\n * @returns Card data with fixed timestamps (does not mutate input)\n */\n fixTimestamps(data: CCv3Data): CCv3Data {\n const result = deepClone(data);\n result.data = this.fixTimestampsInner(\n result.data as unknown as Record<string, unknown>\n ) as unknown as CCv3Data['data'];\n return result;\n },\n\n /**\n * Internal: fix timestamps in data object\n */\n fixTimestampsInner(data: Record<string, unknown>): Record<string, unknown> {\n const result = { ...data };\n\n if (typeof result.creation_date === 'number') {\n if (isMilliseconds(result.creation_date)) {\n result.creation_date = Math.floor(result.creation_date / 1000);\n }\n // Sanitize negative timestamps (.NET default dates like 0001-01-01)\n if ((result.creation_date as number) < 0) {\n delete result.creation_date;\n }\n }\n\n if (typeof result.modification_date === 'number') {\n if (isMilliseconds(result.modification_date)) {\n result.modification_date = Math.floor(result.modification_date / 1000);\n }\n // Sanitize negative timestamps (.NET default dates like 0001-01-01)\n if ((result.modification_date as number) < 0) {\n delete result.modification_date;\n }\n }\n\n return result;\n },\n\n /**\n * Auto-detect spec and normalize.\n *\n * @param data - Raw card data\n * @returns Normalized card data, or null if not a valid card\n */\n autoNormalize(data: unknown): CCv2Wrapped | CCv3Data | null {\n const spec = detectSpec(data);\n if (!spec) return null;\n\n // V1 cards get upgraded to V2\n const targetSpec = spec === 'v3' ? 'v3' : 'v2';\n return this.normalize(data, targetSpec);\n },\n};\n\nexport type { CCv2Wrapped, CCv3Data };\n","/**\n * Validation Utilities\n *\n * Helper functions for Zod validation with Foundry error integration.\n */\n\nimport { z } from 'zod';\n\n/**\n * Convert Zod error to human-readable message\n */\nexport function zodErrorToMessage(zodError: z.ZodError, context?: string): string {\n const messages = zodError.errors.map((err) => {\n const path = err.path.length > 0 ? `${err.path.join('.')}: ` : '';\n return `${path}${err.message}`;\n });\n\n const message = messages.join('; ');\n return context ? `${context} - ${message}` : message;\n}\n\n/**\n * Get the first error field from Zod error\n */\nexport function getFirstErrorField(zodError: z.ZodError): string | undefined {\n return zodError.errors[0]?.path[0]?.toString();\n}\n\n/**\n * Safe parse with detailed error information\n */\nexport function safeParse<T>(\n schema: z.ZodSchema<T>,\n data: unknown\n): { success: true; data: T } | { success: false; error: string; field?: string } {\n const result = schema.safeParse(data);\n\n if (result.success) {\n return { success: true, data: result.data };\n }\n\n return {\n success: false,\n error: zodErrorToMessage(result.error),\n field: getFirstErrorField(result.error),\n };\n}\n"],"mappings":";AAMA,SAAS,SAAS;ACAlB,SAAS,KAAAA,UAAS;ACAlB,SAAS,KAAAA,UAAS;AMAlB,OAAkB;ARSX,IAAM,gBAAgB,EAAE,OAAO,EAAE,SAAS;AAK1C,IAAM,aAAa,EAAE,OAAO,EAAE,KAAK;AAKnC,IAAM,aAAa,EAAE,KAAK,CAAC,MAAM,IAAI,CAAC;AAKtC,IAAM,qBAAqB,EAAE,KAAK;EACvC;;EACA;;EACA;;EACA;;EACA;;EACA;;EACA;;EACA;;AACF,CAAC;AAKM,IAAM,sBAAsB,EAAE,KAAK,CAAC,WAAW,aAAa,QAAQ,CAAC;AAKrE,IAAM,kBAAkB,EAAE,KAAK;EACpC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;AACF,CAAC;AAKM,IAAM,wBAAwB,EAAE,OAAO;EAC5C,MAAM;EACN,KAAK,EAAE,OAAO;EACd,MAAM,EAAE,OAAO;EACf,KAAK,EAAE,OAAO;AAChB,CAAC;AAKM,IAAM,uBAAuB,EAAE,OAAO;EAC3C,YAAY;EACZ,MAAM,EAAE,WAAW,UAAU;EAC7B,UAAU,EAAE,OAAO;AACrB,CAAC;AC9DM,IAAM,0BAA0BA,GAAE,OAAO;EAC9C,MAAMA,GAAE,MAAMA,GAAE,OAAO,CAAC,EAAE,SAAS;;EACnC,SAASA,GAAE,OAAO;EAClB,SAASA,GAAE,QAAQ,EAAE,QAAQ,IAAI;;EACjC,iBAAiBA,GAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,CAAC;;EAEtD,YAAYA,GAAE,OAAOA,GAAE,QAAQ,CAAC,EAAE,SAAS;EAC3C,gBAAgBA,GAAE,QAAQ,EAAE,SAAS,EAAE,SAAS;EAChD,MAAMA,GAAE,OAAO,EAAE,SAAS;EAC1B,UAAUA,GAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS;EAC/C,IAAIA,GAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS;EACzC,SAASA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS;EACxC,WAAWA,GAAE,QAAQ,EAAE,SAAS,EAAE,SAAS;EAC3C,gBAAgBA,GAAE,MAAMA,GAAE,OAAO,CAAC,EAAE,SAAS,EAAE,SAAS;EACxD,UAAUA,GAAE,QAAQ,EAAE,SAAS,EAAE,SAAS;EAC1C,UAAUA,GAAE,MAAM,CAACA,GAAE,KAAK,CAAC,eAAe,YAAY,CAAC,GAAGA,GAAE,OAAO,EAAE,IAAI,GAAGA,GAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,SAAS,EAAE,SAAS;AAClH,CAAC,EAAE,YAAY;AAKR,IAAM,0BAA0BA,GAAE,OAAO;EAC9C,MAAMA,GAAE,OAAO,EAAE,SAAS;EAC1B,aAAaA,GAAE,OAAO,EAAE,SAAS;EACjC,YAAYA,GAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS;EACpD,cAAcA,GAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS;EACtD,oBAAoBA,GAAE,QAAQ,EAAE,SAAS;EACzC,YAAYA,GAAE,OAAOA,GAAE,QAAQ,CAAC,EAAE,SAAS;EAC3C,SAASA,GAAE,MAAM,uBAAuB;AAC1C,CAAC;AAKM,IAAM,iBAAiBA,GAAE,OAAO;;EAErC,MAAMA,GAAE,OAAO,EAAE,QAAQ,EAAE;EAC3B,aAAaA,GAAE,OAAO,EAAE,QAAQ,EAAE;EAClC,aAAaA,GAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE;;EAC7C,UAAUA,GAAE,OAAO,EAAE,QAAQ,EAAE;EAC/B,WAAWA,GAAE,OAAO,EAAE,QAAQ,EAAE;EAChC,aAAaA,GAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE;;;EAE7C,eAAeA,GAAE,OAAO,EAAE,SAAS;EACnC,eAAeA,GAAE,OAAO,EAAE,SAAS;EACnC,2BAA2BA,GAAE,OAAO,EAAE,SAAS;EAC/C,qBAAqBA,GAAE,MAAMA,GAAE,OAAO,CAAC,EAAE,SAAS;EAClD,gBAAgB,wBAAwB,SAAS,EAAE,SAAS;EAC5D,MAAMA,GAAE,MAAMA,GAAE,OAAO,CAAC,EAAE,SAAS;EACnC,SAASA,GAAE,OAAO,EAAE,SAAS;EAC7B,mBAAmBA,GAAE,OAAO,EAAE,SAAS;EACvC,YAAYA,GAAE,OAAOA,GAAE,QAAQ,CAAC,EAAE,SAAS;AAC7C,CAAC;AAKM,IAAM,oBAAoBA,GAAE,OAAO;EACxC,MAAMA,GAAE,QAAQ,eAAe;EAC/B,cAAcA,GAAE,QAAQ,KAAK;EAC7B,MAAM;AACR,CAAC;AAiCM,SAAS,YAAY,MAAoC;AAC9D,SAAO,kBAAkB,UAAU,IAAI,EAAE;AAC3C;AAKO,SAAS,aAAa,MAA+C;AAC1E,SACE,kBAAkB,UAAU,IAAI,EAAE,WAClC,eAAe,UAAU,IAAI,EAAE;AAEnC;AAKO,SAAS,eAAe,MAA4B;AACzD,SAAO,kBAAkB,MAAM,IAAI;AACrC;AAKO,SAAS,YAAY,MAAyB;AACnD,SAAO,eAAe,MAAM,IAAI;AAClC;AAMO,SAAS,mBAAmB,MAAwE;AACzG,MAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO;AAC9C,QAAM,MAAM;AACZ,SACE,IAAI,SAAS,mBACb,IAAI,SAAS,QACb,OAAO,IAAI,SAAS;AAExB;AASO,SAAS,UAAU,MAAwC;AAEhE,MAAI,mBAAmB,IAAI,GAAG;AAC5B,WAAO,KAAK;EACd;AACA,SAAO;AACT;ACpJO,IAAM,0BAA0BA,GAAE,OAAO;EAC9C,MAAMA,GAAE,MAAMA,GAAE,OAAO,CAAC,EAAE,SAAS;;EACnC,SAASA,GAAE,OAAO;EAClB,SAASA,GAAE,QAAQ,EAAE,QAAQ,IAAI;;EACjC,iBAAiBA,GAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,CAAC;;EAEtD,gBAAgBA,GAAE,QAAQ,EAAE,SAAS,EAAE,SAAS;EAChD,MAAMA,GAAE,OAAO,EAAE,SAAS;EAC1B,UAAUA,GAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS;EAC/C,IAAIA,GAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS;EACzC,SAASA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS;EACxC,WAAWA,GAAE,QAAQ,EAAE,SAAS,EAAE,SAAS;EAC3C,gBAAgBA,GAAE,MAAMA,GAAE,OAAO,CAAC,EAAE,SAAS,EAAE,SAAS;EACxD,UAAUA,GAAE,QAAQ,EAAE,SAAS,EAAE,SAAS;EAC1C,UAAUA,GAAE,MAAM,CAACA,GAAE,KAAK,CAAC,eAAe,YAAY,CAAC,GAAGA,GAAE,OAAO,EAAE,IAAI,GAAGA,GAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,SAAS,EAAE,SAAS;EAChH,YAAYA,GAAE,OAAOA,GAAE,QAAQ,CAAC,EAAE,SAAS;;EAE3C,eAAeA,GAAE,OAAO,EAAE,SAAS;EACnC,MAAMA,GAAE,MAAM,CAACA,GAAE,KAAK,CAAC,UAAU,QAAQ,WAAW,CAAC,GAAGA,GAAE,OAAO,EAAE,IAAI,CAAC,CAAC,EAAE,SAAS,EAAE,SAAS;EAC/F,OAAOA,GAAE,OAAO,EAAE,SAAS;EAC3B,gBAAgBA,GAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS;EACxD,aAAaA,GAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS;;EACjD,WAAWA,GAAE,QAAQ,EAAE,SAAS;EAChC,OAAOA,GAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS;EAC/C,iBAAiBA,GAAE,MAAM,CAACA,GAAE,KAAK,CAAC,OAAO,KAAK,CAAC,GAAGA,GAAE,OAAO,EAAE,IAAI,CAAC,CAAC,EAAE,SAAS;AAChF,CAAC,EAAE,YAAY;AAKR,IAAM,0BAA0BA,GAAE,OAAO;EAC9C,MAAMA,GAAE,OAAO,EAAE,SAAS;EAC1B,aAAaA,GAAE,OAAO,EAAE,SAAS;EACjC,YAAYA,GAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS;EACpD,cAAcA,GAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS;EACtD,oBAAoBA,GAAE,QAAQ,EAAE,SAAS;EACzC,YAAYA,GAAE,OAAOA,GAAE,QAAQ,CAAC,EAAE,SAAS;EAC3C,SAASA,GAAE,MAAM,uBAAuB;AAC1C,CAAC;AASM,IAAM,sBAAsBA,GAAE,OAAO;;EAE1C,MAAMA,GAAE,OAAO,EAAE,QAAQ,EAAE;EAC3B,aAAaA,GAAE,OAAO,EAAE,QAAQ,EAAE;EAClC,aAAaA,GAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE;;EAC7C,UAAUA,GAAE,OAAO,EAAE,QAAQ,EAAE;EAC/B,WAAWA,GAAE,OAAO,EAAE,QAAQ,EAAE;EAChC,aAAaA,GAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE;;;EAE7C,SAASA,GAAE,OAAO,EAAE,QAAQ,EAAE;EAC9B,mBAAmBA,GAAE,OAAO,EAAE,QAAQ,EAAE;EACxC,MAAMA,GAAE,MAAMA,GAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,CAAC;EACpC,sBAAsBA,GAAE,MAAMA,GAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,CAAC;;EAEpD,eAAeA,GAAE,OAAO,EAAE,SAAS;EACnC,eAAeA,GAAE,OAAO,EAAE,SAAS;EACnC,2BAA2BA,GAAE,OAAO,EAAE,SAAS;EAC/C,qBAAqBA,GAAE,MAAMA,GAAE,OAAO,CAAC,EAAE,SAAS;EAClD,gBAAgB,wBAAwB,SAAS,EAAE,SAAS;EAC5D,YAAYA,GAAE,OAAOA,GAAE,QAAQ,CAAC,EAAE,SAAS;;EAE3C,QAAQA,GAAE,MAAM,qBAAqB,EAAE,SAAS;EAChD,UAAUA,GAAE,OAAO,EAAE,SAAS;EAC9B,4BAA4BA,GAAE,OAAOA,GAAE,OAAO,CAAC,EAAE,SAAS;EAC1D,QAAQA,GAAE,MAAMA,GAAE,OAAO,CAAC,EAAE,SAAS;EACrC,eAAeA,GAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS;;EACvD,mBAAmBA,GAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS;;AAC7D,CAAC;AAKM,IAAM,iBAAiBA,GAAE,OAAO;EACrC,MAAMA,GAAE,QAAQ,eAAe;EAC/B,cAAcA,GAAE,QAAQ,KAAK;EAC7B,MAAM;AACR,CAAC;AAiCM,SAAS,SAAS,MAAiC;AACxD,SAAO,eAAe,UAAU,IAAI,EAAE;AACxC;AAKO,SAAS,YAAY,MAAyB;AACnD,SAAO,eAAe,MAAM,IAAI;AAClC;AAKO,SAAS,iBAAiB,MAA8B;AAC7D,SAAO,oBAAoB,MAAM,IAAI;AACvC;AAKO,SAAS,UAAU,MAA+B;AACvD,SAAO,KAAK;AACd;AAMO,SAAS,gBAAgB,MAAwE;AACtG,MAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO;AAC9C,QAAM,MAAM;AACZ,SACE,IAAI,SAAS,mBACb,IAAI,SAAS,QACb,OAAO,IAAI,SAAS;AAExB;AC5GO,SAAS,kBAAkB,YAA+C;AAC/E,MAAI,CAAC,WAAY,QAAO;AACxB,SAAO,YAAY,cAAc,UAAU;AAC7C;AAKO,SAAS,eAAe,YAA+C;AAC5E,MAAI,CAAC,WAAY,QAAO;AACxB,QAAM,OAAO,WAAW;AACxB,MAAI,CAAC,KAAM,QAAO;AAClB,SAAO,CAAC,CAAC,KAAK,iBAAiB,CAAC,CAAC,KAAK;AACxC;AAMO,SAAS,eAAe,YAA+C;AAC5E,MAAI,CAAC,WAAY,QAAO;AAExB,MAAI,kBAAkB,cAAc,WAAW,aAAc,QAAO;AAEpE,QAAM,OAAO,WAAW;AACxB,SAAO,CAAC,CAAC,MAAM;AACjB;ACzCO,SAAS,4BAA4C;AAC1D,SAAO;IACL,MAAM;IACN,aAAa;IACb,aAAa;IACb,UAAU;IACV,UAAU;IACV,YAAY;IACZ,oBAAoB,CAAC;IACrB,oBAAoB,CAAC;IACrB,MAAM,CAAC;IACP,YAAY,CAAC;EACf;AACF;AAsCO,SAAS,sBAAuC;AACrD,SAAO;IACL,uBAAuB;IACvB,yBAAyB;IACzB,qBAAqB;;IACrB,aAAa;IACb,sBAAsB;IACtB,mBAAmB;IACnB,qBAAqB;IACrB,YAAY;IACZ,mBAAmB;IACnB,gBAAgB;IAChB,gBAAgB;IAChB,oBAAoB;IACpB,QAAQ;MACN,aAAa;MACb,aAAa;MACb,UAAU;MACV,UAAU;MACV,YAAY;MACZ,cAAc;MACd,OAAO;IACT;EACF;AACF;ACxFO,SAAS,eAAe,MAAiD;AAE9E,QAAM,OAAO,YAAY;AAGzB,QAAM,eAAe,KAAK,uBAAuB,CAAC;AAClD,QAAM,wBAAwB,aAAa,SAAS;AACpD,QAAM,0BAA0B,aAAa;AAE7C,QAAM,sBAAsB,IAAI;AAGhC,QAAM,gBAAgB,KAAK;AAC3B,QAAMC,eAAc,CAAC,CAAC,iBAAiB,cAAc,QAAQ,SAAS;AACtE,QAAM,uBAAuB,eAAe,QAAQ,UAAU;AAG9D,QAAM,SAAS,OAAQ,KAAuB,UAAU,CAAC,IAAI,CAAC;AAC9D,QAAM,kBAAkB,CAAC,QAAQ,cAAc,WAAW,QAAQ;AAClE,QAAM,cAAc,OAAO;IACzB,CAAC,MACC,gBAAgB,SAAS,EAAE,IAAI,KAC/B,CAAC,OAAO,OAAO,QAAQ,QAAQ,KAAK,EAAE,SAAS,EAAE,IAAI,YAAY,CAAC;EACtE;AACA,QAAM,aAAa,YAAY,SAAS;AAGxC,QAAM,qBAAqB,oBAAoB,IAAI;AACnD,QAAM,oBAAoB,qBAAqB;AAG/C,QAAM,aAAa,KAAK,cAAc,CAAC;AACvC,QAAM,UAAU,kBAAkB,UAAU;AAC5C,QAAM,aAAa,eAAe,UAAU;AAC5C,QAAM,WAAW,eAAe,UAAU;AAC1C,QAAM,WAAW,qBAAqB,UAAU;AAGhD,QAAM,SAAS;IACb,aAAa;IACb,aAAa;IACb,UAAU;IACV,UAAU;IACV,YAAY;IACZ,cAAc;IACd,OAAO;EACT;AAEA,SAAO;IACL;IACA;IACA;IACA,aAAAA;IACA;IACA;IACA,qBAAqB;IACrB;IACA,mBAAmB;IACnB,gBAAgB;IAChB,gBAAgB;IAChB,oBAAoB;IACpB;EACF;AACF;AAMA,SAAS,oBAAoB,MAAwC;AACnE,QAAM,aAAa;IACjB,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,GAAI,KAAK,uBAAuB,CAAC;EACnC,EAAE,OAAO,CAAC,UAA2B,OAAO,UAAU,QAAQ;AAG9D,MAAI,0BAA0B,MAAM;AAClC,eAAW,KAAK,GAAI,KAAK,wBAAwB,CAAC,CAAE;EACtD;AAEA,MAAI,QAAQ;AACZ,QAAM,iBAAiB;AAEvB,aAAW,QAAQ,YAAY;AAC7B,UAAM,UAAU,KAAK,MAAM,cAAc;AACzC,QAAI,SAAS;AACX,eAAS,QAAQ;IACnB;EACF;AAEA,SAAO;AACT;AAMA,SAAS,qBAAqB,YAA8C;AAC1E,MAAI,CAAC,WAAW,MAAO,QAAO;AAC9B,QAAM,QAAQ,WAAW;AACzB,SAAO,CAAC,CAAC,MAAM;AACjB;AClIA,IAAM,iBAAiB,CAAC,wBAAwB,iBAAiB,qBAAqB,QAAQ;AAoBvF,SAAS,WAAW,MAA4B;AACrD,SAAO,mBAAmB,IAAI,EAAE;AAClC;AAMO,SAAS,mBAAmB,MAAoC;AACrE,QAAM,SAA8B;IAClC,MAAM;IACN,YAAY;IACZ,YAAY,CAAC;IACb,UAAU,CAAC;EACb;AAEA,MAAI,CAAC,QAAQ,OAAO,SAAS,UAAU;AACrC,WAAO,WAAW,KAAK,wBAAwB;AAC/C,WAAO;EACT;AAEA,QAAM,MAAM;AACZ,QAAM,UAAW,IAAI,QAAQ,OAAO,IAAI,SAAS,WAAW,IAAI,OAAO;AAQvE,MAAI,IAAI,SAAS,iBAAiB;AAChC,WAAO,OAAO;AACd,WAAO,aAAa;AACpB,WAAO,WAAW,KAAK,+BAA+B;AAGtD,QAAI,IAAI,gBAAgB,IAAI,iBAAiB,OAAO;AAClD,aAAO,SAAS,KAAK,iBAAiB,IAAI,YAAY,6BAA6B;IACrF;AAEA,WAAO;EACT;AAGA,MAAI,IAAI,SAAS,iBAAiB;AAChC,WAAO,OAAO;AACd,WAAO,aAAa;AACpB,WAAO,WAAW,KAAK,+BAA+B;AAGtD,QAAI,SAAS;AACX,iBAAW,SAAS,gBAAgB;AAClC,YAAI,SAAS,SAAS;AACpB,iBAAO,SAAS,KAAK,kBAAkB,KAAK,oBAAoB;QAClE;MACF;IACF;AAEA,QAAI,IAAI,gBAAgB,IAAI,iBAAiB,OAAO;AAClD,aAAO,SAAS,KAAK,iBAAiB,IAAI,YAAY,6BAA6B;IACrF;AAEA,WAAO;EACT;AAGA,MAAI,OAAO,IAAI,iBAAiB,UAAU;AACxC,QAAI,IAAI,aAAa,WAAW,GAAG,GAAG;AACpC,aAAO,OAAO;AACd,aAAO,aAAa;AACpB,aAAO,WAAW,KAAK,iBAAiB,IAAI,YAAY,mBAAmB;AAC3E,aAAO;IACT;AACA,QAAI,IAAI,aAAa,WAAW,GAAG,GAAG;AACpC,aAAO,OAAO;AACd,aAAO,aAAa;AACpB,aAAO,WAAW,KAAK,iBAAiB,IAAI,YAAY,mBAAmB;AAC3E,aAAO;IACT;EACF;AAEA,MAAI,IAAI,iBAAiB,KAAO,IAAI,iBAAiB,GAAG;AACtD,WAAO,OAAO;AACd,WAAO,aAAa;AACpB,WAAO,WAAW,KAAK,2BAA2B,IAAI,YAAY,EAAE;AACpE,WAAO;EACT;AAGA,MAAI,SAAS;AACX,UAAM,WAAqB,CAAC;AAC5B,eAAW,SAAS,gBAAgB;AAClC,UAAI,SAAS,SAAS;AACpB,iBAAS,KAAK,KAAK;MACrB;IACF;AAEA,QAAI,SAAS,SAAS,GAAG;AACvB,aAAO,OAAO;AACd,aAAO,aAAa;AACpB,aAAO,WAAW,KAAK,uBAAuB,SAAS,KAAK,IAAI,CAAC,EAAE;AACnE,aAAO;IACT;EACF;AAGA,QAAM,eAAyB,CAAC;AAChC,aAAW,SAAS,gBAAgB;AAClC,QAAI,SAAS,KAAK;AAChB,mBAAa,KAAK,KAAK;IACzB;EACF;AACA,MAAI,aAAa,SAAS,GAAG;AAC3B,WAAO,OAAO;AACd,WAAO,aAAa;AACpB,WAAO,WAAW,KAAK,+BAA+B,aAAa,KAAK,IAAI,CAAC,EAAE;AAC/E,WAAO,SAAS,KAAK,sDAAsD;AAC3E,WAAO;EACT;AAGA,MAAI,IAAI,QAAQ,SAAS;AACvB,UAAM,WAAW,QAAQ;AACzB,QAAI,YAAY,OAAO,aAAa,UAAU;AAE5C,UAAI,OAAO,IAAI,SAAS,UAAU;AAChC,YAAI,IAAI,KAAK,SAAS,IAAI,KAAK,IAAI,KAAK,SAAS,GAAG,GAAG;AACrD,iBAAO,OAAO;AACd,iBAAO,aAAa;AACpB,iBAAO,WAAW,KAAK,eAAe,IAAI,IAAI,wBAAwB;AACtE,iBAAO;QACT;AACA,YAAI,IAAI,KAAK,SAAS,IAAI,KAAK,IAAI,KAAK,SAAS,GAAG,GAAG;AACrD,iBAAO,OAAO;AACd,iBAAO,aAAa;AACpB,iBAAO,WAAW,KAAK,eAAe,IAAI,IAAI,wBAAwB;AACtE,iBAAO;QACT;MACF;AAEA,aAAO,OAAO;AACd,aAAO,aAAa;AACpB,aAAO,WAAW,KAAK,4CAA4C;AACnE,aAAO;IACT;EACF;AAGA,MAAI,IAAI,QAAQ,OAAO,IAAI,SAAS,UAAU;AAC5C,QAAI,iBAAiB,OAAO,iBAAiB,OAAO,cAAc,KAAK;AACrE,aAAO,OAAO;AACd,aAAO,aAAa;AACpB,aAAO,WAAW,KAAK,8DAA8D;AACrF,aAAO;IACT;EACF;AAGA,MAAI,WAAW,OAAO,QAAQ,SAAS,UAAU;AAC/C,QAAI,iBAAiB,WAAW,iBAAiB,SAAS;AACxD,aAAO,OAAO;AACd,aAAO,aAAa;AACpB,aAAO,WAAW,KAAK,wDAAwD;AAC/E,aAAO,SAAS,KAAK,oBAAoB;AACzC,aAAO;IACT;EACF;AAEA,SAAO,WAAW,KAAK,4BAA4B;AACnD,SAAO;AACT;AAKO,SAAS,YAAY,MAAwB;AAClD,MAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO;AAC9C,QAAM,MAAM;AAGZ,QAAM,UAAU,IAAI;AACpB,MAAI,SAAS,gBAAgB;AAC3B,UAAM,OAAO,QAAQ;AACrB,QAAI,MAAM,QAAQ,KAAK,OAAO,KAAK,KAAK,QAAQ,SAAS,EAAG,QAAO;EACrE;AAGA,MAAI,IAAI,gBAAgB;AACtB,UAAM,OAAO,IAAI;AACjB,QAAI,MAAM,QAAQ,KAAK,OAAO,KAAK,KAAK,QAAQ,SAAS,EAAG,QAAO;EACrE;AAEA,SAAO;AACT;AAKO,SAAS,cAAc,MAAwB;AACpD,MAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO;AAC9C,QAAM,MAAM;AAGZ,MAAI,IAAI,SAAS,mBAAmB,IAAI,SAAS,iBAAiB;AAChE,WAAO;EACT;AAGA,MAAI,IAAI,QAAQ,OAAO,IAAI,SAAS,UAAU;AAC5C,UAAM,UAAU,IAAI;AACpB,QAAI,OAAO,QAAQ,SAAS,YAAY,QAAQ,KAAK,SAAS,GAAG;AAC/D,aAAO;IACT;EACF;AAGA,MAAI,OAAO,IAAI,SAAS,YAAY,IAAI,KAAK,SAAS,GAAG;AACvD,QAAI,iBAAiB,OAAO,iBAAiB,OAAO,eAAe,KAAK;AACtE,aAAO;IACT;EACF;AAEA,SAAO;AACT;AChPA,IAAM,eAA6D;EACjE,GAAG;EACH,GAAG;AACL;AAKA,IAAM,uBAAuB;EAC3B;EACA;EACA;EACA;EACA;EACA;EACA;EACA;AACF;AAKA,IAAM,uBAA0C;EAC9C,MAAM;EACN,aAAa;EACb,aAAa;EACb,UAAU;EACV,WAAW;EACX,aAAa;AACf;AAKA,IAAM,uBAAkD;EACtD,MAAM;EACN,aAAa;EACb,aAAa;EACb,UAAU;EACV,WAAW;EACX,aAAa;EACb,SAAS;EACT,mBAAmB;EACnB,MAAM,CAAC;EACP,sBAAsB,CAAC;AACzB;AAUA,IAAM,cAAc;EAClB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;AACF;AAKA,SAAS,UAAa,KAAW;AAC/B,MAAI,QAAQ,QAAQ,QAAQ,QAAW;AACrC,WAAO;EACT;AACA,MAAI,MAAM,QAAQ,GAAG,GAAG;AACtB,WAAO,IAAI,IAAI,CAAC,SAAS,UAAU,IAAI,CAAC;EAC1C;AACA,MAAI,OAAO,QAAQ,UAAU;AAC3B,UAAM,SAAkC,CAAC;AACzC,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,GAA8B,GAAG;AACzE,aAAO,GAAG,IAAI,UAAU,KAAK;IAC/B;AACA,WAAO;EACT;AACA,SAAO;AACT;AAKA,SAAS,eAAe,WAA4B;AAGlD,SAAO,YAAY;AACrB;AAKO,IAAM,iBAAiB;;;;;;;;;;;;;;EAc5B,UAAU,MAAe,MAA2C;AAClE,QAAI,CAAC,QAAQ,OAAO,SAAS,UAAU;AAErC,UAAI,SAAS,MAAM;AACjB,eAAO;UACL,MAAM;UACN,cAAc;UACd,MAAM,EAAE,GAAG,qBAAqB;QAClC;MACF;AACA,aAAO;QACL,MAAM;QACN,cAAc;QACd,MAAM,EAAE,GAAG,qBAAqB;MAClC;IACF;AAEA,UAAM,MAAM;AACZ,UAAM,SAAkC,CAAC;AAGzC,UAAM,eAAgB,IAAI,QAAQ,OAAO,IAAI,SAAS,WAAW,IAAI,OAAO,CAAC;AAI7E,UAAM,aAAsC,CAAC;AAG7C,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,YAAY,GAAG;AACvD,iBAAW,GAAG,IAAI,UAAU,KAAK;IACnC;AAIA,eAAW,SAAS,aAAa;AAC/B,UAAI,SAAS,OAAO,EAAE,SAAS,aAAa;AAC1C,mBAAW,KAAK,IAAI,UAAU,IAAI,KAAK,CAAC;MAC1C;IACF;AAGA,QAAI,WAAW,mBAAmB,MAAM;AACtC,aAAO,WAAW;IACpB;AAGA,QAAI,WAAW,kBAAkB,OAAO,WAAW,mBAAmB,UAAU;AAC9E,iBAAW,iBAAiB,KAAK;QAC/B,WAAW;QACX;MACF;IACF;AAGA,UAAM,WAAW,SAAS,OAAO,uBAAuB;AACxD,eAAW,CAAC,KAAK,YAAY,KAAK,OAAO,QAAQ,QAAQ,GAAG;AAC1D,UAAI,EAAE,OAAO,eAAe,WAAW,GAAG,MAAM,QAAW;AACzD,mBAAW,GAAG,IAAI,MAAM,QAAQ,YAAY,IAAI,CAAC,GAAG,YAAY,IAAI;MACtE;IACF;AAGA,QAAI,WAAW,QAAQ,CAAC,MAAM,QAAQ,WAAW,IAAI,GAAG;AACtD,iBAAW,OAAO,CAAC;IACrB;AACA,QAAI,WAAW,uBAAuB,CAAC,MAAM,QAAQ,WAAW,mBAAmB,GAAG;AACpF,iBAAW,sBAAsB,CAAC;IACpC;AACA,QAAI,SAAS,MAAM;AACjB,UACE,WAAW,wBACX,CAAC,MAAM,QAAQ,WAAW,oBAAoB,GAC9C;AACA,mBAAW,uBAAuB,CAAC;MACrC;IACF;AAGA,QAAI,SAAS,MAAM;AACjB,aAAO,OAAO;AACd,aAAO,eAAe;AACtB,aAAO,OAAO,KAAK,mBAAmB,UAAU;IAClD,OAAO;AACL,aAAO,OAAO;AACd,aAAO,eAAe;AACtB,aAAO,OAAO;IAChB;AAEA,WAAO;EACT;;;;;;;;;;;;;EAcA,uBACE,MACA,MACuC;AACvC,UAAM,SAAkC,CAAC;AAGzC,QAAI,KAAK,SAAS,OAAW,QAAO,OAAO,KAAK;AAChD,QAAI,KAAK,gBAAgB,OAAW,QAAO,cAAc,KAAK;AAC9D,QAAI,KAAK,eAAe,OAAW,QAAO,aAAa,KAAK;AAC5D,QAAI,KAAK,iBAAiB,OAAW,QAAO,eAAe,KAAK;AAChE,QAAI,KAAK,uBAAuB;AAC9B,aAAO,qBAAqB,KAAK;AACnC,QAAI,KAAK,eAAe,OAAW,QAAO,aAAa,UAAU,KAAK,UAAU;AAGhF,UAAM,UAAU,MAAM,QAAQ,KAAK,OAAO,IAAI,KAAK,UAAU,CAAC;AAC9D,WAAO,UAAU,QAAQ;MAAI,CAAC,UAC5B,KAAK,eAAe,OAAkC,IAAI;IAC5D;AAEA,WAAO;EACT;;;;;;;;;;;;;EAcA,eACE,OACA,MACuC;AACvC,UAAM,SAAkC,CAAC;AAGzC,WAAO,OAAO,MAAM,QAAQ,MAAM,IAAI,IAAI,CAAC,GAAG,MAAM,IAAI,IAAI,CAAC;AAC7D,WAAO,UAAU,OAAO,MAAM,YAAY,WAAW,MAAM,UAAU;AACrE,WAAO,UAAU,MAAM,YAAY;AACnC,WAAO,kBACL,OAAO,MAAM,oBAAoB,WAAW,MAAM,kBAAkB;AAGtE,QAAI,SAAS,MAAM;AACjB,aAAO,aACL,MAAM,cAAc,OAAO,MAAM,eAAe,WAC5C,UAAU,MAAM,UAAU,IAC1B,CAAC;IACT;AAGA,QAAI,MAAM,mBAAmB,OAAW,QAAO,iBAAiB,MAAM;AACtE,QAAI,MAAM,SAAS,OAAW,QAAO,OAAO,MAAM;AAClD,QAAI,MAAM,aAAa,OAAW,QAAO,WAAW,MAAM;AAC1D,QAAI,MAAM,OAAO,OAAW,QAAO,KAAK,MAAM;AAC9C,QAAI,MAAM,YAAY,OAAW,QAAO,UAAU,MAAM;AACxD,QAAI,MAAM,cAAc,OAAW,QAAO,YAAY,MAAM;AAC5D,QAAI,MAAM,mBAAmB,QAAW;AACtC,aAAO,iBAAiB,MAAM,QAAQ,MAAM,cAAc,IACtD,CAAC,GAAG,MAAM,cAAc,IACxB,CAAC;IACP;AACA,QAAI,MAAM,aAAa,OAAW,QAAO,WAAW,MAAM;AAG1D,QAAI,MAAM,aAAa,QAAW;AAChC,UAAI,OAAO,MAAM,aAAa,UAAU;AACtC,eAAO,WAAW,aAAa,MAAM,QAAQ,KAAK;MACpD,WAAW,MAAM,aAAa,iBAAiB,MAAM,aAAa,cAAc;AAC9E,eAAO,WAAW,MAAM;MAC1B;IACF;AAGA,QAAI,SAAS,MAAM;AAEjB,UAAI,MAAM,eAAe,OAAW,QAAO,aAAa,UAAU,MAAM,UAAU;AAClF,iBAAW,SAAS,sBAAsB;AACxC,YAAI,MAAM,KAAK,MAAM,QAAW;AAC9B,iBAAO,KAAK,IAAI,MAAM,KAAK;QAC7B;MACF;IACF,OAAO;AAEL,YAAM,MAAO,OAAO,cAAc,CAAC;AACnC,iBAAW,SAAS,sBAAsB;AACxC,YAAI,MAAM,KAAK,MAAM,QAAW;AAC9B,cAAI,KAAK,IAAI,MAAM,KAAK;QAC1B;MACF;AACA,aAAO,aAAa;IACtB;AAEA,WAAO;EACT;;;;;;;;;;EAWA,cAAc,MAA0B;AACtC,UAAM,SAAS,UAAU,IAAI;AAC7B,WAAO,OAAO,KAAK;MACjB,OAAO;IACT;AACA,WAAO;EACT;;;;EAKA,mBAAmB,MAAwD;AACzE,UAAM,SAAS,EAAE,GAAG,KAAK;AAEzB,QAAI,OAAO,OAAO,kBAAkB,UAAU;AAC5C,UAAI,eAAe,OAAO,aAAa,GAAG;AACxC,eAAO,gBAAgB,KAAK,MAAM,OAAO,gBAAgB,GAAI;MAC/D;AAEA,UAAK,OAAO,gBAA2B,GAAG;AACxC,eAAO,OAAO;MAChB;IACF;AAEA,QAAI,OAAO,OAAO,sBAAsB,UAAU;AAChD,UAAI,eAAe,OAAO,iBAAiB,GAAG;AAC5C,eAAO,oBAAoB,KAAK,MAAM,OAAO,oBAAoB,GAAI;MACvE;AAEA,UAAK,OAAO,oBAA+B,GAAG;AAC5C,eAAO,OAAO;MAChB;IACF;AAEA,WAAO;EACT;;;;;;;EAQA,cAAc,MAA8C;AAC1D,UAAM,OAAO,WAAW,IAAI;AAC5B,QAAI,CAAC,KAAM,QAAO;AAGlB,UAAM,aAAa,SAAS,OAAO,OAAO;AAC1C,WAAO,KAAK,UAAU,MAAM,UAAU;EACxC;AACF;AC3YO,SAAS,kBAAkB,UAAsB,SAA0B;AAChF,QAAM,WAAW,SAAS,OAAO,IAAI,CAAC,QAAQ;AAC5C,UAAM,OAAO,IAAI,KAAK,SAAS,IAAI,GAAG,IAAI,KAAK,KAAK,GAAG,CAAC,OAAO;AAC/D,WAAO,GAAG,IAAI,GAAG,IAAI,OAAO;EAC9B,CAAC;AAED,QAAM,UAAU,SAAS,KAAK,IAAI;AAClC,SAAO,UAAU,GAAG,OAAO,MAAM,OAAO,KAAK;AAC/C;AAKO,SAAS,mBAAmB,UAA0C;AAC3E,SAAO,SAAS,OAAO,CAAC,GAAG,KAAK,CAAC,GAAG,SAAS;AAC/C;AAKO,SAAS,UACd,QACA,MACgF;AAChF,QAAM,SAAS,OAAO,UAAU,IAAI;AAEpC,MAAI,OAAO,SAAS;AAClB,WAAO,EAAE,SAAS,MAAM,MAAM,OAAO,KAAK;EAC5C;AAEA,SAAO;IACL,SAAS;IACT,OAAO,kBAAkB,OAAO,KAAK;IACrC,OAAO,mBAAmB,OAAO,KAAK;EACxC;AACF;","names":["z","hasLorebook"]}
|
package/dist/voxta.cjs
CHANGED
|
@@ -6662,18 +6662,18 @@ var CCv2LorebookEntrySchema = import_zod2.z.object({
|
|
|
6662
6662
|
content: import_zod2.z.string(),
|
|
6663
6663
|
enabled: import_zod2.z.boolean().default(true),
|
|
6664
6664
|
// Default to enabled if missing
|
|
6665
|
-
insertion_order: import_zod2.z.number().int().default(0),
|
|
6665
|
+
insertion_order: import_zod2.z.number().int().nullable().default(0),
|
|
6666
6666
|
// Optional fields - be lenient with nulls since wild data has them
|
|
6667
6667
|
extensions: import_zod2.z.record(import_zod2.z.unknown()).optional(),
|
|
6668
6668
|
case_sensitive: import_zod2.z.boolean().nullable().optional(),
|
|
6669
6669
|
name: import_zod2.z.string().optional(),
|
|
6670
|
-
priority: import_zod2.z.number().int().optional(),
|
|
6671
|
-
id: import_zod2.z.number().int().optional(),
|
|
6672
|
-
comment: import_zod2.z.string().optional(),
|
|
6670
|
+
priority: import_zod2.z.number().int().nullable().optional(),
|
|
6671
|
+
id: import_zod2.z.number().int().nullable().optional(),
|
|
6672
|
+
comment: import_zod2.z.string().nullable().optional(),
|
|
6673
6673
|
selective: import_zod2.z.boolean().nullable().optional(),
|
|
6674
|
-
secondary_keys: import_zod2.z.array(import_zod2.z.string()).optional(),
|
|
6674
|
+
secondary_keys: import_zod2.z.array(import_zod2.z.string()).nullable().optional(),
|
|
6675
6675
|
constant: import_zod2.z.boolean().nullable().optional(),
|
|
6676
|
-
position: import_zod2.z.union([import_zod2.z.enum(["before_char", "after_char"]), import_zod2.z.number().int()]).nullable().optional()
|
|
6676
|
+
position: import_zod2.z.union([import_zod2.z.enum(["before_char", "after_char"]), import_zod2.z.number().int(), import_zod2.z.literal("")]).nullable().optional()
|
|
6677
6677
|
}).passthrough();
|
|
6678
6678
|
var CCv2CharacterBookSchema = import_zod2.z.object({
|
|
6679
6679
|
name: import_zod2.z.string().optional(),
|
|
@@ -6716,17 +6716,17 @@ var CCv3LorebookEntrySchema = import_zod3.z.object({
|
|
|
6716
6716
|
content: import_zod3.z.string(),
|
|
6717
6717
|
enabled: import_zod3.z.boolean().default(true),
|
|
6718
6718
|
// Default to enabled if missing
|
|
6719
|
-
insertion_order: import_zod3.z.number().int().default(0),
|
|
6719
|
+
insertion_order: import_zod3.z.number().int().nullable().default(0),
|
|
6720
6720
|
// Optional fields - be lenient with nulls since wild data has them
|
|
6721
6721
|
case_sensitive: import_zod3.z.boolean().nullable().optional(),
|
|
6722
6722
|
name: import_zod3.z.string().optional(),
|
|
6723
|
-
priority: import_zod3.z.number().int().optional(),
|
|
6724
|
-
id: import_zod3.z.number().int().optional(),
|
|
6725
|
-
comment: import_zod3.z.string().optional(),
|
|
6723
|
+
priority: import_zod3.z.number().int().nullable().optional(),
|
|
6724
|
+
id: import_zod3.z.number().int().nullable().optional(),
|
|
6725
|
+
comment: import_zod3.z.string().nullable().optional(),
|
|
6726
6726
|
selective: import_zod3.z.boolean().nullable().optional(),
|
|
6727
|
-
secondary_keys: import_zod3.z.array(import_zod3.z.string()).optional(),
|
|
6727
|
+
secondary_keys: import_zod3.z.array(import_zod3.z.string()).nullable().optional(),
|
|
6728
6728
|
constant: import_zod3.z.boolean().nullable().optional(),
|
|
6729
|
-
position: import_zod3.z.union([import_zod3.z.enum(["before_char", "after_char"]), import_zod3.z.number().int()]).nullable().optional(),
|
|
6729
|
+
position: import_zod3.z.union([import_zod3.z.enum(["before_char", "after_char"]), import_zod3.z.number().int(), import_zod3.z.literal("")]).nullable().optional(),
|
|
6730
6730
|
extensions: import_zod3.z.record(import_zod3.z.unknown()).optional(),
|
|
6731
6731
|
// v3 specific - also lenient with types since SillyTavern uses numbers for enums
|
|
6732
6732
|
automation_id: import_zod3.z.string().optional(),
|