// FrankieSDK v1 — include this in your arcade game to talk to Frankie OS. // // ============================================================================ // GAME STRUCTURE // ============================================================================ // Your game must be packaged as a .zip containing: // // my-game.zip // ├── frankie-game.json ← required manifest (see format below) // ├── index.html ← required entry point // └── assets/ ← optional: JS, CSS, images, audio, etc. // // ============================================================================ // frankie-game.json FORMAT // ============================================================================ // { // "id": "my-game", // slug: lowercase letters, digits, hyphens (1-64 chars) // "version": "1.0.0", // your game version // "title": "My Game", // shown on the cartridge // "tagline": "A one-line pitch", // shown under the title // "icon": "🎮", // emoji shown on the cartridge // "kind": "ARCADE", // badge: ARCADE | VS | LLM | PET // "author": "Your Name", // shown in the admin panel // "order": 50, // sort position in the arcade grid (lower = first) // "sdk_version": 1 // must be 1; future versions may be incompatible // } // // ============================================================================ // LIFECYCLE HOOKS (events received FROM Frankie) // ============================================================================ // sdk.on('init', ({ userId, username, gameId, theme }) => { ... }) // Called once after the iframe loads. Initialize your game here. // You MUST call sdk.emit('ready') when your game has finished loading. // // sdk.on('pause', () => { ... }) // Called when the arcade overlay loses focus (e.g. user switches tab). // Pause timers, animations, and any ongoing audio. // // sdk.on('resume', () => { ... }) // Called when the overlay regains focus. Resume paused state. // // ============================================================================ // EVENTS EMITTED TO Frankie // ============================================================================ // sdk.emit('ready') // Tell Frankie your game is loaded and ready. Required after 'init'. // // sdk.emit('xp', { amount: number, reason: string }) // Award XP to the player. 'reason' is a short label shown in toasts. // // sdk.emit('win') // Record a win for this game (increments win counter and awards XP). // // sdk.emit('score', { value: number }) // Submit a high score. Frankie keeps the all-time best per game. // // sdk.emit('close') // Close the game and return to the arcade grid. // // ============================================================================ // LLM ACCESS (for kind: "LLM" games) // ============================================================================ // const reply = await sdk.llm([ // { role: 'system', content: 'You are a trivia host.' }, // { role: 'user', content: 'Ask me a question.' } // ]) // console.log(reply) // string — the model's response text // // ============================================================================ // EXAMPLE (vanilla JS) // ============================================================================ // import sdk from './frankie-sdk.js' // // sdk.on('init', ({ username }) => { // document.querySelector('#welcome').textContent = `Hello, ${username}!` // sdk.emit('ready') // }) // // sdk.on('pause', () => clearInterval(gameLoop)) // sdk.on('resume', () => { gameLoop = setInterval(tick, 16) }) // // function onPlayerWin() { // sdk.emit('win') // sdk.emit('xp', { amount: 50, reason: 'Victory' }) // sdk.emit('score', { value: playerScore }) // } // ============================================================================ const FrankieSDK = (() => { const _handlers = {} const _llmResolvers = {} // _handle dispatches an inbound postMessage event to the registered hook. function _handle(type, payload) { const hook = _handlers[type] if (typeof hook === 'function') hook(payload) } // Boot: listen for messages from the Frankie parent frame. window.addEventListener('message', (event) => { const { type, payload, id } = event.data || {} if (typeof type !== 'string' || !type.startsWith('FRANKIE_')) return const hook = type.slice(8).toLowerCase() // FRANKIE_INIT → 'init' // LLM responses are resolved as promises rather than dispatched as hooks. if (hook === 'llm_response') { const resolve = _llmResolvers[id] if (typeof resolve === 'function') { resolve(payload?.content ?? '') delete _llmResolvers[id] } return } _handle(hook, payload) }) return { // on registers a lifecycle hook. Returns the SDK for chaining. on(event, handler) { _handlers[event] = handler return this }, // emit sends a typed event to the Frankie parent frame. emit(event, payload = {}) { window.parent.postMessage( { type: `FRANKIE_${event.toUpperCase()}`, payload }, '*' ) }, // llm sends a chat-completion request to Frankie's LLM and returns a Promise // that resolves with the model's reply as a string. llm(messages) { return new Promise((resolve) => { const id = `llm_${Date.now()}_${Math.random().toString(36).slice(2)}` _llmResolvers[id] = resolve window.parent.postMessage( { type: 'FRANKIE_LLM_REQUEST', id, payload: { messages } }, '*' ) }) } } })() export default FrankieSDK