import { get, writable, type Writable } from "svelte/store" import { alertCardExport, alertConfirm, alertError, alertInput, alertMd, alertNormal, alertSelect, alertStore, alertTOS, alertWait } from "./alert" import { DataBase, defaultSdDataFunc, type character, setDatabase, type customscript, type loreSettings, type loreBook, type triggerscript, importPreset } from "./storage/database" import { checkNullish, decryptBuffer, encryptBuffer, isKnownUri, selectFileByDom, selectMultipleFile, sleep } from "./util" import { language } from "src/lang" import { v4 as uuidv4, v4 } from 'uuid'; import { characterFormatUpdate } from "./characters" import { AppendableBuffer, BlankWriter, checkCharOrder, downloadFile, loadAsset, LocalWriter, openURL, readImage, saveAsset, VirtualWriter } from "./storage/globalApi" import { CurrentCharacter, SettingsMenuIndex, ShowRealmFrameStore, selectedCharID, settingsOpen } from "./stores" import { convertImage, hasher } from "./parser" import { CCardLib, type CharacterCardV3, type LorebookEntry } from '@risuai/ccardlib' import { reencodeImage } from "./process/files/image" import { PngChunk } from "./pngChunk" import type { OnnxModelFiles } from "./process/transformers" import { CharXReader, CharXWriter } from "./process/processzip" export const hubURL = "https://sv.risuai.xyz" export async function importCharacter() { try { const files = await selectFileByDom(["*"], 'multiple') if(!files){ return } for(const f of files){ console.log(f) await importCharacterProcess({ name: f.name, data: f }) checkCharOrder() } } catch (error) { alertError(`${error}`) return null } } async function importCharacterProcess(f:{ name: string; data: Uint8Array|File|ReadableStream }) { if(f.name.endsWith('json')){ if(f.data instanceof ReadableStream){ return null } const data = f.data instanceof Uint8Array ? f.data : new Uint8Array(await f.data.arrayBuffer()) const da = JSON.parse(Buffer.from(data).toString('utf-8')) if(await importCharacterCardSpec(da)){ let db = get(DataBase) return db.characters.length - 1 } if((da.char_name || da.name) && (da.char_persona || da.description) && (da.char_greeting || da.first_mes)){ let db = get(DataBase) db.characters.push(convertOldTavernAndJSON(da)) DataBase.set(db) alertNormal(language.importedCharacter) return } else{ alertError(language.errors.noData) return } } if(f.name.endsWith('charx')){ console.log('reading charx') const reader = new CharXReader() alertStore.set({ type: 'wait', msg: 'Loading... (Reading)' }) await reader.read(f.data) const cardData = reader.cardData if(!cardData){ alertError(language.errors.noData) return } const card = JSON.parse(cardData) if(CCardLib.character.check(card) !== 'v3'){ alertError(language.errors.noData) return } await importCharacterCardSpec(card, undefined, 'normal', reader.assets) let db = get(DataBase) return db.characters.length - 1 } if(!f.name.endsWith('png')){ alertError(language.errors.noData) return } alertStore.set({ type: 'wait', msg: 'Loading... (Reading)' }) await sleep(10) // const readed = PngChunk.read(img, ['chara'])?.['chara'] let readedChara = '' let readedCCv3 = '' let img:Uint8Array const readGenerator = PngChunk.readGenerator(f.data, { returnTrimed: true }) const assets:{[key:string]:string} = {} for await (const chunk of readGenerator){ console.log(chunk) if(!chunk){ continue } if(chunk instanceof AppendableBuffer){ img = chunk.buffer break } if(chunk.key === 'chara'){ //For memory reason, limit to 5MB if(readedChara.length < 5 * 1024 * 1024){ readedChara = chunk.value } continue } if(chunk.key === 'ccv3'){ if(readedCCv3.length < 5 * 1024 * 1024){ readedCCv3 = chunk.value } continue } if(chunk.key.startsWith('chara-ext-asset_')){ const assetIndex = (chunk.key.replace('chara-ext-asset_', '')) alertWait('Loading... (Reading Asset ' + assetIndex + ')' ) const assetData = Buffer.from(chunk.value, 'base64') const assetId = await saveAsset(assetData) assets[assetIndex] = assetId } } if(!readedChara && !readedCCv3){ alertError(language.errors.noData) return } if(!readedChara){ readedChara = readedCCv3 } if(!img){ console.error("No Image Found") alertError(language.errors.noData) return } if(readedChara.startsWith('rcc||')){ const parts = readedChara.split('||') const type = parts[1] if(type === 'rccv1'){ if(parts.length !== 5){ alertError(language.errors.noData) return } const encrypted = Buffer.from(parts[2], 'base64') const hashed = await hasher(encrypted) if(hashed !== parts[3]){ alertError(language.errors.noData) return } const metaData:RccCardMetaData = JSON.parse(Buffer.from(parts[4], 'base64').toString('utf-8')) if(metaData.usePassword){ const password = await alertInput(language.inputCardPassword) if(!password){ return } else{ try { const decrypted = await decryptBuffer(encrypted, password) const charaData:CharacterCardV2Risu = JSON.parse(Buffer.from(decrypted).toString('utf-8')) if(await importCharacterCardSpec(charaData, img, "normal", assets)){ let db = get(DataBase) return db.characters.length - 1 } else{ throw new Error('Error while importing') } } catch (error) { alertError(language.errors.wrongPassword) return } } } else{ const decrypted = await decryptBuffer(encrypted, 'RISU_NONE') try { const charaData:CharacterCardV2Risu = JSON.parse(Buffer.from(decrypted).toString('utf-8')) if(await importCharacterCardSpec(charaData, img, "normal", assets)){ let db = get(DataBase) return db.characters.length - 1 } } catch (error) { alertError(language.errors.noData) return } } } } else { const parsed = JSON.parse(Buffer.from(readedChara, 'base64').toString('utf-8')) const checkedVersion = CCardLib.character.check(parsed) if(checkedVersion === 'v2' || checkedVersion === 'v3'){ if(await importCharacterCardSpec(parsed, img, "normal", assets)){ let db = get(DataBase) return db.characters.length - 1 } } if(checkedVersion === 'v1'){ const converted = CCardLib.character.convert(parsed, { from: 'v1', to: 'v2' }) if(await importCharacterCardSpec(converted, img, "normal", assets)){ let db = get(DataBase) return db.characters.length - 1 } } } const charaData:OldTavernChar = JSON.parse(Buffer.from(readedChara, 'base64').toString('utf-8')) const imgp = await saveAsset(await reencodeImage(img)) let db = get(DataBase) db.characters.push(convertOldTavernAndJSON(charaData, imgp)) DataBase.set(db) alertNormal(language.importedCharacter) return db.characters.length - 1 } export const showRealmInfoStore:Writable = writable(null) export async function characterURLImport() { const realmPath = (new URLSearchParams(location.search)).get('realm') try { if(realmPath){ const url = new URL(location.href); url.searchParams.delete('realm'); window.history.pushState(null, '', url.toString()); const res = await fetch(`${hubURL}/hub/info/${realmPath}`) if(res.status !== 200){ alertError(await res.text()) return } showRealmInfoStore.set(await res.json()) } } catch (error) { } const charPath = (new URLSearchParams(location.search)).get('charahub') try { if(charPath){ alertWait('Loading from Chub...') const url = new URL(location.href); url.searchParams.delete('charahub'); window.history.pushState(null, '', url.toString()); const chara = await fetch("https://api.chub.ai/api/characters/download", { method: "POST", body: JSON.stringify({ "format": "tavern", "fullPath": charPath, "version": "main" }), headers: { "content-type": "application/json" } }) const img = new Uint8Array(await chara.arrayBuffer()) await importCharacterProcess({ name: 'charahub.png', data: img }) } } catch (error) { alertError(language.errors.noData) return null } const hash = location.hash if(hash.startsWith('#import_module=')){ const data = hash.replace('#import_module=', '') const importData = JSON.parse(Buffer.from(decodeURIComponent(data), 'base64').toString('utf-8')) importData.id = v4() const db = get(DataBase) if(importData.lowLevelAccess){ const conf = await alertConfirm(language.lowLevelAccessConfirm) if(!conf){ return false } } db.modules.push(importData) setDatabase(db) alertNormal(language.successImport) SettingsMenuIndex.set(1) settingsOpen.set(true) return } if(hash.startsWith('#import_preset=')){ const data = hash.replace('#import_preset=', '') const importData =Buffer.from(decodeURIComponent(data), 'base64') await importPreset({ name: 'imported.risupreset', data: importData }) SettingsMenuIndex.set(14) settingsOpen.set(true) return } } function convertOldTavernAndJSON(charaData:OldTavernChar, imgp:string|undefined = undefined):character{ return { name: charaData.name ?? 'unknown name', firstMessage: charaData.first_mes ?? 'unknown first message', desc: charaData.description ?? '', notes: '', chats: [{ message: [], note: '', name: 'Chat 1', localLore: [] }], chatPage: 0, image: imgp, emotionImages: [], bias: [], globalLore: [], viewScreen: 'none', chaId: uuidv4(), sdData: defaultSdDataFunc(), utilityBot: false, customscript: [], exampleMessage: charaData.mes_example, creatorNotes:'', systemPrompt:'', postHistoryInstructions:'', alternateGreetings:[], tags:[], creator:"", characterVersion: '', personality: charaData.personality ?? '', scenario:charaData.scenario ?? '', firstMsgIndex: -1, replaceGlobalNote: "", triggerscript: [], additionalText: '' } } export async function exportChar(charaID:number):Promise { const db = get(DataBase) let char = structuredClone(db.characters[charaID]) if(char.type === 'group'){ return '' } if(!char.image){ alertError('Image Required') return '' } const option = await alertCardExport() if(option.type === ''){ exportCharacterCard(char, option.type2 === 'json' ? 'json' : (option.type2 === 'charx' ? 'charx' : 'png'), {spec: 'v3'}) } else if(option.type === 'ccv2'){ exportCharacterCard(char,'png', {spec: 'v2'}) } else if(option.type === 'realm'){ ShowRealmFrameStore.set("character") } else{ return option.type } return '' } async function importCharacterCardSpec(card:CharacterCardV2Risu|CharacterCardV3, img?:Uint8Array, mode:'hub'|'normal' = 'normal', assetDict:{[key:string]:string} = {}):Promise{ if(!card ||(card.spec !== 'chara_card_v2' && card.spec !== 'chara_card_v3' )){ return false } const data = card.data console.log(card) let im = img ? await saveAsset(await reencodeImage(img)) : undefined let db = get(DataBase) const risuext = structuredClone(data.extensions.risuai) let emotions:[string, string][] = [] let bias:[string, number][] = [] let viewScreen: "none" | "emotion" | "imggen" = 'none' let customScripts:customscript[] = [] let utilityBot = false let sdData = defaultSdDataFunc() let extAssets:[string,string,string][] = [] let ccAssets:{ type: string uri: string name: string ext: string }[] = [] let vits:null|OnnxModelFiles = null if(risuext && card.spec === 'chara_card_v2'){ if(risuext.emotions){ for(let i=0;i= 3) fileName = risuext.additionalAssets[i][2] if(risuext.additionalAssets[i][1].startsWith('__asset:')){ const key = risuext.additionalAssets[i][1].replace('__asset:', '') const imgp = assetDict[key] if(!imgp){ throw new Error('Error while importing, asset ' + key + ' not found') } extAssets.push([risuext.additionalAssets[i][0],imgp,fileName]) continue } const imgp = await saveAsset(mode === 'hub' ? (await getHubResources(risuext.additionalAssets[i][1])) :Buffer.from(risuext.additionalAssets[i][1], 'base64'), '', fileName) extAssets.push([risuext.additionalAssets[i][0],imgp,fileName]) } } if(risuext.vits){ const keys = Object.keys(risuext.vits) for(let i=0;i 0){ vits = { name: "Imported VITS", files: risuext.vits, id: uuidv4().replace(/-/g, '') } } } if(risuext){ bias = risuext.bias ?? bias viewScreen = risuext.viewScreen ?? viewScreen customScripts = risuext.customScripts ?? customScripts utilityBot = risuext.utilityBot ?? utilityBot sdData = risuext.sdData ?? sdData } } if(card.spec === 'chara_card_v3'){ const data = card.data //required for type checking if(data.assets){ for(let i=0;i r.trim()), secondary_keys: lore.selective ? lore.secondkey.split(',').map(r => r.trim()) : undefined, content: lore.content, extensions: ext, enabled: true, insertion_order: lore.insertorder, constant: lore.alwaysActive, selective:lore.selective, name: lore.comment, comment: lore.comment, case_sensitive: caseSensitive, }) } char.loreExt ??= {} char.loreExt.risu_fullWordMatching = char.loreSettings?.fullWordMatching ?? false const card:CharacterCardV2Risu = { spec: "chara_card_v2", spec_version: "2.0", data: { name: char.name, description: char.desc ?? '', personality: char.personality ?? '', scenario: char.scenario ?? '', first_mes: char.firstMessage ?? '', mes_example: char.exampleMessage ?? '', creator_notes: char.creatorNotes ?? '', system_prompt: char.systemPrompt ?? '', post_history_instructions: char.replaceGlobalNote ?? '', alternate_greetings: char.alternateGreetings ?? [], character_book: { scan_depth: char.loreSettings?.scanDepth, token_budget: char.loreSettings?.tokenBudget, recursive_scanning: char.loreSettings?.recursiveScanning, extensions: char.loreExt ?? {}, entries: charBook }, tags: char.tags ?? [], creator: char.additionalData?.creator ?? '', character_version: `${char.additionalData?.character_version}` ?? '', extensions: { risuai: { // emotions: char.emotionImages, bias: char.bias, viewScreen: char.viewScreen, customScripts: char.customscript, utilityBot: char.utilityBot, sdData: char.sdData, // additionalAssets: char.additionalAssets, backgroundHTML: char.backgroundHTML, license: char.license, triggerscript: char.triggerscript, additionalText: char.additionalText, virtualscript: '', //removed dude to security issue largePortrait: char.largePortrait, lorePlus: char.lorePlus, inlayViewScreen: char.inlayViewScreen, newGenData: char.newGenData, vits: {} }, depth_prompt: char.depth_prompt } } } if(char.extentions){ for(const key in char.extentions){ if(key === 'risuai' || key === 'depth_prompt'){ continue } card.data.extensions[key] = char.extentions[key] } } return card } export async function exportCharacterCard(char:character, type:'png'|'json'|'charx' = 'png', arg:{ password?:string writer?:LocalWriter|VirtualWriter, spec?:'v2'|'v3' } = {}) { let img = await readImage(char.image) const spec:'v2'|'v3' = arg.spec ?? 'v2' //backward compatibility try{ char.image = '' img = await reencodeImage(img) const localWriter = arg.writer ?? (new LocalWriter()) if(!arg.writer && type !== 'json'){ const nameExt = { 'png': ['Image File', 'png'], 'json': ['JSON File', 'json'], 'charx': ['CharX File', 'charx'] } const ext = nameExt[type] console.log(ext) await (localWriter as LocalWriter).init(ext[0], [ext[1]]) } const writer = type === 'charx' ? (new CharXWriter(localWriter)) : type === 'json' ? (new BlankWriter()) : (new PngChunk.streamWriter(img, localWriter)) await writer.init() let assetIndex = 0 if(spec === 'v2'){ const card = await createBaseV2(char) if(card.data.extensions.risuai.emotions && card.data.extensions.risuai.emotions.length > 0){ for(let i=0;i 0){ for(let i=0;i:"/\\|?*\.\,]/g, "")}_export.json`, Buffer.from(JSON.stringify(card, null, 4), 'utf-8')) alertNormal(language.successExport) return } await sleep(10) alertStore.set({ type: 'wait', msg: 'Loading... (Writing)' }) await writer.write("chara", Buffer.from(JSON.stringify(card)).toString('base64')) } else if(spec === 'v3'){ const card = createBaseV3(char) if(card.data.assets && card.data.assets.length > 0){ for(let i=0;i:"/\\|?*\.\,]/g, "")}_export.json`, Buffer.from(JSON.stringify(card, null, 4), 'utf-8')) alertNormal(language.successExport) return } await sleep(10) alertStore.set({ type: 'wait', msg: 'Loading... (Writing)' }) if(type === 'charx'){ await writer.write("card.json", Buffer.from(JSON.stringify(card, null, 4))) } else{ await writer.write("ccv3", Buffer.from(JSON.stringify(card)).toString('base64')) } } await writer.end() await sleep(10) if(!arg.writer){ alertNormal(language.successExport) } } catch(e){ console.error(e, e.stack) alertError(`${e}`) } } export function createBaseV3(char:character){ let charBook:LorebookEntry[] = [] let assets:Array<{ type: string uri: string name: string ext: string }> = structuredClone(char.ccAssets ?? []) if(char.additionalAssets){ for(const asset of char.additionalAssets){ assets.push({ type: 'x-risu-asset', uri: asset[1], name: asset[0], ext: asset[2] || 'unknown' }) } } if(char.emotionImages){ for(const asset of char.emotionImages){ assets.push({ type: 'emotion', uri: asset[1], name: asset[0], ext: 'unknown' }) } assets.push({ type: 'icon', uri: 'ccdefault:', name: 'main', ext: 'png' }) } for(const lore of char.globalLore){ let ext:{ risu_case_sensitive?: boolean; risu_activationPercent?: number risu_loreCache?: { key:string data:string[] } } = structuredClone(lore.extentions ?? {}) let caseSensitive = ext.risu_case_sensitive ?? false ext.risu_activationPercent = lore.activationPercent ext.risu_loreCache = lore.loreCache charBook.push({ keys: lore.key.split(',').map(r => r.trim()), secondary_keys: lore.selective ? lore.secondkey.split(',').map(r => r.trim()) : undefined, content: lore.content, extensions: ext, enabled: true, insertion_order: lore.insertorder, constant: lore.alwaysActive, selective:lore.selective, name: lore.comment, comment: lore.comment, case_sensitive: caseSensitive, use_regex: lore.useRegex ?? false, }) } char.loreExt ??= {} char.loreExt.risu_fullWordMatching = char.loreSettings?.fullWordMatching ?? false const card:CharacterCardV3 = { spec: "chara_card_v3", spec_version: "3.0", data: { name: char.name, description: char.desc ?? '', personality: char.personality ?? '', scenario: char.scenario ?? '', first_mes: char.firstMessage ?? '', mes_example: char.exampleMessage ?? '', creator_notes: char.creatorNotes ?? '', system_prompt: char.systemPrompt ?? '', post_history_instructions: char.replaceGlobalNote ?? '', alternate_greetings: char.alternateGreetings ?? [], character_book: { scan_depth: char.loreSettings?.scanDepth, token_budget: char.loreSettings?.tokenBudget, recursive_scanning: char.loreSettings?.recursiveScanning, extensions: char.loreExt ?? {}, entries: charBook }, tags: char.tags ?? [], creator: char.additionalData?.creator ?? '', character_version: `${char.additionalData?.character_version}` ?? '', extensions: { risuai: { bias: char.bias, viewScreen: char.viewScreen, customScripts: char.customscript, utilityBot: char.utilityBot, sdData: char.sdData, backgroundHTML: char.backgroundHTML, license: char.license, triggerscript: char.triggerscript, additionalText: char.additionalText, virtualscript: '', //removed dude to security issue largePortrait: char.largePortrait, lorePlus: char.lorePlus, inlayViewScreen: char.inlayViewScreen, newGenData: char.newGenData, vits: {}, lowLevelAccess: char.lowLevelAccess ?? false }, depth_prompt: char.depth_prompt }, group_only_greetings: char.group_only_greetings ?? [], nickname: char.nickname ?? '', source: char.source ?? [], creation_date: char.creation_date ?? 0, modification_date: Math.floor(Date.now() / 1000), assets: assets } } if(char.extentions){ for(const key in char.extentions){ if(key === 'risuai' || key === 'depth_prompt'){ continue } card.data.extensions[key] = char.extentions[key] } } return card } export async function shareRisuHub2(char:character, arg:{ nsfw: boolean, tag:string license: string anon: boolean, update: boolean }) { try { char = structuredClone(char) char.license = arg.license let tagList = arg.tag.split(',') if(arg.nsfw){ tagList.push("nsfw") } await alertWait("Uploading...") let tags = tagList.filter((v, i) => { return (!!v) && (tagList.indexOf(v) === i) }) char.tags = tags const writer = new VirtualWriter() await exportCharacterCard(char, 'png', {writer: writer}) const dat = Buffer.from(writer.buf.buffer).toString('base64') + '&' + 'rt.png' openURL(`https://realm.risuai.net/hub/realm/upload#filedata=${encodeURIComponent(dat)}`) let testMode = true if(testMode){ return } const fetchPromise = fetch(hubURL + '/hub/realm/upload', { method: "POST", body: writer.buf.buffer, headers: { "Content-Type": 'image/png', "x-risu-api-version": "4", "x-risu-token": get(DataBase)?.account?.token, 'x-risu-username': arg.anon ? '' : (get(DataBase)?.account?.id), 'x-risu-debug': 'true', 'x-risu-update-id': arg.update ? (char.realmId ?? 'null') : 'null' } }) const res = await fetchPromise if(res.status !== 200){ alertError(await res.text()) } else{ const resJSON = await res.json() alertMd(resJSON.message) const currentChar = get(CurrentCharacter) if(currentChar.type === 'group'){ return } currentChar.realmId = resJSON.id CurrentCharacter.set(currentChar) } } catch (error) { alertError(`${error}`) } } export type hubType = { name:string desc: string download: string, id: string, img: string tags: string[], viewScreen: "none" | "emotion" | "imggen" hasLore:boolean hasEmotion:boolean hasAsset:boolean creator?:string creatorName?:string hot:number license:string authorname?:string } export async function getRisuHub(arg:{ search:string, page:number, nsfw:boolean sort:string }):Promise { try { arg.search += ' __shared' const stringArg = `search==${arg.search}&&page==${arg.page}&&nsfw==${arg.nsfw}&&sort==${arg.sort}` const da = await fetch(hubURL + '/realm/' + encodeURIComponent(stringArg)) if(da.status !== 200){ return [] } return da.json() } catch (error) { return[] } } export async function downloadRisuHub(id:string) { try { if(!(await alertTOS())){ return } alertStore.set({ type: "wait", msg: "Downloading..." }) const res = await fetch("https://realm.risuai.net/api/v1/download/png-v3/" + id + '?cors=true', { headers: { "x-risu-api-version": "4" } }) if(res.status !== 200){ alertError(await res.text()) return } if(res.headers.get('content-type') === 'image/png'){ await importCharacterProcess({ name: 'realm.png', data: res.body }) checkCharOrder() let db = get(DataBase) if(db.characters[db.characters.length-1] && db.goCharacterOnImport){ const index = db.characters.length-1 characterFormatUpdate(index); selectedCharID.set(index); } return } const result = await res.json() const data:CharacterCardV2Risu = result.card const img:string = result.img await importCharacterCardSpec(data, await getHubResources(img), 'hub') checkCharOrder() let db = get(DataBase) if(db.characters[db.characters.length-1] && db.goCharacterOnImport){ const index = db.characters.length-1 characterFormatUpdate(index); selectedCharID.set(index); } } catch (error) { console.error(error) alertError("Error while importing") } } export async function getHubResources(id:string) { const res = await fetch(`${hubURL}/resource/${id}`) if(res.status !== 200){ throw (await res.text()) } return Buffer.from(await (res).arrayBuffer()) } type CharacterCardV2Risu = { spec: 'chara_card_v2' spec_version: '2.0' // May 8th addition data: { name: string description: string personality: string scenario: string first_mes: string mes_example: string creator_notes: string system_prompt: string post_history_instructions: string alternate_greetings: string[] character_book?: CharacterBook tags: string[] creator: string character_version: string extensions: { risuai?:{ emotions?:[string, string][] bias?:[string, number][], viewScreen?: any, customScripts?:customscript[] utilityBot?: boolean, sdData?:[string,string][], additionalAssets?:[string,string,string][], backgroundHTML?:string, license?:string, triggerscript?:triggerscript[] private?:boolean additionalText?:string virtualscript?:string largePortrait?:boolean lorePlus?:boolean inlayViewScreen?:boolean newGenData?: { prompt: string, negative: string, instructions: string, emotionInstructions: string, }, vits?: {[key:string]:string} } depth_prompt?: { depth: number, prompt: string } } } } interface OldTavernChar{ avatar: "none" chat: string create_date: string description: string first_mes: string mes_example: string name: string personality: string scenario: string talkativeness: "0.5" } type CharacterBook = { name?: string description?: string scan_depth?: number // agnai: "Memory: Chat History Depth" token_budget?: number // agnai: "Memory: Context Limit" recursive_scanning?: boolean // no agnai equivalent. whether entry content can trigger other entries extensions: Record entries: Array } interface charBookEntry{ keys: Array content: string extensions: Record enabled: boolean insertion_order: number // if two entries inserted, lower "insertion order" = inserted higher // FIELDS WITH NO CURRENT EQUIVALENT IN SILLY name?: string // not used in prompt engineering priority?: number // if token budget reached, lower priority value = discarded first // FIELDS WITH NO CURRENT EQUIVALENT IN AGNAI id?: number // not used in prompt engineering comment?: string // not used in prompt engineering selective?: boolean // if `true`, require a key from both `keys` and `secondary_keys` to trigger the entry secondary_keys?: Array // see field `selective`. ignored if selective == false constant?: boolean // if true, always inserted in the prompt (within budget limit) position?: 'before_char' | 'after_char' // whether the entry is placed before or after the character defs case_sensitive?:boolean } interface RccCardMetaData{ usePassword?: boolean }