Lorebook Package Documentation The @character-foundry/lorebook package for Node.js and browsers provides primitives to parse, extract and manage lorebooks across AI character card formats including CCv3, SillyTavern, Agnai, Risu and Wyvern. The package keeps a card's multiple lorebooks separate rather than merging them, preserving authorship, update paths for linked content and separation of concerns, and normalizes parsed books to CCv3 while retaining original raw data for round-trip conversion. Package: @character-foundry/lorebook Environment: Node.js and Browser The @character-foundry/lorebook package provides primitives for parsing, extracting, and managing lorebooks also known as world info or memory books across different AI character card formats. A lorebook is a collection of entries that provide contextual information to AI models during roleplay. Each entry has: - Keys/Triggers : Words or phrases that activate the entry - Content : The information injected into the context - Metadata : Priority, position, enabled state, etc. Character cards can contain lorebooks in two ways: 1. Embedded : Stored directly in the card's character book field or extensions.additional lorebooks 2. Linked : Referenced by URL in card extensions, fetched from external sources Chub, Risu, etc. A key design principle: cards can have multiple separate lorebooks . We never "smoosh" them into one giant lorebook. This preserves: - Original authorship and sources - Update paths for linked content - Clean separation of concerns ┌─────────────────────────────────────────────────────────────────┐ │ Card CCv3Data │ ├─────────────────────────────────────────────────────────────────┤ │ data.character book → Primary embedded lorebook │ │ data.extensions.additional lorebooks → Array of extra books │ │ data.extensions.chub.linked lorebooks → Linked refs URLs │ │ data.extensions.world infos → More linked refs │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ LorebookCollection │ ├─────────────────────────────────────────────────────────────────┤ │ embedded: CCv3CharacterBook ← Multiple embedded books │ │ linked: LinkedLorebook ← Fetched external books │ └─────────────────────────────────────────────────────────────────┘ | Module | Purpose | |---|---| | parser.ts | Detect format and normalize to CCv3 | | extractor.ts | Extract refs and separate entries by source | | inserter.ts | Stamp entries with source metadata, manage card lorebooks | | handler.ts | Format conversion, serialization, entry utilities | type LorebookFormat = | 'ccv3' // Standard CCv3 character book | 'sillytavern' // SillyTavern world info JSON | 'agnai' // Agnai lorebook format | 'risu' // RisuAI .risulorebook | 'wyvern' // Wyvern format | 'unknown'; Result of parsing a standalone lorebook file: interface ParsedLorebook { book: CCv3CharacterBook; // Normalized to CCv3 originalFormat: LorebookFormat; originalShape: unknown; // Raw data for round-trip } Reference to a linked lorebook found in card extensions: interface LorebookRef { url: string; // Source URL platform: string; // 'chub' | 'risu' | 'janitor' | etc. id?: string; // Platform-specific ID name?: string; // Display name if available } A fetched/downloaded linked lorebook with source tracking: interface LinkedLorebook { source: string; // Source URL platform: string; // Platform identifier sourceId?: string; // ID on source platform fetchedAt: string; // ISO timestamp name?: string; // Display name book: CCv3CharacterBook; // The actual content } Metadata stamped on entries from linked lorebooks: interface EntrySourceMeta { linkedFrom: string; // Source URL platform: string; // Platform identifier fetchedAt: string; // When fetched originalEntryId?: string; // Original entry name/ID lorebookName?: string; // Lorebook name for display } Stored in entry.extensions.lorebookSource . Complete collection of lorebooks from a card: interface LorebookCollection { embedded: CCv3CharacterBook ; // Directly in card linked: LinkedLorebook ; // From external sources } Parse a standalone lorebook file JSON and normalize to CCv3: js import { parseLorebook, detectLorebookFormat } from '@character-foundry/lorebook'; // Auto-detect format and normalize const jsonBuffer = await fs.readFile 'lorebook.json' ; const { book, originalFormat, originalShape } = parseLorebook jsonBuffer ; console.log Detected format: ${originalFormat} ; console.log Entries: ${book.entries.length} ; // Or detect format without parsing const format = detectLorebookFormat JSON.parse jsonBuffer.toString ; Supported formats: | Format | Detection | |---|---| | CCv3 | entries array with keys / content | | SillyTavern | entries object keyed by uid, entries have uid / key / content | | Agnai | kind: 'memory' , entries have keywords / entry | | Risu | type: 'risu' or ripiVersion present | | Wyvern | format: 'wyvern' or wyvern object present | Get all lorebooks from a character card: js import { getLorebookCollection, extractLorebookRefs, extractLinkedEntries } from '@character-foundry/lorebook'; // Get the full collection embedded + linked const collection = getLorebookCollection card ; console.log Embedded lorebooks: ${collection.embedded.length} ; console.log Linked lorebooks: ${collection.linked.length} ; // List embedded lorebook names for const book of collection.embedded { console.log - ${book.name}: ${book.entries.length} entries ; } // Find linked lorebook references URLs to fetch const refs = extractLorebookRefs card ; for const ref of refs { console.log Linked: ${ref.url} ${ref.platform} ; } Where linked refs are found: - extensions.chub.linked lorebooks - extensions.world infos - extensions.linked lorebooks - extensions.ripiLinkedLorebooks Complete workflow for adding a linked lorebook to a card: js import { extractLorebookRefs, parseLorebook, createLinkedLorebook, addLinkedLorebookToCard, removeLinkedEntriesBySource, } from '@character-foundry/lorebook'; // 1. Find linked refs in card const refs = extractLorebookRefs card ; // 2. App fetches the lorebook not our job const lorebookJson = await yourFetchFunction refs 0 .url ; // 3. Parse and stamp with source metadata const { book } = parseLorebook lorebookJson ; const linked = createLinkedLorebook book, refs 0 .url, // sourceUrl refs 0 .platform, refs 0 .id ; // 4. Add to card stored in extensions.additional lorebooks let updatedCard = addLinkedLorebookToCard card, linked ; // Later: Remove linked entries when unlinking updatedCard = removeLinkedEntriesBySource updatedCard, refs 0 .url ; Work with individual entries: js import { findEntriesByKeys, findEntryByNameOrId, updateEntry, addEntry, removeEntry, reorderEntries, } from '@character-foundry/lorebook'; // Search entries by trigger keys const matches = findEntriesByKeys book, 'dragon', 'fire' , { caseSensitive: false, matchAll: false, // Any key matches OR } ; // Find specific entry const entry = findEntryByNameOrId book, 'Dragons' ; const entryById = findEntryByNameOrId book, 42 ; // Update an entry const updated = updateEntry book, 'Dragons', { content: 'Updated dragon lore...', enabled: true, } ; // Add new entry const withNew = addEntry book, { keys: 'phoenix', 'fire bird' , content: 'Phoenix lore here...', enabled: true, name: 'Phoenix', priority: 10, } ; // Remove entry const removed = removeEntry book, 'Outdated Entry' ; // Reorder entries const reordered = reorderEntries book, 5, 2, 3, 1, 4 ; Convert between lorebook formats: js import { convertLorebook, serializeLorebook, serializeParsedLorebook, } from '@character-foundry/lorebook'; // Convert CCv3 to SillyTavern format const stFormat = convertLorebook book, 'sillytavern' ; // Serialize to JSON string const json = serializeLorebook book, 'sillytavern', undefined, true ; // Round-trip: preserve original format const { book, originalFormat, originalShape } = parseLorebook input ; // ... modify book ... const output = serializeParsedLorebook { book, originalFormat, originalShape } ; Format-specific field preservation: When converting, format-specific fields are preserved in entry.extensions : // SillyTavern fields preserved in entry.extensions.sillytavern { selectiveLogic: 0, excludeRecursion: false, probability: 100, depth: 4, // ... etc } // Agnai fields preserved in entry.extensions.agnai { weight: 1, } Parse a standalone lorebook file. Auto-detects format and normalizes to CCv3. Detect the format of a lorebook JSON object without parsing. Normalize any lorebook format to CCv3 character book structure. Extract linked lorebook references URLs from card extensions. Separate embedded entries from linked entries using extensions.lorebookSource . Get all lorebooks from a card as a collection with embedded and linked separated. Stamp all entries with source metadata for tracking. Create a LinkedLorebook from a fetched book with source info. Add a linked lorebook to card's extensions.additional lorebooks . Add an embedded lorebook. Sets as character book if none exists, otherwise adds to additional lorebooks . Remove a lorebook by name from card. Remove all entries that came from a specific source URL. Replace a lorebook in card matches by name . Rebuild card's lorebooks from a LorebookCollection. Convert CCv3 to another format. Preserves original shape for round-trip. Serialize a lorebook to JSON string. Round-trip a parsed lorebook back to its original format. Merge two lorebooks into one. Use with caution - design preference is to keep separate. Find entries matching search keys. Options: - caseSensitive : boolean default: false - matchAll : boolean - require all keys match default: false Find entry by name string or numeric ID. Update an entry's fields. Add a new entry with auto-generated ID. Remove an entry by ID or name. Reorder entries by specifying ID/name order. 1. Authorship : Each lorebook may have a different creator 2. Updates : Linked lorebooks can be refreshed from source 3. Modularity : Users can enable/disable individual lorebooks 4. Clarity : Clear separation between character lore and world lore The package provides parsing, extraction, and insertion - not downloading . Reasons: 1. Simplicity : No HTTP dependencies, works in any environment 2. Flexibility : Apps handle auth, caching, rate limits differently 3. No bloat : Supporting 100 card sites would be unmaintainable Your app's responsibility: - Fetch lorebook from URL - Handle authentication - Manage caching - Deal with errors Our responsibility: - Parse the fetched data - Stamp with source metadata - Insert into card structure Diffing and merge conflict UI is complex and app-specific. Primitives provide: - Source tracking so you know where entries came from - Entry matching by name/id AND keys - Update/replace operations Your app's responsibility: - Show diff UI to users - Let users resolve conflicts - Call our update functions with resolved data js import { parseCard, exportCard, } from '@character-foundry/loader'; import { getLorebookCollection, extractLorebookRefs, parseLorebook, createLinkedLorebook, addLinkedLorebookToCard, replaceLorebookInCard, setLorebookCollection, updateEntry, } from '@character-foundry/lorebook'; class LorebookEditor { private card: CCv3Data; private collection: LorebookCollection; async load buffer: Uint8Array { const { card } = parseCard buffer ; this.card = card; this.collection = getLorebookCollection card ; } // Refresh a linked lorebook from source async refreshLinked sourceUrl: string, fetchFn: url: string = Promise