@stage5/lumine 0.1.1 → 0.1.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stage5/lumine",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Command line tools for launching Lumine builds on Twinkle.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -2,7 +2,7 @@
2
2
 
3
3
  Version: 1.23.0
4
4
  Updated: 2026-05-24
5
- Generated: 2026-05-25T00:43:05.370Z
5
+ Generated: 2026-05-26T08:23:45.901Z
6
6
 
7
7
  ## Notes
8
8
  - This SDK is injected into Build iframes via the Build preview/runtime.
@@ -27,211 +27,293 @@ files:read, user:read, users:read, dailyReflections:read, content:read, sharedDb
27
27
 
28
28
  ### Twinkle.capabilities
29
29
  - async get() | scopes: none
30
+ - Returns: Capability snapshot
30
31
  - async can(actionName) | scopes: none
32
+ - Returns: boolean
31
33
  - async listActions() | scopes: none
34
+ - Returns: { available, blocked, details }
32
35
  - async refresh() | scopes: none
36
+ - Returns: Capability snapshot
33
37
 
34
38
  ### Twinkle.viewer
35
39
  - async get() | scopes: none
40
+ - Returns: { id, username, profilePicUrl, isLoggedIn, isOwner, isGuest }
36
41
  - async refresh() | scopes: none
42
+ - Returns: Viewer info
37
43
 
38
44
  ### Twinkle.preview
39
45
  - getLayout() | scopes: none
46
+ - Returns: { mode, viewport, stage, safeInsets, playfield }
40
47
  - Read the host preview layout and derive a fixed-world scale from layout.playfield before sizing a canvas, sprites, or mobile game UI.
41
48
  - Example: const WORLD = { width: 360, height: 640 }; const layout = Twinkle.preview.getLayout(); const scale = Math.min(layout.playfield.width / WORLD.width, layout.playfield.height / WORLD.height);
42
49
  - reserveInsets({ top, right, bottom, left }) | scopes: none
50
+ - Returns: { mode, viewport, stage, safeInsets, playfield }
43
51
  - Reserve host-aware safe space for HUD bars, touch controls, or other overlays before clamping gameplay.
44
52
  - Example: Twinkle.preview.reserveInsets({ top: 72, bottom: 120, left: 0, right: 0 });
45
53
  - setPlayfield({ x, y, width, height } | null) | scopes: none
54
+ - Returns: { playfieldBounds, playerBounds, overflowTop, overflowRight, overflowBottom, overflowLeft, status, reportedAt } | null
46
55
  - Declare the actual playable rectangle when the game area is smaller than the raw canvas.
47
56
  - Example: Twinkle.preview.setPlayfield({ x: layout.playfield.x, y: layout.playfield.y, width: layout.playfield.width, height: layout.playfield.height });
48
57
  - reportGameplayState({ playfieldBounds?, playerBounds? } | null) | scopes: none
58
+ - Returns: { playfieldBounds, playerBounds, overflowTop, overflowRight, overflowBottom, overflowLeft, status, reportedAt } | null
49
59
  - Report live player or avatar bounds so the preview host can detect floor, wall, or out-of-bounds issues.
50
60
  - Example: Twinkle.preview.reportGameplayState({ playerBounds: { x: player.x, y: player.y, width: player.width, height: player.height } });
51
61
  - getGameplayTelemetry() | scopes: none
62
+ - Returns: { playfieldBounds, playerBounds, overflowTop, overflowRight, overflowBottom, overflowLeft, status, reportedAt } | null
52
63
  - Read the latest preview-side gameplay telemetry snapshot.
53
64
  - clearGameplayState() | scopes: none
65
+ - Returns: { playfieldBounds, playerBounds, overflowTop, overflowRight, overflowBottom, overflowLeft, status, reportedAt } | null
54
66
  - clearReservedInsets() | scopes: none
67
+ - Returns: { mode, viewport, stage, safeInsets, playfield }
55
68
  - subscribe(listener, { immediate } = {}) | scopes: none
69
+ - Returns: unsubscribe()
56
70
  - Listen for host layout changes so a fixed-world game scale or canvas surface stays synced after resize, mobile viewport changes, or embedded runtime layout shifts.
57
71
  - Example: const unsubscribe = Twinkle.preview.subscribe((layout) => syncGameLayout(layout), { immediate: true });
58
72
 
59
73
  ### Twinkle.mount
60
74
  - async get() | scopes: none
75
+ - Returns: { type: 'subject', id: number } | null
61
76
  - async refresh() | scopes: none
77
+ - Returns: { type: 'subject', id: number } | null
62
78
 
63
79
  ### Twinkle.notifications
64
80
  - getLaunchTarget() | scopes: none
81
+ - Returns: { notificationId, buildId, eventKey, eventLabel, target } | null
65
82
  - Read the current notification launch target, if the app was opened from a Build notification.
66
83
  - onLaunchTarget(listener, { immediate } = {}) | scopes: none
84
+ - Returns: unsubscribe()
67
85
  - Listen for notification launch targets while the Build app is already open.
68
86
  - Example: const off = Twinkle.notifications.onLaunchTarget((launchTarget) => focusEntry(launchTarget?.target?.focus?.entryId));
69
87
  - async getSubjectUpdateSubscription(subjectId) | scopes: notifications:read
88
+ - Returns: { subscription }
70
89
  - Read whether the current viewer is subscribed to Build notifications for updates to a subject.
71
90
  - Example: const { subscription } = await Twinkle.notifications.getSubjectUpdateSubscription(subjectId);
72
91
  - async subscribeToSubjectUpdates(subjectId, { target } = {}) | scopes: notifications:write
92
+ - Returns: { subscription }
73
93
  - Subscribe the current viewer to notifications when the original subject author adds a new page or update.
74
94
  - Example: await Twinkle.notifications.subscribeToSubjectUpdates(subjectId, { target: { view: 'book', subjectId } });
75
95
  - async unsubscribeFromSubjectUpdates(subjectId) | scopes: notifications:write
96
+ - Returns: { subscription: null }
76
97
  - Unsubscribe the current viewer from Build notifications for a subject's new pages or updates.
77
98
 
78
99
  ### Twinkle.chess
79
100
  - async bestMove({ fen, depth?, skillLevel?, maxTimeMs?, timeoutMs? }) | scopes: none
101
+ - Returns: { success, move, bestMove, from, to, promotion, evaluation, depth, mate, error, engine }
80
102
  - Ask the parent-hosted Stockfish engine for the best move from a FEN position.
81
103
  - Example: const result = await Twinkle.chess.bestMove({ fen: game.fen(), skillLevel: 8, maxTimeMs: 1000 });
82
104
  if (result.success) game.move({ from: result.from, to: result.to, promotion: result.promotion || undefined });
83
105
  - async evaluate({ fen, depth?, skillLevel?, maxTimeMs?, timeoutMs? }) | scopes: none
106
+ - Returns: { success, move, bestMove, from, to, promotion, evaluation, depth, mate, error, engine }
84
107
  - Analyze a FEN position and return Stockfish's current best move plus centipawn or mate evaluation.
85
108
  - Example: const analysis = await Twinkle.chess.evaluate({ fen: game.fen(), depth: 12 });
86
109
  console.log(analysis.bestMove, analysis.evaluation, analysis.mate);
87
110
 
88
111
  ### Twinkle.files
89
112
  - async saveAs({ fileName, url, dataUrl, data, text, json, bytes, blob, file, mimeType } = {}) | scopes: none
113
+ - Returns: { success, fileName, size?, mimeType?, method }
90
114
  - Download a generated or remote file to the viewer's local device through the parent frame without opening a popup.
91
115
  - Example: await Twinkle.files.saveAs({ fileName: 'fashion-guide.png', dataUrl: imageUrl, mimeType: 'image/png' });
92
116
  - async uploadGenerated({ fileName, url, dataUrl, data, text, json, bytes, blob, file, mimeType } = {}) | scopes: files:write
117
+ - Returns: { assets: [{ id, buildId, fileName, originalFileName, mimeType, sizeBytes, filePath, url, thumbUrl, fileType, uploadedByUserId, createdAt }], failed?: [{ fileName, message }], canceled }
93
118
  - Upload an app-generated file to Twinkle-hosted cloud storage without opening a picker, then store the returned asset refs in sharedDb/privateDb/userDb.
94
119
  - Example: const { assets } = await Twinkle.files.uploadGenerated({ fileName: 'fashion-guide.png', dataUrl: generatedImageUrl, mimeType: 'image/png' });
95
120
  - async pickAndUpload({ accept, multiple } = {}) | scopes: files:write
121
+ - Returns: { assets: [{ id, buildId, fileName, originalFileName, mimeType, sizeBytes, filePath, url, thumbUrl, fileType, uploadedByUserId, createdAt }], failed?: [{ fileName, message }], canceled }
96
122
  - Pick supported local files and upload them to Twinkle-hosted cloud storage, then store the returned asset refs in sharedDb/privateDb/userDb.
97
123
  - Example: const { assets, canceled } = await Twinkle.files.pickAndUpload({ accept: 'image/*,.pdf', multiple: true });
98
124
  - async list({ cursor, limit } = {}) | scopes: files:read
125
+ - Returns: { assets: [{ id, buildId, fileName, originalFileName, mimeType, sizeBytes, filePath, url, thumbUrl, fileType, uploadedByUserId, createdAt }], nextCursor, usage: { totalBytes, fileCount, maxRuntimeFileStorageBytes, remainingBytes } | null }
99
126
  - List the current viewer's uploaded runtime files for this build.
100
127
  - Example: const { assets, usage } = await Twinkle.files.list({ limit: 20 });
101
128
  - async delete(assetId) | scopes: files:write
129
+ - Returns: { success, deletedAssetId, usage: { totalBytes, fileCount, maxRuntimeFileStorageBytes, remainingBytes } | null }
102
130
  - Delete one of the current viewer's uploaded runtime files and free up Twinkle.files quota.
103
131
  - Example: await Twinkle.files.delete(assetId);
104
132
 
105
133
  ### Twinkle.ai
106
134
  - async listPrompts() | scopes: none
135
+ - Returns: Array<{ id, title, description }>
107
136
  - async chat({ promptId, message, history, systemPrompt, requestId, onText, onStatus } = {}) | scopes: none
137
+ - Returns: { text, response, model, aiUsagePolicy }
108
138
  - Generate text with the default Lumine text model, optionally streaming text updates through onText.
109
139
  - Example: const chatHistory = conversation.slice(-12).map((entry) => ({ role: entry.role === 'assistant' ? 'assistant' : 'user', content: entry.text }));
110
140
  const result = await Twinkle.ai.chat({ message, history: chatHistory, systemPrompt: 'You are a cheerful pirate helper who answers in one sentence.', onText: (text, meta) => renderReply(text), onStatus: (status) => setThinking(status === 'thinking') });
111
141
  - async generateObject({ prompt, expectedStructure, thinkingMode, mode, instructions, systemPrompt } = {}) | scopes: none
142
+ - Returns: { object, result, model, provider, thinkingMode, requestedThinkingMode, aiUsagePolicy }
112
143
  - Generate a validated structured JSON object for app decisions, routing, grading, and game-state logic.
113
144
  - Example: const { object } = await Twinkle.ai.generateObject({ thinkingMode: 'medium', prompt: 'Classify the player intent from: ' + playerText, expectedStructure: { action: 'string', targetCharacter: 'string', confidence: 0, shouldAskFollowUp: false } });
114
145
  - onChatStatus(listener) | scopes: none
146
+ - Returns: unsubscribe function
115
147
  - Listen to shared runtime AI chat stream events.
116
148
  - async generateImage({ prompt, referenceImageB64, previousResponseId, previousImageId, engine, quality, requestId, onStatus, timeoutMs } = {}) | scopes: none
149
+ - Returns: { success, imageUrl, responseId, imageId, engine, quality, aiUsagePolicy } or { success: false, error, reason, code, aiUsagePolicy }
117
150
  - Generate or edit an image from a prompt and optional base64/data-URL reference image.
118
151
  - Example: const result = await Twinkle.ai.generateImage({ prompt: 'Create a fashion guide portrait for this face with flattering colors and outfit ideas', referenceImageB64, quality: 'high', onStatus: (status) => console.log(status.stage) });
119
152
  - onImageGenerationStatus(listener) | scopes: none
153
+ - Returns: unsubscribe function
120
154
  - Subscribe to real-time image generation status events forwarded into the build iframe.
121
155
  - Example: const unsubscribe = Twinkle.ai.onImageGenerationStatus((status) => console.log(status.stage));
122
156
 
123
157
  ### Twinkle.characters
124
158
  - async chat({ character, thinkingMode, message, history, roomContext, scene, systemPrompt, instructions, includeWebsiteContext, requestId, onText, onStatus } = {}) | scopes: none
159
+ - Returns: { text, response, character, aiUsername, thinkingMode, requestedThinkingMode, includeWebsiteContext, model, provider, aiUsagePolicy }
125
160
  - Talk to Zero or Ciel from a Build app, either as a final-response call or streaming RPG-style dialogue text with onText/onStatus.
126
161
  - Example: const dialogueHistory = recentTurns.slice(-16).map((entry) => ({ role: entry.role === 'assistant' ? 'assistant' : 'user', content: entry.text, speaker: entry.speaker }));
127
162
  const result = await Twinkle.characters.chat({ character: 'zero', thinkingMode: thinkHard ? 'high' : 'medium', message: playerText, history: dialogueHistory, roomContext, scene: { location: 'classroom', nearbyCharacters: ['zero', 'ciel'] }, includeWebsiteContext: false, onText: (text) => renderDialogue(text) });
128
163
  - onChatStatus(listener) | scopes: none
164
+ - Returns: unsubscribe function
129
165
  - Listen to shared Zero/Ciel runtime chat stream events.
130
166
 
131
167
  ### Twinkle.userDb
132
168
  - async query(sql, params) | scopes: none
169
+ - Returns: { rows, rowCount, truncated }
133
170
  - Run a SELECT against advanced private per-user SQLite. Use Twinkle.privateDb instead for simple preferences, drafts, settings, or small JSON state.
134
171
  - async exec(sql, params) | scopes: none
172
+ - Returns: { changes, lastInsertRowid }
135
173
  - Run a write or schema statement against advanced private per-user SQLite.
136
174
 
137
175
  ### Twinkle.subjects
138
176
  - async getMySubjects({ limit, cursor } = {}) | scopes: content:read
177
+ - Returns: { subjects: [{ id, title, description, filePath, fileName, fileSize, thumbUrl, timeStamp, rootType, rootId, rewardLevel }], cursor? }
139
178
  - async search({ query, limit, cursor } = {}) | scopes: content:read
179
+ - Returns: { subjects: [{ id, contentType, contentId, title, description, filePath, fileName, fileSize, thumbUrl, timeStamp, userId, username, profilePicUrl, rootType, rootId, rewardLevel, numComments }], cursor?, pagination: { limit, hasMore, nextCursor }, filters: { query } }
140
180
  - Search Twinkle subjects by text for subject picker UIs, book apps, scrapbooks, and galleries.
141
181
  - Example: const { subjects } = await Twinkle.subjects.search({ query: searchText, limit: 12 });
142
182
  - async getSubject(subjectId) | scopes: content:read
183
+ - Returns: { subject: { id, title, description, filePath, fileName, fileSize, thumbUrl, secretAnswer, secretAttachment, timeStamp, userId, username, profilePicUrl, rootType, rootId, rewardLevel } }
143
184
  - async getSubjectComments(subjectId, { limit, cursor } = {}) | scopes: content:read
185
+ - Returns: { comments: [{ id, content, filePath, fileName, fileSize, thumbUrl, timeStamp }], cursor? }
144
186
 
145
187
  ### Twinkle.aiStories
146
188
  - async list({ limit, cursor, difficulty, type, isListening, userId, hasImage, hasQuestions } = {}) | scopes: content:read
189
+ - Returns: { stories: [{ id, contentType, contentId, topic, topicKey, type, story, explanation, difficulty, isListening, imagePath, imageUrl, audioPath, audioUrl, questions, questionsBy, hasImage, hasQuestions, userId, username, profilePicUrl, timeStamp }], cursor?, pagination: { limit, hasMore, nextCursor }, filters }
147
190
  - List completed existing user-generated AI Stories newest first for galleries, quiz apps, readers, and image/story collections.
148
191
  - Example: const { stories } = await Twinkle.aiStories.list({ hasImage: true, hasQuestions: true, limit: 12 });
149
192
  - async search({ query, limit, cursor, difficulty, type, isListening, userId, hasImage, hasQuestions } = {}) | scopes: content:read
193
+ - Returns: { stories: [{ id, contentType, contentId, topic, topicKey, type, story, explanation, difficulty, isListening, imagePath, imageUrl, audioPath, audioUrl, questions, questionsBy, hasImage, hasQuestions, userId, username, profilePicUrl, timeStamp }], cursor?, pagination: { limit, hasMore, nextCursor }, filters }
150
194
  - Search completed existing user-generated AI Stories by topic or story text for galleries, quizzes, and readers.
151
195
  - Example: const { stories } = await Twinkle.aiStories.search({ query: searchText, hasQuestions: true, limit: 12 });
152
196
  - async get(storyId) | scopes: content:read
197
+ - Returns: { story: { id, contentType, contentId, topic, topicKey, type, story, explanation, difficulty, isListening, imagePath, imageUrl, audioPath, audioUrl, questions, questionsBy, hasImage, hasQuestions, userId, username, profilePicUrl, timeStamp } }
153
198
  - Fetch one completed existing AI Story by id, including story text, media URLs, and normalized questions when available.
154
199
  - Example: const { story } = await Twinkle.aiStories.get(storyId);
155
200
 
156
201
  ### Twinkle.grammarbles
157
202
  - async listQuestions({ level, limit, cursor } = {}) | scopes: content:read
203
+ - Returns: { questions: [{ id, level, rating, question, choices, answerIndex, correctChoice, correctChoiceKey, isChecked, explanation }], cursor?, pagination: { level, limit, hasMore, nextCursor } }
158
204
  - Read public Grammarbles questions and answers by level with rating/id cursor pagination.
159
205
  - Example: const page = await Twinkle.grammarbles.listQuestions({ level: 3, limit: 100 }); const question = page.questions[Math.floor(Math.random() * page.questions.length)];
160
206
  - async getMyQuestionHistory({ level, limit, cursor } = {}) | scopes: content:read
207
+ - Returns: { attempts: [{ id, questionId, level, grade, gradeRank, isCorrect, attemptNumber, timeStamp }], cursor?, pagination: { level, limit, hasMore, nextCursor } }
161
208
  - Read the signed-in viewer's real Grammarbles attempt rows for trainer filtering.
162
209
  - Example: const history = await Twinkle.grammarbles.getMyQuestionHistory({ level: selectedLevel, limit: 500 }); const answeredIds = new Set(history.attempts.map((attempt) => attempt.questionId));
163
210
 
164
211
  ### Twinkle.subjectComments
165
212
  - async list(subjectId, { limit, cursor, sortBy, includeReplies, author, authorUserId, replyScope } = {}) | scopes: content:read
213
+ - Returns: { comments: [{ id, content, filePath, fileName, fileSize, thumbUrl, timeStamp, userId, username, profilePicUrl, commentId, replyId }], cursor?, pagination: { limit, hasMore, nextCursor }, filters: { subjectId, sortBy, includeReplies, author, replyScope, authorUserId } }
166
214
  - Read a subject's comment stream with stable keyset pagination, oldest/newest ordering, author filters, and optional same-author reply scoping.
167
215
  - Example: const { subjects } = await Twinkle.subjects.search({ query: searchText, limit: 12 }); const subjectId = pickedSubject.id; const page = await Twinkle.subjectComments.list(subjectId, { sortBy: 'oldest', author: 'subjectPoster', includeReplies: true, replyScope: 'ownThread', limit: 50 });
168
216
 
169
217
  ### Twinkle.profileComments
170
218
  - async getProfileComments({ profileUserId, limit, offset, sortBy, includeReplies, range, since, until } = {}) | scopes: content:read
219
+ - Returns: { comments: [{ id, content, filePath, fileName, fileSize, thumbUrl, timeStamp, userId, username, profilePicUrl, likes, replies, commentId, replyId }], pagination: { limit, offset, hasMore, nextOffset }, filters: { profileUserId, sortBy, includeReplies, since, until } }
171
220
  - async getProfileCommentIds({ profileUserId, limit, offset, sortBy, includeReplies, range, since, until } = {}) | scopes: content:read
221
+ - Returns: { ids: number[], pagination: { limit, offset, hasMore, nextOffset }, filters: { profileUserId, sortBy, includeReplies, since, until } }
172
222
  - async getCommentsByIds(idsOrOpts) | scopes: content:read
223
+ - Returns: { comments: [{ id, content, filePath, fileName, fileSize, thumbUrl, timeStamp, userId, username, profilePicUrl, commentId, replyId }] }
173
224
  - async getProfileCommentCounts(idsOrOpts) | scopes: content:read
225
+ - Returns: { countsById: { [commentId]: { likes, replies } } }
174
226
 
175
227
  ### Twinkle.leaderboards
176
228
  - async get({ boardKey = 'default', limit, cursor } = {}) | scopes: none
229
+ - Returns: { entries: [{ rank, id, buildId, boardKey, viewerKind, userId, displayName, score, meta, achievedAt, createdAt, updatedAt }], scores, cursor, hasMore, personalBest: { id, buildId, boardKey, viewerKind, userId, displayName, score, meta, achievedAt, createdAt, updatedAt } | null }
177
230
  - Read score-sorted personal-best leaderboard rows for this Build app.
178
231
  - async submit({ boardKey = 'default', score, displayName, meta } = {}) | scopes: none
232
+ - Returns: { entry: { id, buildId, boardKey, viewerKind, userId, displayName, score, meta, achievedAt, createdAt, updatedAt } | null, personalBest: { id, buildId, boardKey, viewerKind, userId, displayName, score, meta, achievedAt, createdAt, updatedAt } | null, improved, previousScore }
179
233
  - Submit a score to a public Build leaderboard using server-owned viewer identity.
180
234
 
181
235
  ### Twinkle.sharedDb
182
236
  - async getTopics() | scopes: sharedDb:read
237
+ - Returns: { topics: [{ id, name, createdBy, createdAt }] }
183
238
  - async createTopic(name) | scopes: sharedDb:write
239
+ - Returns: { topic: { id, name, createdBy, createdAt } }
184
240
  - async getEntries(topicName, { limit, pageSize, cursor, order, sort, direction } = {}) | scopes: sharedDb:read
241
+ - Returns: { entries: [{ id, topicId, userId, username, profilePicUrl, data, createdAt, updatedAt }], cursor?, hasMore }
185
242
  - Read shared topic rows with cursor pagination.
186
243
  - async loadMoreEntries(topicName, { limit, pageSize, cursor, order, sort, direction } = {}) | scopes: sharedDb:read
244
+ - Returns: { entries: [{ id, topicId, userId, username, profilePicUrl, data, createdAt, updatedAt }], cursor?, hasMore }
187
245
  - Fetch the next sharedDb page.
188
246
  - async addEntry(topicName, data, { notify } = {}) | scopes: sharedDb:write
247
+ - Returns: { entry: { id, topicId, userId, username, profilePicUrl, data, createdAt, updatedAt } }
189
248
  - Append a shared JSON row, optionally creating a Twinkle notification from the canonical write.
190
249
  - async updateEntry(entryId, data, { notify } = {}) | scopes: sharedDb:write
250
+ - Returns: { entry: { id, topicId, userId, username, profilePicUrl, data, createdAt, updatedAt } }
191
251
  - Update a viewer-owned shared row, optionally notifying safe recipients from the canonical write.
192
252
  - async deleteEntry(entryId) | scopes: sharedDb:write
253
+ - Returns: { success: true }
193
254
 
194
255
  ### Twinkle.chat
195
256
  - async listRooms() | scopes: chat:read
257
+ - Returns: { rooms: [{ id, buildId, key, name, createdByUserId, createdAt, updatedAt }] }
196
258
  - async createRoom({ roomKey, name }) | scopes: chat:write
259
+ - Returns: { room: { id, buildId, key, name, createdByUserId, createdAt, updatedAt } }
197
260
  - async listMessages(roomKey, { cursor, limit } = {}) | scopes: chat:read
261
+ - Returns: { messages: [{ id, roomId, roomKey, userId, username, profilePicUrl, role, status, text, metadata, clientMessageId, createdAt, updatedAt }], cursor? }
198
262
  - async sendMessage(roomKey, textOrOptions, options) | scopes: chat:write
263
+ - Returns: { message: { id, buildId, roomId, roomKey, userId, username, profilePicUrl, role, status, text, metadata, clientMessageId, createdAt, updatedAt }, room: { id, buildId, key, name, createdByUserId, createdAt, updatedAt }, created }
199
264
  - async deleteMessage(messageId) | scopes: chat:write
265
+ - Returns: { success: true, messageId }
200
266
  - subscribe(roomKey, listener) | scopes: chat:read
267
+ - Returns: unsubscribe function
201
268
 
202
269
  ### Twinkle.world
203
270
  - async join({ worldKey = 'default', roomKey = 'main', instanceId = 'main', presence, player } = {}) | scopes: none
271
+ - Returns: { sessionId, session, room, players, snapshot, subscribe(listener), updatePresence(patch), send(actionOrType, data), leave() }
204
272
  - Join a realtime Build world room and receive a snapshot plus a session handle for presence updates, actions, and room events.
205
273
  - Example: const world = await Twinkle.world.join({ roomKey: 'town-square', presence: { x: 0, y: 0, z: 0, facing: 'south' }, player: { name: avatarName } });
206
274
  world.subscribe((event) => updateRemotePlayers(event.players));
207
275
  world.updatePresence({ x, y, z, facing });
208
276
  - leaveAll() | scopes: none
277
+ - Returns: void
209
278
  - Leave every active world session in the current iframe.
210
279
 
211
280
  ### Twinkle.users
212
281
  - async getUser(userId) | scopes: user:read
282
+ - Returns: { id, username, profilePicUrl, realName } | null
213
283
  - async getUsers({ search, userIds, cursor, limit } = {}) | scopes: users:read
284
+ - Returns: { users: [{ id, username, profilePicUrl, realName }], cursor? }
214
285
 
215
286
  ### Twinkle.reflections
216
287
  - async getDailyReflections({ userIds, cursor, lastId, limit } = {}) | scopes: dailyReflections:read
288
+ - Returns: { reflections: [{ id, userId, response, questionId, submittedAt, sharedAt, username, profilePicUrl, question }], cursor? }
217
289
  - async getDailyReflectionsByUser(userId, { cursor, lastId, limit } = {}) | scopes: dailyReflections:read
290
+ - Returns: { reflections: [{ id, userId, response, questionId, submittedAt, sharedAt, username, profilePicUrl, question }], cursor? }
218
291
 
219
292
  ### Twinkle.privateDb
220
293
  - async get(key) | scopes: privateDb:read
294
+ - Returns: { item: { id, key, value, updatedAt } | null }
221
295
  - Read one key from the default private per-user JSON store.
222
296
  - async list({ prefix, limit, cursor } = {}) | scopes: privateDb:read
297
+ - Returns: { items: [{ id, key, value, updatedAt }], cursor? }
223
298
  - List keys from the default private per-user JSON store.
224
299
  - async set(key, value) | scopes: privateDb:write
300
+ - Returns: { item: { id, key, value, updatedAt } }
225
301
  - Upsert one JSON-serializable value in the default private per-user store.
226
302
  - async remove(key) | scopes: privateDb:write
303
+ - Returns: { success: true, deleted: boolean }
227
304
  - Delete one key from the default private per-user JSON store.
228
305
 
229
306
  ### Twinkle.reminders
230
307
  - async list({ includeDisabled, limit } = {}) | scopes: reminders:read
308
+ - Returns: { reminders: [{ id, buildId, userId, title, body, targetPath, payload, isEnabled, schedule, lastTriggeredAt, createdAt, updatedAt }] }
231
309
  - async create({ title, body, targetPath, payload, schedule, isEnabled }) | scopes: reminders:write
310
+ - Returns: { reminder: { id, buildId, userId, title, body, targetPath, payload, isEnabled, schedule, lastTriggeredAt, createdAt, updatedAt } | null }
232
311
  - async update(reminderId, patch) | scopes: reminders:write
312
+ - Returns: { reminder: { id, buildId, userId, title, body, targetPath, payload, isEnabled, schedule, lastTriggeredAt, createdAt, updatedAt } | null }
233
313
  - async remove(reminderId) | scopes: reminders:write
314
+ - Returns: { success: true, deleted: boolean }
234
315
  - async getDue({ now, autoAcknowledge, limit } = {}) | scopes: reminders:read
316
+ - Returns: { now, reminders: [{ id, buildId, userId, title, body, targetPath, payload, isEnabled, schedule, lastTriggeredAt, createdAt, updatedAt }] }
235
317
 
236
318
  ## Examples
237
319