@zaflun/lumio-agent-skill 0.1.0

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.
@@ -0,0 +1,1729 @@
1
+ // src/install.ts
2
+ import { existsSync, mkdirSync, writeFileSync } from "fs";
3
+ import { join } from "path";
4
+
5
+ // src/content/skill.ts
6
+ var skillContent = `# Lumio Extension SDK
7
+
8
+ AI agent skill file for building Lumio Extensions.
9
+
10
+ ## Architecture
11
+
12
+ Lumio Extensions are sandboxed React apps that run inside a 3-layer iframe sandbox. The custom React reconciler serializes component trees to \`LumioComponentNode[]\` \u2014 there is no DOM access from extension code.
13
+
14
+ \`\`\`
15
+ 3-Layer Sandbox:
16
+ Host (lumio.vision / OBS browser source)
17
+ \u2514\u2500\u2500 Supervisor iframe (supervisor.extension.lumio.vision)
18
+ \u2514\u2500\u2500 Extension Runtime iframe (ext.lumio.vision)
19
+ \u2514\u2500\u2500 Web Worker (server function execution)
20
+ \`\`\`
21
+
22
+ \`\`\`
23
+ Extension Project:
24
+ lumio.config.json <- Extension manifest (ID, name, category, permissions, targets, config_schema)
25
+ src/editor.tsx <- Dashboard editor panel (sidebar in overlay editor)
26
+ src/layer.tsx <- OBS overlay surface (transparent, read-only inputs)
27
+ src/interactive.tsx <- Audience interaction page (optional)
28
+ server/schema.ts <- DB schema (defineTable + validators)
29
+ server/functions.ts <- Server functions (queries, mutations, actions)
30
+ \`\`\`
31
+
32
+ **Runtime flow:**
33
+ 1. Extension Runtime iframe loads from \`ext.lumio.vision/ext/{slug}/layer?token=lm_ext_xxx&install_id=uuid\`
34
+ 2. \`Lumio.render(<Component />, { target: "layer" })\` must be called within 10 seconds
35
+ 3. The custom React reconciler serializes the tree to \`LumioComponentNode[]\` via postMessage to the Supervisor
36
+ 4. The Supervisor validates and forwards the node tree to the Host for rendering
37
+ 5. Server functions execute inside a Web Worker in a V8 isolate sandbox (256 MB memory, 10s CPU timeout)
38
+ 6. Cross-frame messaging uses \`WorkerBridge\` (exported from \`@zaflun/lumio-sdk\`; replaces the legacy \`PostMessageBridge\`)
39
+
40
+ **Key constraints:**
41
+ - No direct DOM access \u2014 the reconciler serializes to \`LumioComponentNode[]\`, never touches the DOM
42
+ - localStorage and sessionStorage are blocked in the Extension Runtime (separate origin isolation)
43
+ - No access to Lumio core data (users, accounts, overlays) except via SDK hooks
44
+ - Network access only via \`ctx.fetch()\` with declared \`egress.allowHosts\` (enforced by the Supervisor)
45
+ - Extension tokens (\`lm_ext_*\`) are scoped and short-lived (15 min, auto-refreshed via postMessage)
46
+ - Widget tokens (\`lm_widget_*\`) are permanent and scoped to a widget_id (Widget Gallery)
47
+
48
+ ## Core Concepts
49
+
50
+ ### Three Surfaces
51
+
52
+ | Target | Where | Purpose |
53
+ |--------|-------|---------|
54
+ | \`editor\` | Dashboard overlay editor sidebar | Config UI, settings, content management |
55
+ | \`layer\` | OBS Browser Source (transparent) | Visual overlay in stream |
56
+ | \`interactive\` | Standalone page (\`/ext/{slug}\`) | Viewer interaction (votes, games) |
57
+
58
+ All three surfaces share storage via \`useExtensionStorage()\` \u2014 changes in editor propagate to layer in real-time.
59
+
60
+ ### lumio.config.json
61
+
62
+ \`\`\`json
63
+ {
64
+ "$schema": "https://docs.lumio.vision/schemas/lumio.config.schema.json",
65
+ "extensionId": "uuid",
66
+ "slug": "k8f2x-my-extension",
67
+ "name": "My Extension",
68
+ "description": "Description",
69
+ "category": "widget",
70
+ "version": "1.0.0",
71
+ "targets": ["editor", "layer"],
72
+ "server": true,
73
+ "permissions": ["events:read", "chat:read"],
74
+ "egress": {
75
+ "allowHosts": ["api.example.com"],
76
+ "allowPorts": [443]
77
+ },
78
+ "visibility": "public",
79
+ "pricing": { "type": "free" },
80
+ "config_schema": {
81
+ "fields": [
82
+ { "key": "title", "type": "string", "label": "Widget Title", "default": "My Widget" },
83
+ { "key": "showBorder", "type": "boolean", "label": "Show Border", "default": true }
84
+ ]
85
+ }
86
+ }
87
+ \`\`\`
88
+
89
+ **Slug format:** \`{short_id}-{kebab-name}\` (e.g. \`k8f2x-my-extension\`). Auto-generated on \`lumio init\`. The store URL is \`/store/{slug}\` (e.g. \`/store/k8f2x-my-extension\`).
90
+
91
+ **Categories:** \`widget\`, \`extension\`, \`overlay\`, \`bot_module\`, \`integration\`, \`theme\`
92
+
93
+ **Pricing:** \`{ "type": "free" }\` | \`{ "type": "one_time", "amount": 499, "currency": "eur" }\` | \`{ "type": "subscription", "amount": 199, "currency": "eur", "interval": "monthly" }\`
94
+
95
+ **Access control:**
96
+ - \`"visibility": "public"\` \u2014 listed in Widget Gallery
97
+ - \`"visibility": "unlisted"\` \u2014 accessible by direct link only
98
+ - Private invite: grant \`free_access\` to specific accounts via dashboard
99
+
100
+ **Revenue split:** Configurable per developer (default 70/30 developer/Lumio).
101
+
102
+ **Feature gates:** The following Lumio plan features must be enabled for extension functionality:
103
+ - \`feature:extensions\` \u2014 allows using extensions at all
104
+ - \`feature:extension_development\` \u2014 allows publishing and developing extensions
105
+ - \`feature:widgets\` \u2014 allows Widget Gallery access
106
+
107
+ ### Developer Teams
108
+
109
+ Extensions are owned by a solo developer or a team. Team members share publishing rights and revenue. Select team on \`lumio init\` or manage via dashboard.
110
+
111
+ ### Widget Gallery
112
+
113
+ Widgets installed from the Widget Gallery use \`lm_widget_*\` tokens (permanent, scoped to widget_id). Unlike extension installs (which use \`lm_ext_*\`, 15 min TTL), widget tokens persist across sessions and are used for Widget Gallery standalone embeds.
114
+
115
+ ## Quick Start
116
+
117
+ \`\`\`bash
118
+ # Install CLI
119
+ npm install -g @zaflun/lumio-cli
120
+
121
+ # Authenticate
122
+ lumio login
123
+
124
+ # Log out (clears stored credentials)
125
+ lumio logout
126
+
127
+ # Create extension (registers ID on Lumio, scaffolds project; prompts for team if applicable)
128
+ lumio init my-widget
129
+
130
+ # Start dev server with hot reload + sandbox
131
+ lumio dev
132
+
133
+ # Build for deployment
134
+ lumio build
135
+
136
+ # Deploy (creates draft, submit via dashboard for review)
137
+ lumio deploy
138
+ \`\`\`
139
+
140
+ ## Components
141
+
142
+ 15 SDK components (all accept \`style: VisionStyle\`):
143
+
144
+ **Layout:** \`Box\`, \`VStack\`, \`HStack\`, \`CompactView\`, \`List\`
145
+ **Display:** \`Text\` (variant: heading/muted/default), \`Image\` (objectFit), \`Animated\`
146
+ **Input:** \`Button\` (variant: default/destructive), \`Toggle\`, \`TextField\`, \`TextArea\`, \`NumberField\`, \`Dropdown\`, \`Slider\`
147
+
148
+ **VisionStyle** is a typed CSS subset (allowed: flexbox, grid, \`position: relative\` / \`absolute\`, sizing, spacing, colors, border, typography, transitions; blocked: \`fixed\`, \`sticky\`, CSS expressions).
149
+
150
+ On OBS layer: input components become read-only (buttons hidden, toggles show on/off text).
151
+
152
+ See \`components.md\` for full props and examples.
153
+
154
+ ## Hooks
155
+
156
+ 11 SDK hooks:
157
+
158
+ | Hook | Returns | Purpose |
159
+ |------|---------|---------|
160
+ | \`useExtensionStorage()\` | \`[storage, setStorage]\` | Persistent KV store, synced across targets |
161
+ | \`useVisionState(key, default)\` | \`[value, setValue]\` | useState for single keys |
162
+ | \`useExtensionContext()\` | \`{ extensionId, target, overlayId, interactiveUrl }\` | Extension metadata |
163
+ | \`useLumioEvent(eventType)\` | \`event\` | Live events (chat, alerts, spotify) \u2014 type-safe: \`eventType\` is \`LumioEventType\` |
164
+ | \`useLumioConfig()\` | \`[config, setConfig]\` | Extension config read/write (uses config_schema fields) |
165
+ | \`useLumioTheme()\` | \`theme\` | Current theme (dark/light/custom) |
166
+ | \`useLumioIdentity()\` | \`{ userId, accountId, role }\` | Current user context |
167
+ | \`useLumioAction(action, params)\` | \`{ execute, data, isLoading, error }\` | Trigger Lumio actions \u2014 type-safe: \`action\` is \`LumioActionType\` |
168
+ | \`useQuery(fn, params?)\` | \`{ data, isLoading, error, refetch }\` | Call server query |
169
+ | \`useMutation(fn)\` | \`{ mutate, mutateAsync, data, isLoading, error }\` | Call server mutation/action |
170
+ | \`useAnimation(preset, options?)\` | \`animationProps\` | SDK animation presets for \`Animated\` component |
171
+
172
+ **Important:** \`setStorage\` must always spread: \`setStorage({ ...storage, key: value })\`
173
+
174
+ ## Server Functions
175
+
176
+ Two types:
177
+
178
+ **Declarative** (preferred, transpiled to SQL, no JS runtime):
179
+ \`\`\`typescript
180
+ import { queryRows, insertRow, patchRow, deleteRow } from "@zaflun/lumio-sdk/server";
181
+ export const getItems = queryRows("items");
182
+ export const addItem = insertRow("items", { text: arg("text") }, { editorOnly: true });
183
+ export const updateItem = patchRow("items", arg("id"), { done: arg("done") });
184
+ export const removeItem = deleteRow("items", arg("id"), { editorOnly: true });
185
+ \`\`\`
186
+
187
+ **Handler-based** (V8 isolate Web Worker, full control):
188
+ \`\`\`typescript
189
+ import { action, arg, v } from "@zaflun/lumio-sdk/server";
190
+ export const fetchScores = action({
191
+ args: { league: v.string() },
192
+ handler: async (ctx, args) => {
193
+ // ctx.db, ctx.fetch, ctx.secrets, ctx.auth all available
194
+ const res = await ctx.fetch("https://api.example.com/scores?league=" + args.league);
195
+ return res.json();
196
+ },
197
+ });
198
+ \`\`\`
199
+
200
+ **Extension Actions** via \`useLumioAction()\` (21 total, \`action\` is \`LumioActionType\`):
201
+ \`\`\`tsx
202
+ // Trigger platform actions (requires matching permission in lumio.config.json)
203
+ const { execute: sendChat } = useLumioAction("chat:send", {});
204
+ const { execute: banUser } = useLumioAction("chat:ban", {});
205
+ const { execute: unbanUser } = useLumioAction("chat:unban", {});
206
+ const { execute: timeoutUser } = useLumioAction("chat:timeout", {});
207
+ const { execute: setScene } = useLumioAction("obs:set_scene", {});
208
+ const { execute: startRecording } = useLumioAction("obs:start_recording", {});
209
+ const { execute: stopRecording } = useLumioAction("obs:stop_recording", {});
210
+ const { execute: emitEvent } = useLumioAction("events:emit", {});
211
+ const { execute: updateLayer } = useLumioAction("overlay:update_layer", {});
212
+ const { execute: spotifyPlay } = useLumioAction("spotify:play", {});
213
+ const { execute: spotifyPause } = useLumioAction("spotify:pause", {});
214
+ const { execute: spotifySkip } = useLumioAction("spotify:skip", {});
215
+ const { execute: spotifySeek } = useLumioAction("spotify:seek", {});
216
+ const { execute: spotifyVolume } = useLumioAction("spotify:volume", {});
217
+ const { execute: spotifyShuffle } = useLumioAction("spotify:shuffle", {});
218
+ const { execute: spotifyRepeat } = useLumioAction("spotify:repeat", {});
219
+ const { execute: spotifyQueueAdd } = useLumioAction("spotify:queue_add", {});
220
+ \`\`\`
221
+
222
+ See \`server-functions.md\` for schema definition, storage scoping, and advanced patterns.
223
+
224
+ ## CLI Commands
225
+
226
+ 12 commands provided by \`@zaflun/lumio-cli\`:
227
+
228
+ | Command | Description |
229
+ |---------|-------------|
230
+ | \`lumio login\` | PKCE OAuth via browser (\`lumio login --token <token>\` for CI) |
231
+ | \`lumio logout\` | Clear stored credentials |
232
+ | \`lumio whoami\` | Show current user + account |
233
+ | \`lumio init <name>\` | Scaffold extension project, register extension ID (prompts for team) |
234
+ | \`lumio dev\` | Local dev server with hot reload + mock events |
235
+ | \`lumio build\` | Bundle as standalone HTML + source archive |
236
+ | \`lumio deploy\` | Upload to Lumio (status: draft). Flags: \`-v\`, \`-d\`, \`--breaking\`, \`--draft\` |
237
+ | \`lumio status\` | Check review status of latest version |
238
+ | \`lumio logs\` | Stream server function logs |
239
+ | \`lumio secrets\` | Manage server-side secrets |
240
+ | \`lumio team\` | Manage developer team members |
241
+ | \`lumio config\` | Show/set CLI configuration |
242
+
243
+ See \`cli-workflows.md\` for detailed usage and examples.
244
+
245
+ ## Auth
246
+
247
+ 9 token types in the Lumio auth system. Extensions use two:
248
+
249
+ - **\`lm_ext_*\`** \u2014 Extension tokens: 15 min TTL, scoped to install_id + extension_id + account_id, auto-refreshed via postMessage
250
+ - **\`lm_widget_*\`** \u2014 Widget tokens: permanent, scoped to widget_id (Widget Gallery standalone embeds)
251
+
252
+ Extension tokens have no RBAC permissions (\`has_permission()\` always returns false) and can only access \`ext_{extension_id}.*\` DB tables via server functions.
253
+
254
+ **\`editorOnly: true\`** on server functions restricts mutations to editor surface only (layer/interactive cannot call them).
255
+
256
+ See \`auth.md\` for all 9 token types and the full token lifecycle.
257
+
258
+ ## Extension Version Flow
259
+
260
+ \`\`\`
261
+ lumio deploy -> draft -> [Submit via dashboard] -> pending_review
262
+ -> [Admin approves] -> testing -> [Developer requests publish] -> pending_publish
263
+ -> [Admin approves] -> published
264
+ -> [Admin rejects] -> rejected (fix + resubmit)
265
+ \`\`\`
266
+
267
+ ## Common Patterns
268
+
269
+ ### Editor + Layer sync
270
+ \`\`\`tsx
271
+ // editor.tsx
272
+ const [storage, setStorage] = useExtensionStorage();
273
+ <Toggle label="Show" checked={storage.visible ?? false}
274
+ onChange={(v) => setStorage({ ...storage, visible: v })} />
275
+
276
+ // layer.tsx
277
+ const [storage] = useExtensionStorage();
278
+ if (!storage.visible) return null;
279
+ return <Text content={storage.text} />;
280
+ \`\`\`
281
+
282
+ ### Live events
283
+ \`\`\`tsx
284
+ // eventType is LumioEventType \u2014 autocomplete shows all 49 valid event strings
285
+ const event = useLumioEvent("twitch:follower");
286
+ // event updates automatically when a new follower event arrives
287
+ \`\`\`
288
+
289
+ **All 49 event types** (\`LumioEventType\`):
290
+
291
+ Twitch (22): \`twitch:follower\`, \`twitch:subscriber\`, \`twitch:subscribe\`, \`twitch:gift_sub\`, \`twitch:resub\`, \`twitch:cheer\`, \`twitch:raid\`, \`twitch:redemption\`, \`twitch:ban\`, \`twitch:unban\`, \`twitch:hype_train\`, \`twitch:poll\`, \`twitch:prediction\`, \`twitch:goal\`, \`twitch:ad_break\`, \`twitch:stream_update\`, \`twitch:chat\`
292
+
293
+ YouTube (7): \`youtube:subscriber\`, \`youtube:superchat\`, \`youtube:supersticker\`, \`youtube:gift_membership\`, \`youtube:gift_membership_received\`, \`youtube:poll\`, \`youtube:chat\`
294
+
295
+ Kick (6): \`kick:follower\`, \`kick:subscribe\`, \`kick:gift\`, \`kick:stream_online\`, \`kick:stream_offline\`, \`kick:chat\`
296
+
297
+ Trovo (3): \`trovo:subscribe\`, \`trovo:spell\`, \`trovo:chat\`
298
+
299
+ Spotify (11): \`spotify:track\`, \`spotify:play\`, \`spotify:pause\`, \`spotify:skip\`, \`spotify:volume\`, \`spotify:queue_add\`, \`spotify:device\`, \`spotify:playlist_add\`, \`spotify:playlist_remove\`, \`spotify:playlist_create\`, \`spotify:playlist_delete\`
300
+
301
+ ### Server queries
302
+ \`\`\`tsx
303
+ const { data: items } = useQuery("getItems");
304
+ const { mutate: addItem } = useMutation("addItem");
305
+ addItem({ text: "New item" });
306
+ \`\`\`
307
+
308
+ ### Lumio actions
309
+ \`\`\`tsx
310
+ const { execute } = useLumioAction("chat:send", {});
311
+ execute({ message: "Hello from my extension!" });
312
+ \`\`\`
313
+
314
+ ### Animations
315
+ \`\`\`tsx
316
+ import { Animated, useAnimation } from "@zaflun/lumio-sdk";
317
+ const anim = useAnimation("slide-in", { duration: 300 });
318
+ <Animated {...anim}><Text content="Animated!" /></Animated>
319
+ \`\`\`
320
+
321
+ ## Troubleshooting
322
+
323
+ | Error | Cause | Fix |
324
+ |-------|-------|-----|
325
+ | \`RENDER_TIMEOUT\` | \`Lumio.render()\` not called within 10s | Ensure render call is at module top level |
326
+ | \`EXTENSION_NOT_FOUND\` | Invalid extensionId in config | Run \`lumio init\` or create via dashboard |
327
+ | \`EGRESS_BLOCKED\` | Fetch to undeclared host | Add host to \`egress.allowHosts\` in config |
328
+ | \`STORAGE_QUOTA\` | Exceeded storage limit | Reduce data or request limit increase |
329
+ | \`EDITOR_ONLY\` | Mutation called from layer/interactive | Remove \`editorOnly\` or restrict to editor |
330
+ | \`SUPERVISOR_BLOCKED\` | Supervisor rejected a postMessage | Check message origin matches ext.lumio.vision |
331
+ | \`WORKER_CREATION_FAILED\` | Web Worker could not be created | Verify server functions bundle is valid JS |
332
+ | \`STORAGE_DOM_EXCEPTION\` | localStorage/sessionStorage access | Use \`useExtensionStorage()\` instead (storage blocked) |
333
+
334
+ See \`troubleshooting.md\` for the full error reference.
335
+ `;
336
+
337
+ // src/content/components.ts
338
+ var componentsContent = `# Lumio SDK Components
339
+
340
+ All 15 components available in \`@zaflun/lumio-sdk\`. Every component accepts an optional \`style: VisionStyle\` prop.
341
+
342
+ > **Note:** There is no direct DOM access from extension code. The custom React reconciler serializes component trees to \`LumioComponentNode[]\` via postMessage \u2014 you must use SDK components exclusively.
343
+
344
+ ## Layout Components
345
+
346
+ ### Box
347
+
348
+ Generic container with flexbox support.
349
+
350
+ \`\`\`tsx
351
+ import { Box } from "@zaflun/lumio-sdk";
352
+
353
+ <Box style={{ display: "flex", gap: 8, padding: 16 }}>
354
+ {children}
355
+ </Box>
356
+ \`\`\`
357
+
358
+ **Props:**
359
+ - \`children: ReactNode\`
360
+ - \`style?: VisionStyle\`
361
+
362
+ ### VStack
363
+
364
+ Vertical stack layout (column direction).
365
+
366
+ \`\`\`tsx
367
+ import { VStack } from "@zaflun/lumio-sdk";
368
+
369
+ <VStack style={{ gap: 12 }}>
370
+ <Text content="Line 1" />
371
+ <Text content="Line 2" />
372
+ </VStack>
373
+ \`\`\`
374
+
375
+ **Props:**
376
+ - \`children: ReactNode\`
377
+ - \`gap?: number\` (default: 8)
378
+ - \`style?: VisionStyle\`
379
+
380
+ ### HStack
381
+
382
+ Horizontal stack layout (row direction).
383
+
384
+ \`\`\`tsx
385
+ import { HStack } from "@zaflun/lumio-sdk";
386
+
387
+ <HStack style={{ gap: 8 }}>
388
+ <Text content="Live" />
389
+ <Text content="Score: 3-1" />
390
+ </HStack>
391
+ \`\`\`
392
+
393
+ **Props:**
394
+ - \`children: ReactNode\`
395
+ - \`gap?: number\` (default: 8)
396
+ - \`style?: VisionStyle\`
397
+
398
+ ### CompactView
399
+
400
+ Panel container with title bar. Standard layout for editor surfaces.
401
+
402
+ \`\`\`tsx
403
+ import { CompactView } from "@zaflun/lumio-sdk";
404
+
405
+ <CompactView title="Settings">
406
+ <TextField label="Name" value={name} onChange={setName} />
407
+ <Toggle label="Visible" checked={visible} onChange={setVisible} />
408
+ </CompactView>
409
+ \`\`\`
410
+
411
+ **Props:**
412
+ - \`title: string\`
413
+ - \`children: ReactNode\`
414
+ - \`collapsible?: boolean\` (default: false)
415
+ - \`defaultCollapsed?: boolean\` (default: false)
416
+ - \`style?: VisionStyle\`
417
+
418
+ ### List
419
+
420
+ Scrollable list container with virtualization for large datasets.
421
+
422
+ \`\`\`tsx
423
+ import { List } from "@zaflun/lumio-sdk";
424
+
425
+ <List
426
+ items={messages}
427
+ renderItem={(msg) => <Text content={msg.text} />}
428
+ maxHeight={400}
429
+ />
430
+ \`\`\`
431
+
432
+ **Props:**
433
+ - \`items: T[]\`
434
+ - \`renderItem: (item: T, index: number) => ReactNode\`
435
+ - \`maxHeight?: number\`
436
+ - \`emptyText?: string\`
437
+ - \`style?: VisionStyle\`
438
+
439
+ ## Display Components
440
+
441
+ ### Text
442
+
443
+ Text display with variants.
444
+
445
+ \`\`\`tsx
446
+ import { Text } from "@zaflun/lumio-sdk";
447
+
448
+ <Text content="Hello World" variant="heading" />
449
+ <Text content="Subtitle" variant="muted" />
450
+ <Text content="Normal text" />
451
+ \`\`\`
452
+
453
+ **Props:**
454
+ - \`content: string\`
455
+ - \`variant?: "heading" | "muted" | "default"\` (default: "default")
456
+ - \`style?: VisionStyle\`
457
+
458
+ ### Image
459
+
460
+ Image display with fit options.
461
+
462
+ \`\`\`tsx
463
+ import { Image } from "@zaflun/lumio-sdk";
464
+
465
+ <Image src="https://example.com/logo.png" alt="Logo" objectFit="contain" />
466
+ \`\`\`
467
+
468
+ **Props:**
469
+ - \`src: string\`
470
+ - \`alt: string\`
471
+ - \`objectFit?: "cover" | "contain" | "fill" | "none"\` (default: "cover")
472
+ - \`width?: number | string\`
473
+ - \`height?: number | string\`
474
+ - \`style?: VisionStyle\`
475
+
476
+ ## Input Components
477
+
478
+ **Important:** On OBS layer surface, all input components become read-only. Buttons are hidden, toggles show "On"/"Off" text, dropdowns show the selected value.
479
+
480
+ ### Button
481
+
482
+ Clickable button with variants.
483
+
484
+ \`\`\`tsx
485
+ import { Button } from "@zaflun/lumio-sdk";
486
+
487
+ <Button label="Save" onClick={handleSave} />
488
+ <Button label="Delete" variant="destructive" onClick={handleDelete} />
489
+ \`\`\`
490
+
491
+ **Props:**
492
+ - \`label: string\`
493
+ - \`onClick: () => void\`
494
+ - \`variant?: "default" | "destructive"\` (default: "default")
495
+ - \`disabled?: boolean\`
496
+ - \`style?: VisionStyle\`
497
+
498
+ ### Toggle
499
+
500
+ Boolean toggle switch.
501
+
502
+ \`\`\`tsx
503
+ import { Toggle } from "@zaflun/lumio-sdk";
504
+
505
+ <Toggle label="Show overlay" checked={visible} onChange={setVisible} />
506
+ \`\`\`
507
+
508
+ **Props:**
509
+ - \`label: string\`
510
+ - \`checked: boolean\`
511
+ - \`onChange: (checked: boolean) => void\`
512
+ - \`disabled?: boolean\`
513
+ - \`style?: VisionStyle\`
514
+
515
+ ### TextField
516
+
517
+ Single-line text input.
518
+
519
+ \`\`\`tsx
520
+ import { TextField } from "@zaflun/lumio-sdk";
521
+
522
+ <TextField label="Title" value={title} onChange={setTitle} placeholder="Enter title..." />
523
+ \`\`\`
524
+
525
+ **Props:**
526
+ - \`label: string\`
527
+ - \`value: string\`
528
+ - \`onChange: (value: string) => void\`
529
+ - \`placeholder?: string\`
530
+ - \`disabled?: boolean\`
531
+ - \`maxLength?: number\`
532
+ - \`style?: VisionStyle\`
533
+
534
+ ### TextArea
535
+
536
+ Multi-line text input.
537
+
538
+ \`\`\`tsx
539
+ import { TextArea } from "@zaflun/lumio-sdk";
540
+
541
+ <TextArea label="Description" value={desc} onChange={setDesc} rows={4} />
542
+ \`\`\`
543
+
544
+ **Props:**
545
+ - \`label: string\`
546
+ - \`value: string\`
547
+ - \`onChange: (value: string) => void\`
548
+ - \`placeholder?: string\`
549
+ - \`rows?: number\` (default: 3)
550
+ - \`maxLength?: number\`
551
+ - \`disabled?: boolean\`
552
+ - \`style?: VisionStyle\`
553
+
554
+ ### NumberField
555
+
556
+ Numeric input with optional min/max/step.
557
+
558
+ \`\`\`tsx
559
+ import { NumberField } from "@zaflun/lumio-sdk";
560
+
561
+ <NumberField label="Score" value={score} onChange={setScore} min={0} max={100} step={1} />
562
+ \`\`\`
563
+
564
+ **Props:**
565
+ - \`label: string\`
566
+ - \`value: number\`
567
+ - \`onChange: (value: number) => void\`
568
+ - \`min?: number\`
569
+ - \`max?: number\`
570
+ - \`step?: number\`
571
+ - \`disabled?: boolean\`
572
+ - \`style?: VisionStyle\`
573
+
574
+ ### Dropdown
575
+
576
+ Select dropdown with options.
577
+
578
+ \`\`\`tsx
579
+ import { Dropdown } from "@zaflun/lumio-sdk";
580
+
581
+ <Dropdown
582
+ label="League"
583
+ value={league}
584
+ onChange={setLeague}
585
+ options={[
586
+ { label: "NFL", value: "nfl" },
587
+ { label: "NBA", value: "nba" },
588
+ { label: "NHL", value: "nhl" },
589
+ ]}
590
+ />
591
+ \`\`\`
592
+
593
+ **Props:**
594
+ - \`label: string\`
595
+ - \`value: string\`
596
+ - \`onChange: (value: string) => void\`
597
+ - \`options: { label: string; value: string }[]\`
598
+ - \`placeholder?: string\`
599
+ - \`disabled?: boolean\`
600
+ - \`style?: VisionStyle\`
601
+
602
+ ### Slider
603
+
604
+ Range slider input.
605
+
606
+ \`\`\`tsx
607
+ import { Slider } from "@zaflun/lumio-sdk";
608
+
609
+ <Slider label="Volume" value={volume} onChange={setVolume} min={0} max={100} step={1} />
610
+ \`\`\`
611
+
612
+ **Props:**
613
+ - \`label: string\`
614
+ - \`value: number\`
615
+ - \`onChange: (value: number) => void\`
616
+ - \`min: number\`
617
+ - \`max: number\`
618
+ - \`step?: number\` (default: 1)
619
+ - \`showValue?: boolean\` (default: true)
620
+ - \`disabled?: boolean\`
621
+ - \`style?: VisionStyle\`
622
+
623
+ ### Animated
624
+
625
+ Wrapper component for SDK-managed entrance/exit animations. Use with \`useAnimation()\` hook \u2014 do not use raw CSS keyframes for entrance/exit effects.
626
+
627
+ \`\`\`tsx
628
+ import { Animated, useAnimation } from "@zaflun/lumio-sdk";
629
+
630
+ // Use a preset
631
+ const anim = useAnimation("slide-in", { duration: 400, delay: 0 });
632
+ <Animated {...anim}>
633
+ <Text content="I animate in!" />
634
+ </Animated>
635
+
636
+ // Fade out on condition
637
+ const exitAnim = useAnimation("fade-out", { duration: 200 });
638
+ <Animated {...exitAnim} trigger={isHiding}>
639
+ <Box>{children}</Box>
640
+ </Animated>
641
+ \`\`\`
642
+
643
+ **Available presets:** \`fade-in\`, \`fade-out\`, \`slide-in\`, \`slide-out\`, \`scale-in\`, \`scale-out\`, \`bounce-in\`
644
+
645
+ **Props:**
646
+ - \`children: ReactNode\`
647
+ - \`trigger?: boolean\` (play animation when true; default: plays on mount)
648
+ - \`style?: VisionStyle\`
649
+ - _(spread \`useAnimation()\` return value for preset props)_
650
+
651
+ **\`useAnimation(preset, options?)\`**
652
+ - \`preset: string\` \u2014 animation preset name
653
+ - \`options?: { duration?: number; delay?: number; easing?: string }\`
654
+ - Returns: props object to spread onto \`<Animated>\`
655
+
656
+ **Best practices:**
657
+ - Always use SDK presets instead of raw CSS animations for entrance/exit effects \u2014 the reconciler maps them to allowed VisionStyle \`animation\` properties
658
+ - Keep animation durations short on the OBS layer surface (< 500ms) to avoid frame drops
659
+ - Avoid animating layout-affecting properties (\`width\`, \`height\`) on the layer; prefer \`opacity\` and \`transform\`
660
+
661
+ ## VisionStyle
662
+
663
+ Typed CSS subset accepted by all components. Sanitized by the host \u2014 only safe properties are allowed.
664
+
665
+ **Allowed:**
666
+ - Flexbox: \`display\`, \`flexDirection\`, \`justifyContent\`, \`alignItems\`, \`gap\`, \`flex\`, \`flexWrap\`
667
+ - Grid: \`gridTemplateColumns\`, \`gridTemplateRows\`, \`gridGap\`
668
+ - Positioning: \`position: "relative" | "absolute"\`, \`top\`, \`right\`, \`bottom\`, \`left\`, \`zIndex\`
669
+ - Sizing: \`width\`, \`height\`, \`minWidth\`, \`minHeight\`, \`maxWidth\`, \`maxHeight\`
670
+ - Spacing: \`margin\`, \`padding\` (and directional variants)
671
+ - Colors: \`color\`, \`backgroundColor\`, \`borderColor\`, \`opacity\`
672
+ - Border: \`border\`, \`borderWidth\`, \`borderRadius\`, \`borderStyle\`
673
+ - Typography: \`fontSize\`, \`fontWeight\`, \`fontFamily\`, \`lineHeight\`, \`textAlign\`, \`textTransform\`
674
+ - Transform: \`transform\`, \`transformOrigin\`
675
+ - Animations: \`transition\`, \`animation\`, \`animationDuration\`
676
+ - Other: \`objectFit\`, \`pointerEvents\`, \`cursor\`, \`overflow\`, \`overflowX\`, \`overflowY\`
677
+
678
+ **Blocked:**
679
+ - \`position: "fixed" | "sticky"\` \u2014 iframe escape prevention
680
+ - CSS expressions
681
+ - \`javascript:\` URLs
682
+ `;
683
+
684
+ // src/content/server-functions.ts
685
+ var serverFunctionsContent = `# Server Functions
686
+
687
+ Server functions run on the Lumio API, not in the browser. They access the extension's isolated DB schema (\`ext_{extension_id}\`).
688
+
689
+ ## Schema Definition
690
+
691
+ Define your data model in \`server/schema.ts\`:
692
+
693
+ \`\`\`typescript
694
+ import { defineSchema, defineTable, v } from "@zaflun/lumio-sdk/server";
695
+
696
+ export default defineSchema({
697
+ items: defineTable({
698
+ text: v.string(),
699
+ done: v.boolean(),
700
+ priority: v.number().optional(),
701
+ tags: v.array(v.string()).optional(),
702
+ }).storage("instance"),
703
+
704
+ settings: defineTable({
705
+ key: v.string(),
706
+ value: v.string(),
707
+ }).storage("account"),
708
+ });
709
+ \`\`\`
710
+
711
+ ### Validators
712
+
713
+ | Validator | Description |
714
+ |-----------|-------------|
715
+ | \`v.string()\` | String field |
716
+ | \`v.number()\` | Numeric field |
717
+ | \`v.boolean()\` | Boolean field |
718
+ | \`v.array(v.string())\` | Array of strings |
719
+ | \`v.object({ key: v.string() })\` | Nested object |
720
+ | \`.optional()\` | Mark field as nullable |
721
+
722
+ ### Storage Scoping
723
+
724
+ \`\`\`typescript
725
+ defineTable({...}).storage("instance") // Per overlay instance (most isolated)
726
+ defineTable({...}).storage("overlay") // Per overlay (shared across instances)
727
+ defineTable({...}).storage("account") // Per account (shared across overlays)
728
+ defineTable({...}).storage("global") // Global (shared across all accounts \u2014 admin only)
729
+ \`\`\`
730
+
731
+ See \`runtime-scopes.md\` for when to use each scope.
732
+
733
+ ## Declarative Functions (preferred)
734
+
735
+ Transpiled directly to parameterized SQL \u2014 no JS runtime, fast, secure.
736
+
737
+ \`\`\`typescript
738
+ import { queryRows, insertRow, patchRow, deleteRow, arg, filter } from "@zaflun/lumio-sdk/server";
739
+
740
+ // Read all rows
741
+ export const getItems = queryRows("items");
742
+
743
+ // Read with filter
744
+ export const getDoneItems = queryRows("items", {
745
+ filter: filter("done", "=", true),
746
+ });
747
+
748
+ // Insert a row (editorOnly: only callable from editor surface)
749
+ export const addItem = insertRow("items", {
750
+ text: arg("text"),
751
+ done: false,
752
+ priority: arg("priority"),
753
+ }, { editorOnly: true });
754
+
755
+ // Update specific fields
756
+ export const toggleItem = patchRow("items", arg("id"), {
757
+ done: arg("done"),
758
+ });
759
+
760
+ // Delete a row
761
+ export const removeItem = deleteRow("items", arg("id"), { editorOnly: true });
762
+ \`\`\`
763
+
764
+ ### \`arg(name)\` and expressions
765
+
766
+ - \`arg("name")\` \u2014 reads from the client-provided arguments
767
+ - \`identity()\` \u2014 returns \`{ userId, accountId }\` of the caller
768
+ - \`secret("KEY")\` \u2014 reads a server-side secret (never exposed to client)
769
+ - \`filter(field, op, value)\` \u2014 query filter (=, !=, >, <, >=, <=, LIKE, IN)
770
+
771
+ ## Handler-Based Functions
772
+
773
+ For complex logic that cannot be expressed declaratively. Runs in V8 isolate sandbox.
774
+
775
+ \`\`\`typescript
776
+ import { query, mutation, action, v } from "@zaflun/lumio-sdk/server";
777
+
778
+ // Read-only query
779
+ export const getLeaderboard = query({
780
+ args: { limit: v.number().optional() },
781
+ handler: async (ctx, args) => {
782
+ const rows = await ctx.db.get("scores", {
783
+ order: { score: "desc" },
784
+ limit: args.limit ?? 10,
785
+ });
786
+ return rows;
787
+ },
788
+ });
789
+
790
+ // Write operation
791
+ export const submitScore = mutation({
792
+ args: { playerName: v.string(), score: v.number() },
793
+ handler: async (ctx, args) => {
794
+ return ctx.db.insert("scores", {
795
+ player_name: args.playerName,
796
+ score: args.score,
797
+ });
798
+ },
799
+ editorOnly: false,
800
+ });
801
+
802
+ // External API call (requires egress.allowHosts in config)
803
+ // Egress allowlist is enforced by the Supervisor \u2014 requests to undeclared hosts are rejected before reaching the network
804
+ export const fetchScores = action({
805
+ args: { sport: v.string(), league: v.string() },
806
+ handler: async (ctx, args) => {
807
+ const apiKey = ctx.secrets.get("ESPN_API_KEY");
808
+ const res = await ctx.fetch(
809
+ \`https://site.api.espn.com/apis/v2/sports/\${args.sport}/\${args.league}/scoreboard\`,
810
+ { headers: { "Authorization": \`Bearer \${apiKey}\` } }
811
+ );
812
+ return res.json();
813
+ },
814
+ });
815
+ \`\`\`
816
+
817
+ ### Handler Context (\`ctx\`)
818
+
819
+ Handler-based functions run inside a V8 isolate Web Worker. The \`ctx\` object provides the full sandbox API:
820
+
821
+ | Property | Description |
822
+ |----------|-------------|
823
+ | \`ctx.db.get(table, options?)\` | Read rows from extension DB schema |
824
+ | \`ctx.db.insert(table, data)\` | Insert row |
825
+ | \`ctx.db.patch(table, id, data)\` | Update row |
826
+ | \`ctx.db.delete(table, id)\` | Delete row |
827
+ | \`ctx.fetch(url, options?)\` | HTTP request (egress allowlist enforced by Supervisor) |
828
+ | \`ctx.secrets.get(key)\` | Read server-side secret (set via dashboard or \`lumio secrets\`) |
829
+ | \`ctx.auth\` | \`{ userId, accountId, target }\` \u2014 caller identity from extension token |
830
+
831
+ ### Sandbox Limits
832
+
833
+ | Limit | Value |
834
+ |-------|-------|
835
+ | Memory | 256 MB per invocation |
836
+ | CPU timeout | 10 seconds |
837
+ | Filesystem | No access |
838
+ | Network | Only \`ctx.fetch()\` via egress allowlist (Supervisor-enforced) |
839
+ | Dynamic code | Blocked (\`eval\`, \`Function\`, \`import()\`) |
840
+ | Storage APIs | localStorage/sessionStorage blocked (separate origin) |
841
+
842
+ ### Rate Limits
843
+
844
+ | Function type | Limit |
845
+ |--------------|-------|
846
+ | Queries | 100/min per installation |
847
+ | Mutations | 50/min per installation |
848
+ | Actions (with fetch) | 20/min per installation |
849
+ | Storage writes | 200/min per installation |
850
+
851
+ ## Extension Actions
852
+
853
+ Extension Actions let extensions trigger Lumio platform capabilities on behalf of the account. They are called via \`useLumioAction()\` in the frontend and require the matching permission in \`lumio.config.json\`.
854
+
855
+ \`\`\`tsx
856
+ import { useLumioAction } from "@zaflun/lumio-sdk";
857
+
858
+ // Send a chat message on the connected platform
859
+ const { execute: sendChat, isLoading, error } = useLumioAction("chat:send", {});
860
+ sendChat({ message: "Hello from the extension!" });
861
+
862
+ // Switch OBS scene (requires OBS WebSocket integration)
863
+ const { execute: setScene } = useLumioAction("obs:set_scene", {});
864
+ setScene({ sceneName: "Main Scene" });
865
+
866
+ // Emit a custom event into the Lumio event bus
867
+ const { execute: emitEvent } = useLumioAction("events:emit", {});
868
+ emitEvent({ type: "extension:custom", payload: { score: 42 } });
869
+
870
+ // Update a specific layer in the overlay
871
+ const { execute: updateLayer } = useLumioAction("overlay:update_layer", {});
872
+ updateLayer({ layerId: "uuid", visible: true });
873
+ \`\`\`
874
+
875
+ **Available actions (21 total, \`action\` is \`LumioActionType\`):**
876
+
877
+ | Action | Permission required | Description |
878
+ |--------|--------------------|----|
879
+ | \`chat:send\` | \`chat:send\` | Send chat message on connected platform |
880
+ | \`chat:ban\` | \`chat:ban\` | Ban a user from chat |
881
+ | \`chat:unban\` | \`chat:ban\` | Unban a user from chat |
882
+ | \`chat:timeout\` | \`chat:ban\` | Timeout a user from chat |
883
+ | \`obs:set_scene\` | \`obs:control\` | Switch OBS scene |
884
+ | \`obs:start_recording\` | \`obs:control\` | Start OBS recording |
885
+ | \`obs:stop_recording\` | \`obs:control\` | Stop OBS recording |
886
+ | \`events:emit\` | \`events:write\` | Emit event into Lumio event bus |
887
+ | \`overlay:update_layer\` | \`overlays:edit\` | Update overlay layer properties |
888
+ | \`spotify:play\` | \`spotify:control\` | Resume Spotify playback |
889
+ | \`spotify:pause\` | \`spotify:control\` | Pause Spotify playback |
890
+ | \`spotify:skip\` | \`spotify:control\` | Skip to next track |
891
+ | \`spotify:seek\` | \`spotify:control\` | Seek to position in current track |
892
+ | \`spotify:volume\` | \`spotify:control\` | Set Spotify volume |
893
+ | \`spotify:shuffle\` | \`spotify:control\` | Toggle shuffle mode |
894
+ | \`spotify:repeat\` | \`spotify:control\` | Set repeat mode |
895
+ | \`spotify:queue_add\` | \`spotify:control\` | Add track to Spotify queue |
896
+
897
+ Actions are proxied through the Lumio API \u2014 the extension never accesses OAuth tokens or platform credentials directly.
898
+ `;
899
+
900
+ // src/content/cli-workflows.ts
901
+ var cliWorkflowsContent = `# CLI Workflows
902
+
903
+ All 12 commands provided by \`@zaflun/lumio-cli\` (\`npm install -g @zaflun/lumio-cli\`).
904
+
905
+ ## lumio login
906
+
907
+ Authenticate with Lumio via PKCE OAuth flow.
908
+
909
+ \`\`\`bash
910
+ # Browser-based login (opens browser for OAuth)
911
+ lumio login
912
+
913
+ # Token-based login (CI/CD)
914
+ lumio login --token lm_usr_xxxxxxxxxxxx
915
+
916
+ # Or via environment variable
917
+ export LUMIO_TOKEN=lm_usr_xxxxxxxxxxxx
918
+ \`\`\`
919
+
920
+ Credentials stored in \`~/.lumio/credentials\`.
921
+
922
+ ## lumio logout
923
+
924
+ Clear stored credentials from \`~/.lumio/credentials\`.
925
+
926
+ \`\`\`bash
927
+ lumio logout
928
+ # Output:
929
+ # Logged out. Credentials removed from ~/.lumio/credentials.
930
+ \`\`\`
931
+
932
+ ## lumio whoami
933
+
934
+ Show current authenticated user and account.
935
+
936
+ \`\`\`bash
937
+ lumio whoami
938
+ # Output:
939
+ # User: raphael (raphael@example.com)
940
+ # Account: My Stream (plan: pro)
941
+ # API: https://api.lumio.vision
942
+ \`\`\`
943
+
944
+ ## lumio init
945
+
946
+ Scaffold a new extension project. Registers the extension ID on Lumio (requires auth). If you belong to developer teams, you will be prompted to select which team (or personal account) owns the extension.
947
+
948
+ \`\`\`bash
949
+ lumio init my-widget
950
+
951
+ # Interactive prompts:
952
+ # ? Extension name: My Widget
953
+ # ? Description: A cool widget
954
+ # ? Category: widget
955
+ # ? Targets: editor, layer
956
+ # ? Server functions: yes
957
+ # ? Template: Widget (editor + layer + storage)
958
+ # ? Owner: My Stream (personal) / Awesome Team <- team selection if applicable
959
+ #
960
+ # Creating extension on Lumio...
961
+ # Extension-ID: 88b2478a-39c7-468e-bebd-14e52fd97ac0
962
+ # Slug: k8f2x-my-widget
963
+ #
964
+ # Writing lumio.config.json...
965
+ # Writing src/editor.tsx...
966
+ # Writing src/layer.tsx...
967
+ # Writing server/schema.ts...
968
+ # Writing server/functions.ts...
969
+ #
970
+ # Done! Run \`lumio dev\` to start developing.
971
+ \`\`\`
972
+
973
+ ### Templates
974
+
975
+ | Template | Targets | Server | Description |
976
+ |----------|---------|--------|-------------|
977
+ | Blank | editor + layer | No | Minimal starting point |
978
+ | Widget | editor + layer | Yes | Widget with storage |
979
+ | Full-Stack | editor + layer | Yes | Complete extension with server functions |
980
+ | Interactive Game | editor + layer + interactive | Yes | Viewer interaction game |
981
+ | Bot Module | server-only | Yes | Chat command module |
982
+ | Integration | server | Yes | External API integration |
983
+ | Theme | editor | No | Style variables only |
984
+
985
+ ## lumio dev
986
+
987
+ Start local development server with hot reload and Lumio sandbox.
988
+
989
+ \`\`\`bash
990
+ lumio dev
991
+
992
+ # Output:
993
+ # Lumio Dev Server running at http://localhost:5173
994
+ # Editor: http://localhost:5173/editor
995
+ # Layer: http://localhost:5173/layer
996
+ # Interactive: http://localhost:5173/interactive
997
+ #
998
+ # Mock events available. Press 'e' to emit a test event.
999
+ # Press 'q' to quit.
1000
+ \`\`\`
1001
+
1002
+ Features:
1003
+ - Hot reload on file changes
1004
+ - Mock event system (simulated chat, follows, subs)
1005
+ - Sandbox environment matching production constraints
1006
+ - Server function execution with local DB
1007
+
1008
+ ## lumio build
1009
+
1010
+ Bundle the extension as standalone HTML and create source archive.
1011
+
1012
+ \`\`\`bash
1013
+ lumio build
1014
+
1015
+ # Output:
1016
+ # Building extension...
1017
+ # Bundling editor.tsx -> dist/editor.html (42 KB)
1018
+ # Bundling layer.tsx -> dist/layer.html (38 KB)
1019
+ # Creating source archive -> dist/source.tar.gz
1020
+ # Build complete!
1021
+ \`\`\`
1022
+
1023
+ Warns if \`CHANGELOG.md\` is missing.
1024
+
1025
+ ## lumio deploy
1026
+
1027
+ Upload the built extension to Lumio. Creates a draft version.
1028
+
1029
+ \`\`\`bash
1030
+ # Basic deploy
1031
+ lumio deploy
1032
+
1033
+ # With version override
1034
+ lumio deploy -v 1.2.0
1035
+
1036
+ # With release notes
1037
+ lumio deploy -d "Added NHL support"
1038
+
1039
+ # Mark as breaking change
1040
+ lumio deploy --breaking -v 2.0.0
1041
+
1042
+ # Explicit draft (default behavior)
1043
+ lumio deploy --draft
1044
+ \`\`\`
1045
+
1046
+ After deploy:
1047
+ 1. Version status = \`draft\`
1048
+ 2. Submit for review via dashboard (\`/dashboard/extensions/{slug}/submit\`)
1049
+ 3. Admin reviews code
1050
+ 4. Testing phase (invite testers)
1051
+ 5. Request publish
1052
+ 6. Admin final approval
1053
+ 7. Published to store
1054
+
1055
+ ### CI/CD Example
1056
+
1057
+ \`\`\`yaml
1058
+ # .github/workflows/deploy.yml
1059
+ name: Deploy Extension
1060
+ on:
1061
+ push:
1062
+ tags: ["v*"]
1063
+
1064
+ jobs:
1065
+ deploy:
1066
+ runs-on: ubuntu-latest
1067
+ steps:
1068
+ - uses: actions/checkout@v4
1069
+ - uses: actions/setup-node@v4
1070
+ - run: npm install -g @zaflun/lumio-cli
1071
+ - run: lumio login --token \${{ secrets.LUMIO_TOKEN }}
1072
+ - run: pnpm install
1073
+ - run: lumio build
1074
+ - run: lumio deploy -v \${{ github.ref_name }} -d "Automated release"
1075
+ \`\`\`
1076
+
1077
+ ## lumio status
1078
+
1079
+ Check the review status of the latest deployed version.
1080
+
1081
+ \`\`\`bash
1082
+ lumio status
1083
+
1084
+ # Output:
1085
+ # Extension: Sports Scoreboard (sports-scoreboard)
1086
+ # Latest version: 1.2.0
1087
+ # Status: testing
1088
+ # Submitted: May 17, 2026
1089
+ # Approved: May 18, 2026
1090
+ # Testers: 3 invited, 2 active
1091
+ \`\`\`
1092
+
1093
+ ## lumio logs
1094
+
1095
+ Stream server function execution logs in real-time.
1096
+
1097
+ \`\`\`bash
1098
+ lumio logs
1099
+
1100
+ # Output:
1101
+ # [2026-05-17 14:32:01] QUERY getItems -> 12 rows (3ms)
1102
+ # [2026-05-17 14:32:05] MUTATION addItem -> { id: "abc123" } (5ms)
1103
+ # [2026-05-17 14:32:10] ACTION fetchScores -> 200 OK (142ms)
1104
+ # [2026-05-17 14:32:15] ERROR fetchScores -> EGRESS_BLOCKED: host not in allowlist
1105
+
1106
+ # Filter by function name
1107
+ lumio logs --filter fetchScores
1108
+
1109
+ # Filter by level
1110
+ lumio logs --level error
1111
+ \`\`\`
1112
+
1113
+ ## lumio secrets
1114
+
1115
+ Manage server-side secrets used by handler-based server functions via \`ctx.secrets.get("KEY")\`.
1116
+
1117
+ \`\`\`bash
1118
+ # Set a secret
1119
+ lumio secrets set ESPN_API_KEY abc123xyz
1120
+
1121
+ # List secret keys (values are never shown)
1122
+ lumio secrets list
1123
+ # Output:
1124
+ # ESPN_API_KEY (set 2026-05-19)
1125
+ # WEATHER_TOKEN (set 2026-04-01)
1126
+
1127
+ # Delete a secret
1128
+ lumio secrets delete ESPN_API_KEY
1129
+ \`\`\`
1130
+
1131
+ Secrets are stored encrypted on the Lumio platform and injected into the V8 isolate at runtime. They are never exposed to the frontend or to other extensions.
1132
+
1133
+ ## lumio team
1134
+
1135
+ Manage developer team members. Teams share publishing rights and revenue.
1136
+
1137
+ \`\`\`bash
1138
+ # List team members
1139
+ lumio team list
1140
+
1141
+ # Invite a team member
1142
+ lumio team invite alice@example.com --role editor
1143
+
1144
+ # Remove a team member
1145
+ lumio team remove alice@example.com
1146
+
1147
+ # Show current team
1148
+ lumio team whoami
1149
+ \`\`\`
1150
+
1151
+ ## lumio config
1152
+
1153
+ Show or set CLI configuration.
1154
+
1155
+ \`\`\`bash
1156
+ # Show current config
1157
+ lumio config
1158
+
1159
+ # Set API endpoint (useful for self-hosted or staging)
1160
+ lumio config set api https://api.lumio.vision
1161
+
1162
+ # Set default account when you have multiple
1163
+ lumio config set account my-account-slug
1164
+ \`\`\`
1165
+ `;
1166
+
1167
+ // src/content/auth.ts
1168
+ var authContent = `# Auth Model
1169
+
1170
+ ## All 9 Token Types
1171
+
1172
+ The Lumio auth system has 9 token types:
1173
+
1174
+ | # | Prefix | Name | TTL | Purpose |
1175
+ |---|--------|------|-----|---------|
1176
+ | 1 | \`lm_sys_\` | System Key | Permanent | Internal service-to-service auth |
1177
+ | 2 | \`lm_usr_\` | User API Key | Permanent | External API access for users |
1178
+ | 3 | _(JWT)_ | JWT | Session | OAuth login session |
1179
+ | 4 | \`lm_pop_\` | Popout Token | Non-expiring | Overlay access for non-session viewers |
1180
+ | 5 | \`lm_overlay_\` | Overlay Token | Permanent | Per-overlay OBS browser source access |
1181
+ | 6 | \`lm_share_\` | Shared Overlay Token | Time-limited | Temporary shared overlay links |
1182
+ | 7 | \`lm_ext_\` | Extension Token | 15 min | Scoped to one extension installation |
1183
+ | 8 | \`lm_widget_\` | Widget Token | Permanent | Widget Gallery standalone embeds |
1184
+ | 9 | _(none)_ | Anonymous | N/A | Public/unauthenticated access |
1185
+
1186
+ ## Extension Tokens (\`lm_ext_*\`)
1187
+
1188
+ Extensions authenticate via short-lived \`lm_ext_*\` tokens.
1189
+
1190
+ | Property | Value |
1191
+ |----------|-------|
1192
+ | Prefix | \`lm_ext_\` |
1193
+ | TTL | 15 min (auto-refreshed via postMessage) |
1194
+ | Scope | Single extension installation (install_id + extension_id) |
1195
+ | RBAC | None (\`has_permission()\` returns false) |
1196
+ | DB access | Only \`ext_{extension_id}.*\` tables |
1197
+ | Rate limit | Popout tier (600 req/min) |
1198
+
1199
+ ### Token Fields
1200
+
1201
+ \`\`\`
1202
+ AuthContext::ExtensionToken {
1203
+ token_id: Uuid,
1204
+ extension_id: Uuid,
1205
+ install_id: Uuid,
1206
+ account_id: Uuid,
1207
+ target: "editor" | "layer" | "interactive",
1208
+ }
1209
+ \`\`\`
1210
+
1211
+ ### Token Lifecycle
1212
+
1213
+ 1. User opens extension in dashboard/overlay \u2014 Lumio server generates \`lm_ext_*\` token
1214
+ 2. Token passed to Extension Runtime iframe: \`ext.lumio.vision/ext/{slug}/layer?token=lm_ext_xxx&install_id=uuid\` where \`slug\` includes the \`short_id\` prefix (e.g. \`k8f2x-my-extension\`); the store URL is \`/store/k8f2x-my-extension\`
1215
+ 3. SDK uses token for all API calls (server functions, storage, events)
1216
+ 4. Token auto-refreshes via postMessage every 12 minutes (before 15-min expiry) \u2014 the Supervisor mediates the refresh
1217
+ 5. On page reload, a new token is generated
1218
+
1219
+ ### What Extensions Cannot Access
1220
+
1221
+ - No Lumio core tables (users, accounts, overlays, channel_connections)
1222
+ - No OAuth tokens, API keys, client secrets
1223
+ - No other extensions' data
1224
+ - No other accounts' data
1225
+ - No direct Redis access
1226
+ - No platform API calls (only via \`useLumioAction()\`)
1227
+ - No localStorage or sessionStorage (blocked at Extension Runtime origin)
1228
+
1229
+ ## Widget Tokens (\`lm_widget_*\`)
1230
+
1231
+ Widget Gallery standalone embeds use \`lm_widget_*\` tokens.
1232
+
1233
+ | Property | Value |
1234
+ |----------|-------|
1235
+ | Prefix | \`lm_widget_\` |
1236
+ | TTL | Permanent (no expiry) |
1237
+ | Scope | Single widget_id |
1238
+ | RBAC | None |
1239
+ | Use | Widget Gallery embeds, standalone widget pages |
1240
+
1241
+ Widget tokens are suitable for persistent, public-facing embeds where session refresh is not practical.
1242
+
1243
+ ## \`editorOnly\` Flag
1244
+
1245
+ Server functions marked with \`editorOnly: true\` can only be called from the \`editor\` surface.
1246
+
1247
+ \`\`\`typescript
1248
+ // Only callable from editor.tsx \u2014 layer.tsx and interactive.tsx get 403
1249
+ export const addItem = insertRow("items", { text: arg("text") }, { editorOnly: true });
1250
+
1251
+ // Callable from any surface
1252
+ export const getItems = queryRows("items");
1253
+ \`\`\`
1254
+
1255
+ Use \`editorOnly\` for:
1256
+ - Write operations that should only happen in the dashboard
1257
+ - Admin/config mutations
1258
+ - Destructive operations (delete, reset)
1259
+
1260
+ ## Lumio Actions Auth
1261
+
1262
+ \`useLumioAction()\` calls are authorized by checking:
1263
+ 1. Extension has the permission declared in \`lumio.config.json -> permissions\`
1264
+ 2. Account has the required connection/integration configured
1265
+ 3. Extension token is valid and matches the installation
1266
+
1267
+ \`\`\`json
1268
+ {
1269
+ "permissions": ["chat:send", "obs:set_scene", "events:emit", "overlay:update_layer"]
1270
+ }
1271
+ \`\`\`
1272
+
1273
+ The extension never sees OAuth tokens or credentials \u2014 Lumio executes the action on behalf of the account.
1274
+
1275
+ ## Extension Access Patterns
1276
+
1277
+ - **Public** (\`"visibility": "public"\`): Listed in Widget Gallery, anyone can install.
1278
+ - **Unlisted** (\`"visibility": "unlisted"\`): Not in Widget Gallery, accessible by direct link.
1279
+ - **Private invite**: Developer grants \`free_access\` to specific accounts via the dashboard. The account can install the extension even if it is paid or restricted.
1280
+ `;
1281
+
1282
+ // src/content/runtime-scopes.ts
1283
+ var runtimeScopesContent = `# Runtime Scopes
1284
+
1285
+ ## Sandbox Architecture
1286
+
1287
+ Extensions run inside a 3-layer iframe sandbox. Understanding the layers is important for debugging messaging and storage issues:
1288
+
1289
+ \`\`\`
1290
+ Host (lumio.vision / OBS browser source)
1291
+ \u2514\u2500\u2500 Supervisor iframe (supervisor.extension.lumio.vision)
1292
+ Purpose: validates postMessage traffic, enforces egress allowlist,
1293
+ mediates token refresh, proxies LumioComponentNode[] to host
1294
+ \u2514\u2500\u2500 Extension Runtime iframe (ext.lumio.vision)
1295
+ Purpose: runs extension code on a separate origin
1296
+ Storage: localStorage/sessionStorage BLOCKED (cross-origin isolation)
1297
+ \u2514\u2500\u2500 Web Worker
1298
+ Purpose: V8 isolate for server function execution
1299
+ Memory: 256 MB CPU: 10s timeout
1300
+ \`\`\`
1301
+
1302
+ **Supervisor layer responsibilities:**
1303
+ - Validates all postMessage messages between Extension Runtime and Host
1304
+ - Enforces \`egress.allowHosts\` \u2014 rejects fetch requests to undeclared hosts before they reach the network
1305
+ - Mediates \`lm_ext_*\` token refresh (12 min interval, before 15 min expiry)
1306
+ - Forwards serialized \`LumioComponentNode[]\` to the Host renderer
1307
+
1308
+ **Extension Runtime (ext.lumio.vision) origin isolation:**
1309
+ - Runs on a separate origin from the Host \u2014 this prevents direct DOM access and localStorage sharing
1310
+ - \`localStorage\` and \`sessionStorage\` throw \`DOMException: Access is denied\` \u2014 use \`useExtensionStorage()\` instead
1311
+ - \`document.cookie\` is unavailable
1312
+ - Cross-origin postMessage to the Host goes through the Supervisor
1313
+
1314
+ ## Storage Scoping
1315
+
1316
+ Every \`defineTable()\` call specifies a storage scope that controls data isolation.
1317
+
1318
+ ## Scopes
1319
+
1320
+ ### \`instance\` (most isolated)
1321
+
1322
+ Data is scoped to a specific extension installation within a specific overlay.
1323
+
1324
+ \`\`\`typescript
1325
+ defineTable({...}).storage("instance")
1326
+ \`\`\`
1327
+
1328
+ - Filtered by: \`_account_id\` + \`_overlay_id\`
1329
+ - Use for: per-widget data (scoreboard scores, timer state, per-overlay config)
1330
+ - Example: A "Sports Scoreboard" widget has different scores in each overlay it appears in
1331
+
1332
+ ### \`overlay\`
1333
+
1334
+ Data is scoped to a specific overlay (shared across all instances of this extension in that overlay).
1335
+
1336
+ \`\`\`typescript
1337
+ defineTable({...}).storage("overlay")
1338
+ \`\`\`
1339
+
1340
+ - Filtered by: \`_account_id\` + \`_overlay_id\`
1341
+ - Use for: overlay-level shared state (theme overrides, layout preferences)
1342
+ - Example: All widgets in one overlay share the same color scheme
1343
+
1344
+ ### \`account\`
1345
+
1346
+ Data is scoped to the account (shared across all overlays).
1347
+
1348
+ \`\`\`typescript
1349
+ defineTable({...}).storage("account")
1350
+ \`\`\`
1351
+
1352
+ - Filtered by: \`_account_id\`
1353
+ - Use for: account-wide settings, user preferences, persistent state
1354
+ - Example: A streamer's global extension settings, saved templates
1355
+
1356
+ ### \`global\`
1357
+
1358
+ Data is shared across all accounts. Use sparingly \u2014 typically only for extension-level configuration.
1359
+
1360
+ \`\`\`typescript
1361
+ defineTable({...}).storage("global")
1362
+ \`\`\`
1363
+
1364
+ - Filtered by: nothing (all accounts see the same data)
1365
+ - Use for: extension-wide leaderboards, shared content
1366
+ - Example: A global high-score leaderboard across all streamers
1367
+ - **Warning:** Write access to global tables requires careful rate limiting
1368
+
1369
+ ## Choosing a Scope
1370
+
1371
+ | Scope | Data shared across | Best for |
1372
+ |-------|-------------------|----------|
1373
+ | \`instance\` | Nothing (fully isolated) | Per-widget state, game sessions |
1374
+ | \`overlay\` | Instances in same overlay | Overlay-level config |
1375
+ | \`account\` | All overlays in account | User preferences, persistent data |
1376
+ | \`global\` | All accounts | Leaderboards, shared content |
1377
+
1378
+ **Default recommendation:** Start with \`instance\` and widen only when needed.
1379
+
1380
+ ## DB Schema Mapping
1381
+
1382
+ Each scope maps to automatic WHERE clauses:
1383
+
1384
+ \`\`\`sql
1385
+ -- instance scope:
1386
+ SELECT * FROM ext_{id}.items WHERE _account_id = $1 AND _overlay_id = $2
1387
+
1388
+ -- overlay scope:
1389
+ SELECT * FROM ext_{id}.items WHERE _account_id = $1 AND _overlay_id = $2
1390
+
1391
+ -- account scope:
1392
+ SELECT * FROM ext_{id}.items WHERE _account_id = $1
1393
+
1394
+ -- global scope:
1395
+ SELECT * FROM ext_{id}.items
1396
+ \`\`\`
1397
+
1398
+ The scoping is enforced server-side \u2014 extensions cannot bypass it.
1399
+ `;
1400
+
1401
+ // src/content/best-practices.ts
1402
+ var bestPracticesContent = `# Best Practices
1403
+
1404
+ ## Performance
1405
+
1406
+ ### Minimize renders
1407
+ - Use \`useVisionState()\` for individual values instead of \`useExtensionStorage()\` for everything
1408
+ - Avoid spreading entire storage on every keystroke \u2014 debounce text inputs
1409
+ - Use \`useMemo\` and \`useCallback\` to avoid unnecessary re-renders
1410
+
1411
+ ### Optimize server function calls
1412
+ - Prefer declarative functions (\`queryRows\`, \`insertRow\`) over handler-based \u2014 they skip the V8 isolate
1413
+ - Batch related data into single queries instead of multiple round-trips
1414
+ - Use \`useQuery\` with appropriate refetch intervals, not continuous polling
1415
+
1416
+ ### Keep bundles small
1417
+ - Import only what you need from \`@zaflun/lumio-sdk\` (tree-shakeable)
1418
+ - Avoid large libraries \u2014 the bundle runs in an iframe, every KB counts
1419
+ - Images should be loaded via URLs, not bundled as base64
1420
+
1421
+ ### Layer surface
1422
+ - Use CSS animations via \`VisionStyle\` instead of JS-driven animations
1423
+ - Keep the layer surface minimal \u2014 it runs in OBS at 30/60 fps
1424
+ - Return \`null\` from components when they should be invisible (do not use \`display: none\`)
1425
+
1426
+ ## UX Patterns
1427
+
1428
+ ### Editor-Layer sync
1429
+ Always use \`useExtensionStorage()\` for state that should be visible on both editor and layer. Changes propagate in real-time via WebSocket.
1430
+
1431
+ \`\`\`tsx
1432
+ // editor.tsx \u2014 user configures
1433
+ const [storage, setStorage] = useExtensionStorage();
1434
+ <TextField label="Title" value={storage.title ?? ""}
1435
+ onChange={(v) => setStorage({ ...storage, title: v })} />
1436
+
1437
+ // layer.tsx \u2014 displays result
1438
+ const [storage] = useExtensionStorage();
1439
+ <Text content={storage.title ?? "Default Title"} variant="heading" />
1440
+ \`\`\`
1441
+
1442
+ ### Graceful defaults
1443
+ Always provide sensible defaults using \`??\` (nullish coalescing). Storage might be empty on first load.
1444
+
1445
+ \`\`\`tsx
1446
+ const visible = storage.visible ?? true;
1447
+ const maxItems = storage.maxItems ?? 10;
1448
+ const theme = storage.theme ?? "dark";
1449
+ \`\`\`
1450
+
1451
+ ### Loading states
1452
+ Use the \`isLoading\` flag from \`useQuery\` and \`useMutation\`:
1453
+
1454
+ \`\`\`tsx
1455
+ const { data, isLoading, error } = useQuery("getItems");
1456
+
1457
+ if (isLoading) return <Text content="Loading..." variant="muted" />;
1458
+ if (error) return <Text content="Error loading data" variant="muted" />;
1459
+ return <List items={data} renderItem={(item) => <Text content={item.text} />} />;
1460
+ \`\`\`
1461
+
1462
+ ### CompactView for editor panels
1463
+ Wrap editor settings in \`CompactView\` for consistent styling with the Lumio dashboard:
1464
+
1465
+ \`\`\`tsx
1466
+ <CompactView title="Display Settings" collapsible>
1467
+ <Toggle label="Show title" checked={showTitle} onChange={setShowTitle} />
1468
+ <Dropdown label="Theme" value={theme} onChange={setTheme}
1469
+ options={[{ label: "Dark", value: "dark" }, { label: "Light", value: "light" }]} />
1470
+ </CompactView>
1471
+ \`\`\`
1472
+
1473
+ ### Transparent backgrounds on layer
1474
+ The layer surface should have a transparent background so it composites correctly over the stream in OBS:
1475
+
1476
+ \`\`\`tsx
1477
+ // layer.tsx \u2014 no background, content floats over stream
1478
+ Lumio.render(<Overlay />, { target: "layer" });
1479
+ // The host iframe already has background: transparent
1480
+ \`\`\`
1481
+
1482
+ ## Config Schema
1483
+
1484
+ Use \`config_schema\` in \`lumio.config.json\` for user-customizable settings. The Lumio dashboard renders these as a form in the Widget Gallery install flow \u2014 the user fills them in without needing to open the editor surface.
1485
+
1486
+ \`\`\`json
1487
+ {
1488
+ "config_schema": {
1489
+ "fields": [
1490
+ { "key": "title", "type": "string", "label": "Widget Title", "default": "My Widget" },
1491
+ { "key": "maxItems", "type": "number", "label": "Max Items", "default": 10 },
1492
+ { "key": "showBorder", "type": "boolean", "label": "Show Border", "default": true },
1493
+ {
1494
+ "key": "theme",
1495
+ "type": "select",
1496
+ "label": "Color Theme",
1497
+ "options": [
1498
+ { "label": "Dark", "value": "dark" },
1499
+ { "label": "Light", "value": "light" }
1500
+ ],
1501
+ "default": "dark"
1502
+ }
1503
+ ]
1504
+ }
1505
+ }
1506
+ \`\`\`
1507
+
1508
+ Read config values via \`useLumioConfig()\`:
1509
+
1510
+ \`\`\`tsx
1511
+ const [config] = useLumioConfig();
1512
+ const title = config.title ?? "My Widget";
1513
+ const maxItems = config.maxItems ?? 10;
1514
+ \`\`\`
1515
+
1516
+ **When to use config_schema vs storage:**
1517
+ - \`config_schema\` \u2014 install-time settings (set once, rarely changed)
1518
+ - \`useExtensionStorage()\` \u2014 runtime state (changes during stream)
1519
+
1520
+ ## Animation Best Practices
1521
+
1522
+ - Always use SDK presets via \`useAnimation()\` + \`<Animated>\` for entrance/exit effects \u2014 never write raw CSS keyframe \`@keyframes\` rules
1523
+ - The reconciler maps animation preset names to allowed \`VisionStyle\` \`animation\` properties; raw keyframe strings are stripped by the Supervisor
1524
+ - Keep durations short on OBS layer (< 400ms). Longer animations cause visible frame drops in OBS at 30/60 fps
1525
+ - Prefer animating \`opacity\` and \`transform\` (GPU-composited) over \`width\`, \`height\`, \`margin\`, \`padding\` (layout-triggering)
1526
+
1527
+ \`\`\`tsx
1528
+ // Good: SDK preset, short duration, transform-based
1529
+ const anim = useAnimation("slide-in", { duration: 300 });
1530
+ <Animated {...anim}><Text content="New follower!" /></Animated>
1531
+
1532
+ // Bad: raw CSS animation string \u2014 stripped by Supervisor
1533
+ <Box style={{ animation: "mySlide 1s ease-in-out" }}>...</Box>
1534
+ \`\`\`
1535
+
1536
+ ## Extension Access Patterns
1537
+
1538
+ Choose the right visibility setting for your extension:
1539
+
1540
+ | Pattern | Config | Use when |
1541
+ |---------|--------|----------|
1542
+ | **Public** | \`"visibility": "public"\` | Extension is listed in Widget Gallery, anyone can install |
1543
+ | **Unlisted** | \`"visibility": "unlisted"\` | Share via direct link only, not discoverable in store |
1544
+ | **Private invite** | Any visibility + grant \`free_access\` via dashboard | Give specific accounts access to a paid or restricted extension |
1545
+
1546
+ For beta testing before public launch:
1547
+ 1. Deploy with \`"visibility": "unlisted"\`
1548
+ 2. Grant \`free_access\` to invited testers via dashboard
1549
+ 3. Switch to \`"visibility": "public"\` when ready for public release
1550
+
1551
+ ## Common Mistakes
1552
+
1553
+ | Mistake | Fix |
1554
+ |---------|-----|
1555
+ | Direct DOM manipulation | Use SDK components only \u2014 reconciler serializes to \`LumioComponentNode[]\` |
1556
+ | \`setStorage(newObj)\` without spread | \`setStorage({ ...storage, key: value })\` |
1557
+ | Polling with setInterval | Use \`useLumioEvent()\` for live data |
1558
+ | Large inline images | Use URL-based images via \`Image\` component |
1559
+ | Missing \`Lumio.render()\` | Must be called within 10s of page load |
1560
+ | Hardcoded strings | Use \`config_schema\` fields for user-customizable text |
1561
+ | Using localStorage directly | Use \`useExtensionStorage()\` (localStorage blocked at ext.lumio.vision) |
1562
+ | Raw CSS \`@keyframes\` animations | Use \`useAnimation()\` + \`<Animated>\` SDK presets |
1563
+ `;
1564
+
1565
+ // src/content/troubleshooting.ts
1566
+ var troubleshootingContent = `# Troubleshooting
1567
+
1568
+ ## Error Reference
1569
+
1570
+ | Error Code | Message | Cause | Fix |
1571
+ |------------|---------|-------|-----|
1572
+ | \`RENDER_TIMEOUT\` | Extension did not render within 10 seconds | \`Lumio.render()\` not called or called too late | Move \`Lumio.render()\` to module top level, outside async code |
1573
+ | \`EXTENSION_NOT_FOUND\` | Extension ID not found | Invalid \`extensionId\` in \`lumio.config.json\` | Run \`lumio init\` to register, or create via dashboard |
1574
+ | \`INSTALL_NOT_FOUND\` | Installation not found | Extension not installed on this account | Install via store or check \`install_id\` parameter |
1575
+ | \`EGRESS_BLOCKED\` | Host not in allowlist | \`ctx.fetch()\` called with undeclared host | Add host to \`egress.allowHosts\` in \`lumio.config.json\` |
1576
+ | \`EGRESS_PRIVATE_IP\` | Private IP range blocked | Fetch targeting localhost or private network | Use public HTTPS endpoints only |
1577
+ | \`STORAGE_QUOTA\` | Storage quota exceeded | Extension data exceeds allocated storage | Reduce stored data or request limit increase |
1578
+ | \`EDITOR_ONLY\` | Function restricted to editor | Mutation with \`editorOnly: true\` called from layer/interactive | Remove \`editorOnly\` flag or call only from editor |
1579
+ | \`MEMORY_LIMIT\` | V8 isolate memory exceeded (128 MB) | Handler-based function using too much memory | Reduce data processing, stream large datasets |
1580
+ | \`CPU_TIMEOUT\` | V8 isolate CPU timeout (5s) | Handler-based function running too long | Optimize logic, break into smaller operations |
1581
+ | \`RATE_LIMITED\` | Too many requests (429) | Exceeded per-installation rate limit | Reduce call frequency, batch operations |
1582
+ | \`TOKEN_EXPIRED\` | Extension token expired | \`lm_ext_*\` token past 15-min TTL | SDK auto-refreshes; if manual, request new token |
1583
+ | \`SCHEMA_VALIDATION\` | Invalid function arguments | Args do not match \`v.string()\`/\`v.number()\` validators | Check argument types match schema definition |
1584
+ | \`TABLE_NOT_FOUND\` | Table does not exist | Referenced table not defined in \`server/schema.ts\` | Add table to \`defineSchema()\` and redeploy |
1585
+ | \`PERMISSION_DENIED\` | Missing declared permission | Action requires undeclared permission | Add permission to \`lumio.config.json -> permissions\` |
1586
+ | \`SECRET_NOT_FOUND\` | Secret key not set | \`ctx.secrets.get("KEY")\` with unset secret | Set secret via dashboard (\`/dashboard/extensions/{slug}/secrets\`) |
1587
+ | \`CHANGELOG_MISSING\` | No changelog entry for version | \`lumio deploy\` without matching CHANGELOG.md entry | Add entry for the version in CHANGELOG.md, or use \`-d\` flag |
1588
+ | \`BUNDLE_TOO_LARGE\` | Bundle exceeds size limit | Built bundle exceeds \`max_bundle_size_mb\` | Reduce bundle size or request limit increase |
1589
+ | \`SUPERVISOR_BLOCKED\` | Supervisor rejected postMessage | Message origin or schema invalid | Ensure you are in the correct iframe layer; do not post to Host directly |
1590
+ | \`WORKER_CREATION_FAILED\` | Web Worker could not start | Server functions bundle is invalid or too large | Check for syntax errors in server functions; run \`lumio build\` locally |
1591
+ | \`STORAGE_DOM_EXCEPTION\` | localStorage/sessionStorage access attempt | Extension Runtime (ext.lumio.vision) blocks storage APIs | Replace \`localStorage.setItem\` with \`useExtensionStorage()\` |
1592
+ | \`SLUG_FORMAT_INVALID\` | Slug does not match short-ID format | Manually edited slug in \`lumio.config.json\` | Use auto-generated slug (\`k8f2x-my-extension\` format) or run \`lumio init\` |
1593
+
1594
+ ## Common Issues
1595
+
1596
+ ### Extension not showing in overlay
1597
+ 1. Check the extension is installed (\`/dashboard/store/installed\`)
1598
+ 2. Verify the overlay has an extension layer with this extension
1599
+ 3. Check browser console for \`RENDER_TIMEOUT\` errors
1600
+ 4. Verify \`Lumio.render()\` is called with \`target: "layer"\`
1601
+
1602
+ ### Server functions returning empty data
1603
+ 1. Check storage scope \u2014 \`instance\` scope needs matching overlay context
1604
+ 2. Verify the table is defined in \`server/schema.ts\`
1605
+ 3. Check \`lumio logs\` for query errors
1606
+ 4. Ensure data was inserted with the correct scope
1607
+
1608
+ ### Hot reload not working in dev mode
1609
+ 1. Verify \`lumio dev\` is running
1610
+ 2. Check for TypeScript compilation errors in terminal
1611
+ 3. Ensure file is saved (autosave may be off)
1612
+ 4. Try stopping and restarting \`lumio dev\`
1613
+
1614
+ ### CORS errors in browser console
1615
+ Extensions run in sandboxed iframes \u2014 they should never make direct HTTP requests. Use:
1616
+ - \`useQuery()\` / \`useMutation()\` for server functions
1617
+ - \`useLumioAction()\` for platform actions
1618
+ - \`ctx.fetch()\` in handler-based server functions for external APIs
1619
+
1620
+ ### Token refresh failures
1621
+ The SDK auto-refreshes \`lm_ext_*\` tokens via postMessage. The Supervisor mediates refresh requests. If refreshes fail:
1622
+ 1. Check the parent window is the Lumio dashboard/overlay (not standalone)
1623
+ 2. Verify the extension is still installed
1624
+ 3. Check network connectivity to the Lumio API
1625
+
1626
+ ### Supervisor blocked messages (\`SUPERVISOR_BLOCKED\`)
1627
+ The Supervisor validates all postMessage traffic between Extension Runtime and Host. If you see this error:
1628
+ 1. Do not post messages directly from extension code to \`window.parent.parent\` (Host) \u2014 always use SDK hooks
1629
+ 2. Verify the extension iframe is loaded inside \`supervisor.extension.lumio.vision\`, not directly in the Host
1630
+ 3. Check that the message schema matches the \`@zaflun/lumio-protocol\` wire format (SDK handles this automatically)
1631
+
1632
+ ### Worker creation failures (\`WORKER_CREATION_FAILED\`)
1633
+ The V8 isolate Web Worker for server functions failed to start:
1634
+ 1. Run \`lumio build\` locally and check for TypeScript/syntax errors in \`server/functions.ts\`
1635
+ 2. Verify the server functions bundle is under the size limit
1636
+ 3. Check \`lumio logs\` for the underlying JS error from the Worker initializer
1637
+
1638
+ ### Storage DOMException errors (\`STORAGE_DOM_EXCEPTION\`)
1639
+ \`localStorage\` and \`sessionStorage\` are blocked on the Extension Runtime origin (\`ext.lumio.vision\`):
1640
+ 1. Replace all \`localStorage.setItem\` / \`localStorage.getItem\` calls with \`useExtensionStorage()\`
1641
+ 2. Replace all \`sessionStorage\` usage with \`useVisionState()\` for transient values
1642
+ 3. For server-side persistence, use \`ctx.db\` in server functions
1643
+
1644
+ ### Short-ID slug format issues (\`SLUG_FORMAT_INVALID\`)
1645
+ Extension slugs use a short-ID prefix format (\`k8f2x-my-extension\`):
1646
+ 1. Do not manually edit the \`slug\` field in \`lumio.config.json\`
1647
+ 2. The slug is auto-generated by \`lumio init\` and must not be changed
1648
+ 3. If the slug is wrong, create a new extension via \`lumio init\` (slug is immutable after registration)
1649
+
1650
+ ## Debug Tools
1651
+
1652
+ ### \`lumio dev\` console
1653
+ Press \`e\` to emit test events (chat message, follow, sub, etc.)
1654
+
1655
+ ### \`lumio logs\`
1656
+ Stream server function execution logs in real-time:
1657
+ \`\`\`bash
1658
+ lumio logs # All logs
1659
+ lumio logs --filter addItem # Filter by function name
1660
+ lumio logs --level error # Errors only
1661
+ \`\`\`
1662
+
1663
+ ### Browser DevTools
1664
+ Open the extension iframe's DevTools (right-click -> Inspect Frame) to see:
1665
+ - Console logs from extension code
1666
+ - Network requests (should be empty \u2014 all communication via postMessage)
1667
+ - \`postMessage\` events in the Console
1668
+ `;
1669
+
1670
+ // src/install.ts
1671
+ var SKILL_FILES = [
1672
+ { filename: "SKILL.md", content: skillContent },
1673
+ { filename: "components.md", content: componentsContent },
1674
+ { filename: "server-functions.md", content: serverFunctionsContent },
1675
+ { filename: "cli-workflows.md", content: cliWorkflowsContent },
1676
+ { filename: "auth.md", content: authContent },
1677
+ { filename: "runtime-scopes.md", content: runtimeScopesContent },
1678
+ { filename: "best-practices.md", content: bestPracticesContent },
1679
+ { filename: "troubleshooting.md", content: troubleshootingContent }
1680
+ ];
1681
+ var geminiContent = `## Lumio Extension SDK
1682
+
1683
+ This project is a Lumio extension. Read \`.lumio/skills/SKILL.md\` for the full SDK reference.
1684
+
1685
+ Key files:
1686
+ - \`.lumio/skills/components.md\` \u2014 UI components
1687
+ - \`.lumio/skills/server-functions.md\` \u2014 Server functions
1688
+ - \`.lumio/skills/cli-workflows.md\` \u2014 CLI commands
1689
+ - \`.lumio/skills/auth.md\` \u2014 Authentication
1690
+ `;
1691
+ var agentsContent = `## Lumio Extension SDK
1692
+
1693
+ This project is a Lumio extension built with \`@zaflun/lumio-sdk\`.
1694
+ Read \`.lumio/skills/SKILL.md\` for the complete SDK reference including components, hooks, server functions, and CLI commands.
1695
+ `;
1696
+ var windsurfContent = `Read .lumio/skills/SKILL.md for Lumio Extension SDK conventions.
1697
+ This project uses @zaflun/lumio-sdk for components and hooks, and @zaflun/lumio-cli for development.
1698
+ `;
1699
+ var copilotContent = `See .lumio/skills/SKILL.md for Lumio Extension SDK conventions.
1700
+ `;
1701
+ var ROOT_FILES = [
1702
+ { filename: "GEMINI.md", content: geminiContent },
1703
+ { filename: "AGENTS.md", content: agentsContent },
1704
+ { filename: ".windsurfrules", content: windsurfContent },
1705
+ { filename: ".github/copilot-instructions.md", content: copilotContent }
1706
+ ];
1707
+ function installSkillFiles(targetDir) {
1708
+ const skillsDir = join(targetDir, ".lumio", "skills");
1709
+ if (!existsSync(skillsDir)) {
1710
+ mkdirSync(skillsDir, { recursive: true });
1711
+ }
1712
+ for (const file of SKILL_FILES) {
1713
+ writeFileSync(join(skillsDir, file.filename), file.content, "utf-8");
1714
+ }
1715
+ for (const file of ROOT_FILES) {
1716
+ const filePath = join(targetDir, file.filename);
1717
+ const fileDir = join(filePath, "..");
1718
+ if (!existsSync(fileDir)) {
1719
+ mkdirSync(fileDir, { recursive: true });
1720
+ }
1721
+ writeFileSync(filePath, file.content, "utf-8");
1722
+ }
1723
+ }
1724
+
1725
+ export {
1726
+ SKILL_FILES,
1727
+ ROOT_FILES,
1728
+ installSkillFiles
1729
+ };