@zaflun/lumio-agent-skill 0.4.0 → 0.5.3
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/dist/bin/lumio-skills.js
CHANGED
|
@@ -14,8 +14,8 @@ Lumio Extensions are sandboxed React apps that run inside a 3-layer iframe sandb
|
|
|
14
14
|
\`\`\`
|
|
15
15
|
3-Layer Sandbox:
|
|
16
16
|
Host (lumio.vision / browser source)
|
|
17
|
-
\u2514\u2500\u2500 Supervisor iframe (supervisor.
|
|
18
|
-
\u2514\u2500\u2500 Extension Runtime iframe (ext.lumio.vision)
|
|
17
|
+
\u2514\u2500\u2500 Supervisor iframe (supervisor.ext.lumio.vision)
|
|
18
|
+
\u2514\u2500\u2500 Extension Runtime iframe (<label>.runtime.ext.lumio.vision)
|
|
19
19
|
\u2514\u2500\u2500 Web Worker (server function execution)
|
|
20
20
|
\`\`\`
|
|
21
21
|
|
|
@@ -30,7 +30,7 @@ Extension Project:
|
|
|
30
30
|
\`\`\`
|
|
31
31
|
|
|
32
32
|
**Runtime flow:**
|
|
33
|
-
1. Extension Runtime iframe loads
|
|
33
|
+
1. Extension Runtime iframe loads on the extension's own origin \`<label>.runtime.ext.lumio.vision\` (\`<label>\` = extension UUID as 32 lowercase hex)
|
|
34
34
|
2. \`Lumio.render(<Component />, { target: "layer" })\` must be called within 10 seconds
|
|
35
35
|
3. The custom React reconciler serializes the tree to \`LumioComponentNode[]\` via postMessage to the Supervisor
|
|
36
36
|
4. The Supervisor validates and forwards the node tree to the Host for rendering
|
|
@@ -76,8 +76,13 @@ All three surfaces share storage via \`useExtensionStorage()\` \u2014 changes in
|
|
|
76
76
|
},
|
|
77
77
|
"visibility": "public",
|
|
78
78
|
"pricing": { "type": "free" },
|
|
79
|
+
"sounds": [
|
|
80
|
+
{ "file": "alert.mp3", "name": "Alert" },
|
|
81
|
+
{ "file": "chime.wav", "name": "Chime", "default_volume": 0.8 }
|
|
82
|
+
],
|
|
79
83
|
"config_schema": [
|
|
80
|
-
{ "key": "title", "type": "string", "label": "Widget Title", "default_value": "My Widget", "required": true },
|
|
84
|
+
{ "key": "title", "type": "string", "label": "Widget Title", "default_value": "My Widget", "required": true, "placeholder": "Enter title..." },
|
|
85
|
+
{ "key": "fontSize", "type": "number", "label": "Font Size", "default_value": 16, "required": false, "min": 8, "max": 200, "unit": "px" },
|
|
81
86
|
{ "key": "showBorder", "type": "boolean", "label": "Show Border", "default_value": true, "required": false }
|
|
82
87
|
]
|
|
83
88
|
}
|
|
@@ -94,6 +99,8 @@ All three surfaces share storage via \`useExtensionStorage()\` \u2014 changes in
|
|
|
94
99
|
- \`"visibility": "unlisted"\` \u2014 accessible by direct link only
|
|
95
100
|
- Private invite: grant \`free_access\` to specific accounts via dashboard
|
|
96
101
|
|
|
102
|
+
**Bundled sounds:** Place audio files in \`dist/sounds/\` and declare them in the \`sounds\` array. Supported: MP3, WAV, OGG, WebM, M4A, AAC, FLAC. Default limits: 50 sounds per extension, 10 MB per file, 200 MB total storage (all admin-configurable). Developers can view limits at \`/account/developer/limits\` and request increases via a 4-step wizard. Requests are reviewed by admins in the admin app. Users cannot delete bundled sounds \u2014 uninstalling the extension removes them.
|
|
103
|
+
|
|
97
104
|
**Revenue split:** Configurable per developer (default 70/30 developer/Lumio).
|
|
98
105
|
|
|
99
106
|
**Feature gates:** The following Lumio plan features must be enabled for extension functionality:
|
|
@@ -136,11 +143,11 @@ lumio deploy
|
|
|
136
143
|
|
|
137
144
|
## Components
|
|
138
145
|
|
|
139
|
-
|
|
146
|
+
17 SDK components (all accept \`style: VisionStyle\`):
|
|
140
147
|
|
|
141
148
|
**Layout:** \`Box\`, \`VStack\`, \`HStack\`, \`CompactView\`, \`List\`
|
|
142
|
-
**Display:** \`Text\` (variant: heading/muted/default), \`Image\` (objectFit), \`Animated\`
|
|
143
|
-
**Input:** \`Button\` (variant: default/destructive), \`Toggle\`, \`TextField\`, \`TextArea\`, \`NumberField\`, \`Dropdown\`, \`Slider\`
|
|
149
|
+
**Display:** \`Text\` (variant: heading/muted/default), \`Image\` (objectFit), \`Animated\`, \`IconDisplay\`
|
|
150
|
+
**Input:** \`Button\` (variant: default/destructive), \`Toggle\`, \`TextField\`, \`TextArea\`, \`NumberField\`, \`Dropdown\`, \`Slider\`, \`IconPicker\`
|
|
144
151
|
|
|
145
152
|
**VisionStyle** is a typed CSS subset (allowed: flexbox, grid, \`position: relative\` / \`absolute\`, sizing, spacing, colors, border, typography, transitions; blocked: \`fixed\`, \`sticky\`, CSS expressions).
|
|
146
153
|
|
|
@@ -150,7 +157,7 @@ See \`components.md\` for full props and examples.
|
|
|
150
157
|
|
|
151
158
|
## Hooks
|
|
152
159
|
|
|
153
|
-
|
|
160
|
+
12 SDK hooks:
|
|
154
161
|
|
|
155
162
|
| Hook | Returns | Purpose |
|
|
156
163
|
|------|---------|---------|
|
|
@@ -165,6 +172,7 @@ See \`components.md\` for full props and examples.
|
|
|
165
172
|
| \`useQuery(fn, params?)\` | \`{ data, isLoading, error, refetch }\` | Call server query |
|
|
166
173
|
| \`useMutation(fn)\` | \`{ mutate, mutateAsync, data, isLoading, error }\` | Call server mutation/action |
|
|
167
174
|
| \`useAnimation(preset, options?)\` | \`animationProps\` | SDK animation presets for \`Animated\` component |
|
|
175
|
+
| \`useIconPicker(options?)\` | \`{ isOpen, selected, open, close, select, clear, sources }\` | Headless state for custom icon picker UIs |
|
|
168
176
|
|
|
169
177
|
**Important:** \`setStorage\` must always spread: \`setStorage({ ...storage, key: value })\`
|
|
170
178
|
|
|
@@ -194,26 +202,17 @@ export const fetchScores = action({
|
|
|
194
202
|
});
|
|
195
203
|
\`\`\`
|
|
196
204
|
|
|
197
|
-
**Extension Actions** via \`useLumioAction()\` (
|
|
205
|
+
**Extension Actions** via \`useLumioAction()\` (dispatched by the Lumio API; \`action\` is \`LumioActionType\`):
|
|
198
206
|
\`\`\`tsx
|
|
199
207
|
// Trigger platform actions (requires matching permission in lumio.config.json)
|
|
200
208
|
const { execute: sendChat } = useLumioAction("chat:send");
|
|
201
|
-
const { execute:
|
|
202
|
-
const { execute: unbanUser } = useLumioAction("chat:unban");
|
|
203
|
-
const { execute: timeoutUser } = useLumioAction("chat:timeout");
|
|
209
|
+
const { execute: deleteChat } = useLumioAction("chat:delete");
|
|
204
210
|
const { execute: setScene } = useLumioAction("obs:set_scene");
|
|
205
|
-
const { execute:
|
|
206
|
-
const { execute:
|
|
211
|
+
const { execute: setSourceVisible } = useLumioAction("obs:set_source_visible");
|
|
212
|
+
const { execute: startStream } = useLumioAction("obs:start_stream");
|
|
213
|
+
const { execute: stopStream } = useLumioAction("obs:stop_stream");
|
|
207
214
|
const { execute: emitEvent } = useLumioAction("events:emit");
|
|
208
215
|
const { execute: updateLayer } = useLumioAction("overlay:update_layer");
|
|
209
|
-
const { execute: spotifyPlay } = useLumioAction("spotify:play");
|
|
210
|
-
const { execute: spotifyPause } = useLumioAction("spotify:pause");
|
|
211
|
-
const { execute: spotifySkip } = useLumioAction("spotify:skip");
|
|
212
|
-
const { execute: spotifySeek } = useLumioAction("spotify:seek");
|
|
213
|
-
const { execute: spotifyVolume } = useLumioAction("spotify:volume");
|
|
214
|
-
const { execute: spotifyShuffle } = useLumioAction("spotify:shuffle");
|
|
215
|
-
const { execute: spotifyRepeat } = useLumioAction("spotify:repeat");
|
|
216
|
-
const { execute: spotifyQueueAdd } = useLumioAction("spotify:queue_add");
|
|
217
216
|
\`\`\`
|
|
218
217
|
|
|
219
218
|
See \`server-functions.md\` for schema definition, storage scoping, and advanced patterns.
|
|
@@ -373,22 +372,26 @@ return <Text content={storage.text} />;
|
|
|
373
372
|
|
|
374
373
|
### Live events
|
|
375
374
|
\`\`\`tsx
|
|
376
|
-
// eventType is LumioEventType \u2014 autocomplete shows all
|
|
375
|
+
// eventType is LumioEventType \u2014 autocomplete shows all 58 valid event strings
|
|
377
376
|
const event = useLumioEvent("twitch:follower");
|
|
378
377
|
// event updates automatically when a new follower event arrives
|
|
379
378
|
\`\`\`
|
|
380
379
|
|
|
381
|
-
**All
|
|
380
|
+
**All 58 event types** (\`LumioEventType\`):
|
|
381
|
+
|
|
382
|
+
Twitch (23): \`twitch:follower\`, \`twitch:subscribe\`, \`twitch:gift_sub\`, \`twitch:resub\`, \`twitch:cheer\`, \`twitch:raid\`, \`twitch:reward\`, \`twitch:hype_train\`, \`twitch:hype_train_end\`, \`twitch:poll\`, \`twitch:poll_end\`, \`twitch:prediction\`, \`twitch:prediction_lock\`, \`twitch:prediction_end\`, \`twitch:goal\`, \`twitch:goal_end\`, \`twitch:ad_break\`, \`twitch:ban\`, \`twitch:unban\`, \`twitch:stream_online\`, \`twitch:stream_offline\`, \`twitch:stream_update\`, \`twitch:chat\`
|
|
383
|
+
|
|
384
|
+
YouTube (10): \`youtube:subscribe\`, \`youtube:member\`, \`youtube:superchat\`, \`youtube:supersticker\`, \`youtube:gift_membership\`, \`youtube:gift_membership_received\`, \`youtube:poll\`, \`youtube:stream_online\`, \`youtube:stream_offline\`, \`youtube:chat\`
|
|
382
385
|
|
|
383
|
-
|
|
386
|
+
Kick (11): \`kick:follower\`, \`kick:subscribe\`, \`kick:resub\`, \`kick:gift\`, \`kick:kicks\`, \`kick:reward\`, \`kick:reward_status\`, \`kick:stream_online\`, \`kick:stream_offline\`, \`kick:stream_update\`, \`kick:chat\`
|
|
384
387
|
|
|
385
|
-
|
|
388
|
+
Spotify (12): \`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_edit\`, \`spotify:playlist_delete\`
|
|
386
389
|
|
|
387
|
-
|
|
390
|
+
StreamElements (1): \`streamelements:tip\`
|
|
388
391
|
|
|
389
|
-
|
|
392
|
+
Cross-platform chat (1, fires for every platform): \`chat:message\`
|
|
390
393
|
|
|
391
|
-
|
|
394
|
+
Kick events carry canonical top-level fields (\`user_id\`, \`user_login\`, \`user_name\`, \`avatarUrl\`, \`is_anonymous\`) plus the original Kick payload. \`kick:kicks\` has \`kicks\` (amount, like Twitch \`bits\`) and \`message\`; \`kick:reward\` / \`kick:reward_status\` have \`status\`, \`user_input\` and \`reward { id, title, cost, prompt }\`; \`kick:resub\` has \`cumulative_months\`.
|
|
392
395
|
|
|
393
396
|
### Server queries
|
|
394
397
|
\`\`\`tsx
|
|
@@ -419,7 +422,7 @@ const anim = useAnimation("slide-in", { duration: 300 });
|
|
|
419
422
|
| \`EGRESS_BLOCKED\` | Fetch to undeclared host | Add host to \`egress.allowHosts\` in config |
|
|
420
423
|
| \`STORAGE_QUOTA\` | Exceeded storage limit | Reduce data or request limit increase |
|
|
421
424
|
| \`EDITOR_ONLY\` | Mutation called from layer/interactive | Remove \`editorOnly\` or restrict to editor |
|
|
422
|
-
| \`SUPERVISOR_BLOCKED\` | Supervisor rejected a postMessage | Check message origin matches ext.lumio.vision |
|
|
425
|
+
| \`SUPERVISOR_BLOCKED\` | Supervisor rejected a postMessage | Check message origin matches the extension's runtime origin (\`<label>.runtime.ext.lumio.vision\`) |
|
|
423
426
|
| \`WORKER_CREATION_FAILED\` | Web Worker could not be created | Verify server functions bundle is valid JS |
|
|
424
427
|
| \`STORAGE_DOM_EXCEPTION\` | localStorage/sessionStorage access | Use \`useExtensionStorage()\` instead (storage blocked) |
|
|
425
428
|
|
|
@@ -429,7 +432,59 @@ See \`troubleshooting.md\` for the full error reference.
|
|
|
429
432
|
// src/content/components.ts
|
|
430
433
|
var componentsContent = `# Lumio SDK Components
|
|
431
434
|
|
|
432
|
-
All
|
|
435
|
+
All 31 components available in \`@zaflun/lumio-sdk\`. Every component accepts an optional \`style: VisionStyle\` prop.
|
|
436
|
+
|
|
437
|
+
## Editor Components
|
|
438
|
+
|
|
439
|
+
14 components for building settings UIs in \`editor.tsx\` and \`designer.tsx\`. They serialize via the Reconciler and render as \`@lumio/ui\` components on the host \u2014 matching the dashboard design system.
|
|
440
|
+
|
|
441
|
+
\`\`\`tsx
|
|
442
|
+
import {
|
|
443
|
+
EditorInput, EditorTextarea, EditorSelect, EditorCheckbox,
|
|
444
|
+
EditorSlider, EditorColorPicker, EditorGroup, EditorLabel,
|
|
445
|
+
EditorDivider, EditorInfo, EditorFontRow, EditorButton,
|
|
446
|
+
EditorFontPicker, EditorChannelRow, useEditorAction,
|
|
447
|
+
} from "@zaflun/lumio-sdk";
|
|
448
|
+
|
|
449
|
+
// Text/number input with label, placeholder, min/max, unit
|
|
450
|
+
<EditorInput label="Font Size" type="number" value="16" onChange={setSize} min={8} max={200} unit="px" />
|
|
451
|
+
|
|
452
|
+
// Select dropdown
|
|
453
|
+
<EditorSelect label="Theme" value={theme} onChange={setTheme} options={[{ value: "dark", label: "Dark" }]} />
|
|
454
|
+
|
|
455
|
+
// Checkbox toggle
|
|
456
|
+
<EditorCheckbox label="Show Badges" checked={show} onChange={setShow} />
|
|
457
|
+
|
|
458
|
+
// Slider with range
|
|
459
|
+
<EditorSlider label="Opacity" value={100} onChange={setOpacity} min={0} max={100} unit="%" />
|
|
460
|
+
|
|
461
|
+
// Color picker (HSV canvas + hex input)
|
|
462
|
+
<EditorColorPicker label="Background" value="#000000" onChange={setColor} />
|
|
463
|
+
|
|
464
|
+
// Compact font row: [B] [size px] [color]
|
|
465
|
+
<EditorFontRow bold={isBold} onBoldChange={setBold} fontSize={16} onFontSizeChange={setSize} fontColor="#fff" onFontColorChange={setColor} />
|
|
466
|
+
|
|
467
|
+
// Full-width button (primary/default/destructive)
|
|
468
|
+
<EditorButton label="Open Designer" variant="primary" onClick={openDesigner} />
|
|
469
|
+
|
|
470
|
+
// Collapsible section
|
|
471
|
+
<EditorGroup label="Advanced"><EditorCheckbox ... /></EditorGroup>
|
|
472
|
+
|
|
473
|
+
// Font picker with weight dropdown (opens NavigationStack page)
|
|
474
|
+
<EditorFontPicker label="Font Family" value="Lexend" onChange={setFont} weight="400" onWeightChange={setWeight} />
|
|
475
|
+
|
|
476
|
+
// Channel row: platform + name + color + delete in one line
|
|
477
|
+
<EditorChannelRow platform="twitch" onPlatformChange={setPlatform} channel="username" onChannelChange={setChannel} color="#9146FF" onColorChange={setColor} onRemove={remove} />
|
|
478
|
+
|
|
479
|
+
// Dispatch UI actions (e.g. open designer)
|
|
480
|
+
const { dispatch } = useEditorAction();
|
|
481
|
+
<EditorButton label="Open Designer" variant="primary" onClick={() => dispatch("open-designer")} />
|
|
482
|
+
|
|
483
|
+
// Section label, divider, info text
|
|
484
|
+
<EditorLabel text="Settings" />
|
|
485
|
+
<EditorDivider />
|
|
486
|
+
<EditorInfo content="Configure your widget here." />
|
|
487
|
+
\`\`\`
|
|
433
488
|
|
|
434
489
|
> **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.
|
|
435
490
|
|
|
@@ -750,6 +805,69 @@ const exitAnim = useAnimation("fade-out", { duration: 200 });
|
|
|
750
805
|
- Keep animation durations short on the layer surface (< 500ms) to avoid frame drops
|
|
751
806
|
- Avoid animating layout-affecting properties (\`width\`, \`height\`) on the layer; prefer \`opacity\` and \`transform\`
|
|
752
807
|
|
|
808
|
+
## Picker Components
|
|
809
|
+
|
|
810
|
+
### IconPicker
|
|
811
|
+
|
|
812
|
+
Universal icon/emoji/emote picker with 4 tabs: Lucide icons (1960), Unicode emojis (1584), platform emotes (via API), and file upload. **Editor-only** \u2014 use \`IconDisplay\` to render the selected icon on the layer surface.
|
|
813
|
+
|
|
814
|
+
\`\`\`tsx
|
|
815
|
+
import { IconPicker, IconDisplay } from "@zaflun/lumio-sdk";
|
|
816
|
+
import type { IconValue } from "@zaflun/lumio-sdk";
|
|
817
|
+
|
|
818
|
+
// In editor.tsx \u2014 let the user pick an icon
|
|
819
|
+
const [storage, setStorage] = useExtensionStorage();
|
|
820
|
+
|
|
821
|
+
<IconPicker
|
|
822
|
+
value={storage.icon ?? null}
|
|
823
|
+
onChange={(icon) => setStorage({ ...storage, icon })}
|
|
824
|
+
sources={["lucide", "emoji", "emote", "upload"]}
|
|
825
|
+
size="md"
|
|
826
|
+
placeholder="Choose an icon..."
|
|
827
|
+
/>
|
|
828
|
+
|
|
829
|
+
// In layer.tsx \u2014 render the selected icon
|
|
830
|
+
<IconDisplay icon={storage.icon ?? null} size={32} />
|
|
831
|
+
\`\`\`
|
|
832
|
+
|
|
833
|
+
**IconPickerProps:**
|
|
834
|
+
- \`value: IconValue | null\` \u2014 currently selected icon
|
|
835
|
+
- \`onChange: (icon: IconValue | null) => void\` \u2014 selection callback
|
|
836
|
+
- \`sources?: IconSource[]\` \u2014 tabs to show (default: all four: \`["lucide", "emoji", "emote", "upload"]\`)
|
|
837
|
+
- \`size?: "sm" | "md" | "lg"\` \u2014 picker trigger button size
|
|
838
|
+
- \`placeholder?: string\` \u2014 placeholder text when no icon selected
|
|
839
|
+
|
|
840
|
+
**IconDisplayProps:**
|
|
841
|
+
- \`icon: IconValue | null\` \u2014 icon to render (null renders nothing)
|
|
842
|
+
- \`size?: number\` \u2014 pixel size
|
|
843
|
+
- \`className?: string\`
|
|
844
|
+
|
|
845
|
+
**IconValue type:**
|
|
846
|
+
\`\`\`typescript
|
|
847
|
+
type IconSource = "lucide" | "emoji" | "emote" | "upload";
|
|
848
|
+
|
|
849
|
+
interface IconValue {
|
|
850
|
+
type: IconSource; // which tab/source the icon came from
|
|
851
|
+
value: string; // icon name (lucide), Unicode char (emoji), CDN URL (emote), upload URL (upload)
|
|
852
|
+
label?: string; // human-readable label
|
|
853
|
+
platform?: string; // platform identifier for emotes (e.g. "twitch", "7tv")
|
|
854
|
+
}
|
|
855
|
+
\`\`\`
|
|
856
|
+
|
|
857
|
+
**\`useIconPicker()\` hook** \u2014 headless state management for custom picker UIs:
|
|
858
|
+
\`\`\`tsx
|
|
859
|
+
import { useIconPicker } from "@zaflun/lumio-sdk";
|
|
860
|
+
|
|
861
|
+
const { isOpen, selected, open, close, select, clear, sources } = useIconPicker({
|
|
862
|
+
sources: ["lucide", "emoji"],
|
|
863
|
+
onSelect: (icon) => console.log("Selected:", icon),
|
|
864
|
+
});
|
|
865
|
+
\`\`\`
|
|
866
|
+
|
|
867
|
+
Returns: \`{ isOpen, selected, open, close, select, clear, sources }\`
|
|
868
|
+
|
|
869
|
+
**Upload tab** requires the extension to declare \`egress.allowHosts\` including the Lumio API. Uploaded files are stored per-extension with quota tracking via the \`extension_uploads\` table.
|
|
870
|
+
|
|
753
871
|
## VisionStyle
|
|
754
872
|
|
|
755
873
|
Typed CSS subset accepted by all components. Sanitized by the host \u2014 only safe properties are allowed.
|
|
@@ -946,49 +1064,126 @@ import { useLumioAction } from "@zaflun/lumio-sdk";
|
|
|
946
1064
|
|
|
947
1065
|
// Send a chat message on the connected platform
|
|
948
1066
|
const { execute: sendChat, isLoading, error } = useLumioAction("chat:send");
|
|
949
|
-
sendChat({
|
|
1067
|
+
sendChat({
|
|
1068
|
+
platform: "twitch",
|
|
1069
|
+
channel_id: "primary",
|
|
1070
|
+
message: "Hello from the extension!"
|
|
1071
|
+
});
|
|
950
1072
|
|
|
951
1073
|
// Switch OBS scene (requires OBS WebSocket integration)
|
|
952
1074
|
const { execute: setScene } = useLumioAction("obs:set_scene");
|
|
953
|
-
setScene({
|
|
1075
|
+
setScene({ scene_name: "Main Scene" });
|
|
954
1076
|
|
|
955
1077
|
// Emit a custom event into the Lumio event bus
|
|
956
1078
|
const { execute: emitEvent } = useLumioAction("events:emit");
|
|
957
|
-
emitEvent({
|
|
1079
|
+
emitEvent({ event_type: "extension:custom", data: { score: 42 } });
|
|
958
1080
|
|
|
959
1081
|
// Update a specific layer in the overlay
|
|
960
1082
|
const { execute: updateLayer } = useLumioAction("overlay:update_layer");
|
|
961
|
-
updateLayer({
|
|
1083
|
+
updateLayer({
|
|
1084
|
+
overlay_id: "overlay-uuid",
|
|
1085
|
+
layer_id: "layer-uuid",
|
|
1086
|
+
config: { visible: true }
|
|
1087
|
+
});
|
|
962
1088
|
\`\`\`
|
|
963
1089
|
|
|
964
|
-
**Available actions (
|
|
1090
|
+
**Available dispatched actions (\`action\` is \`LumioActionType\`):**
|
|
965
1091
|
|
|
966
1092
|
| Action | Permission required | Description |
|
|
967
1093
|
|--------|--------------------|----|
|
|
968
1094
|
| \`chat:send\` | \`chat:send\` | Send chat message on connected platform |
|
|
969
1095
|
| \`chat:delete\` | \`chat:delete\` | Delete a chat message |
|
|
970
|
-
| \`
|
|
971
|
-
| \`
|
|
972
|
-
| \`
|
|
973
|
-
| \`obs:
|
|
974
|
-
| \`
|
|
975
|
-
| \`
|
|
976
|
-
| \`obs:stop_stream\` | \`obs:control\` | Stop OBS streaming |
|
|
977
|
-
| \`obs:start_recording\` | \`obs:control\` | Start OBS recording |
|
|
978
|
-
| \`obs:stop_recording\` | \`obs:control\` | Stop OBS recording |
|
|
979
|
-
| \`events:emit\` | \`events:write\` | Emit event into Lumio event bus |
|
|
980
|
-
| \`overlay:update_layer\` | \`overlays:edit\` | Update overlay layer properties |
|
|
981
|
-
| \`spotify:play\` | \`spotify:control\` | Resume Spotify playback |
|
|
982
|
-
| \`spotify:pause\` | \`spotify:control\` | Pause Spotify playback |
|
|
983
|
-
| \`spotify:skip\` | \`spotify:control\` | Skip to next track |
|
|
984
|
-
| \`spotify:seek\` | \`spotify:control\` | Seek to position in current track |
|
|
985
|
-
| \`spotify:volume\` | \`spotify:control\` | Set Spotify volume |
|
|
986
|
-
| \`spotify:shuffle\` | \`spotify:control\` | Toggle shuffle mode |
|
|
987
|
-
| \`spotify:repeat\` | \`spotify:control\` | Set repeat mode |
|
|
988
|
-
| \`spotify:queue_add\` | \`spotify:control\` | Add track to Spotify queue |
|
|
1096
|
+
| \`obs:set_scene\` | \`obs:set_scene\` | Switch OBS scene |
|
|
1097
|
+
| \`obs:set_source_visible\` | \`obs:set_source_visible\` | Show or hide an OBS source |
|
|
1098
|
+
| \`obs:start_stream\` | \`obs:start_stream\` | Start OBS streaming |
|
|
1099
|
+
| \`obs:stop_stream\` | \`obs:stop_stream\` | Stop OBS streaming |
|
|
1100
|
+
| \`events:emit\` | \`events:emit\` | Emit event into Lumio event bus |
|
|
1101
|
+
| \`overlay:update_layer\` | \`overlay:update_layer\` | Update overlay layer properties |
|
|
989
1102
|
|
|
990
1103
|
Actions are proxied through the Lumio API \u2014 the extension never accesses OAuth tokens or platform credentials directly.
|
|
991
1104
|
|
|
1105
|
+
## Automation Node Handlers
|
|
1106
|
+
|
|
1107
|
+
Extensions with category \`automation_node\` export a single handler in \`server/handler.ts\`. The handler type depends on the \`node_type\` declared in \`lumio.config.json\`.
|
|
1108
|
+
|
|
1109
|
+
### \`defineAutomationAction(handler)\`
|
|
1110
|
+
|
|
1111
|
+
Action nodes execute when triggered by an upstream node in the automation workflow. They receive input data from the previous node and can perform side effects (HTTP calls, DB writes).
|
|
1112
|
+
|
|
1113
|
+
\`\`\`typescript
|
|
1114
|
+
import { defineAutomationAction } from "@zaflun/lumio-sdk/server";
|
|
1115
|
+
|
|
1116
|
+
export default defineAutomationAction({
|
|
1117
|
+
handler: async (ctx) => {
|
|
1118
|
+
const webhookUrl = ctx.nodeConfig.webhook_url;
|
|
1119
|
+
const res = await ctx.fetch(webhookUrl, {
|
|
1120
|
+
method: "POST",
|
|
1121
|
+
headers: { "Content-Type": "application/json" },
|
|
1122
|
+
body: JSON.stringify(ctx.inputData),
|
|
1123
|
+
});
|
|
1124
|
+
return { status: res.status, body: await res.json() };
|
|
1125
|
+
},
|
|
1126
|
+
});
|
|
1127
|
+
\`\`\`
|
|
1128
|
+
|
|
1129
|
+
### \`defineAutomationTrigger({ onWebhook?, onPoll? })\`
|
|
1130
|
+
|
|
1131
|
+
Trigger nodes start automation workflows. They support two modes: webhook (receives external HTTP calls) and poll (periodic checks for new data).
|
|
1132
|
+
|
|
1133
|
+
\`\`\`typescript
|
|
1134
|
+
import { defineAutomationTrigger } from "@zaflun/lumio-sdk/server";
|
|
1135
|
+
|
|
1136
|
+
export default defineAutomationTrigger({
|
|
1137
|
+
onWebhook: async (ctx, request) => {
|
|
1138
|
+
const body = await request.json();
|
|
1139
|
+
if (body.event === "order.created") {
|
|
1140
|
+
return { trigger: true, outputData: { orderId: body.id, total: body.total } };
|
|
1141
|
+
}
|
|
1142
|
+
return { trigger: false };
|
|
1143
|
+
},
|
|
1144
|
+
onPoll: async (ctx) => {
|
|
1145
|
+
const lastCheck = await ctx.cache.get("last_check") ?? "1970-01-01T00:00:00Z";
|
|
1146
|
+
const res = await ctx.fetch(
|
|
1147
|
+
\`https://api.example.com/events?since=\${lastCheck}\`,
|
|
1148
|
+
{ headers: { "Authorization": \`Bearer \${ctx.secrets.get("API_KEY")}\` } }
|
|
1149
|
+
);
|
|
1150
|
+
const events = await res.json();
|
|
1151
|
+
await ctx.cache.set("last_check", new Date().toISOString());
|
|
1152
|
+
return events.map((e: any) => ({ trigger: true, outputData: e }));
|
|
1153
|
+
},
|
|
1154
|
+
});
|
|
1155
|
+
\`\`\`
|
|
1156
|
+
|
|
1157
|
+
### \`defineAutomationLogic(handler)\`
|
|
1158
|
+
|
|
1159
|
+
Logic nodes evaluate conditions and route the workflow to different branches. They return a branch key that determines which downstream path executes.
|
|
1160
|
+
|
|
1161
|
+
\`\`\`typescript
|
|
1162
|
+
import { defineAutomationLogic } from "@zaflun/lumio-sdk/server";
|
|
1163
|
+
|
|
1164
|
+
export default defineAutomationLogic({
|
|
1165
|
+
handler: async (ctx) => {
|
|
1166
|
+
const amount = ctx.inputData.total;
|
|
1167
|
+
if (amount >= 100) return { branch: "high_value" };
|
|
1168
|
+
if (amount >= 25) return { branch: "medium_value" };
|
|
1169
|
+
return { branch: "low_value" };
|
|
1170
|
+
},
|
|
1171
|
+
});
|
|
1172
|
+
\`\`\`
|
|
1173
|
+
|
|
1174
|
+
### Automation Handler Context (\`ctx\`)
|
|
1175
|
+
|
|
1176
|
+
| Property | Available in | Description |
|
|
1177
|
+
|----------|-------------|-------------|
|
|
1178
|
+
| \`ctx.nodeConfig\` | All | Node configuration from the user's config panel |
|
|
1179
|
+
| \`ctx.inputData\` | Action, Logic | Data from the upstream node |
|
|
1180
|
+
| \`ctx.fetch(url, options?)\` | All | HTTP request (egress allowlist enforced) |
|
|
1181
|
+
| \`ctx.cache.get(key)\` / \`ctx.cache.set(key, value, ttl?)\` | All | Per-installation KV cache (Redis, 1h default TTL) |
|
|
1182
|
+
| \`ctx.secrets.get(key)\` | All | Server-side secret |
|
|
1183
|
+
| \`ctx.db\` | All | Extension DB schema access |
|
|
1184
|
+
| \`ctx.auth\` | All | \`{ accountId }\` \u2014 account identity |
|
|
1185
|
+
| \`ctx.defer()\` | Action | Mark execution as async (returns 202, completes in background) |
|
|
1186
|
+
|
|
992
1187
|
## Bot Module Handlers
|
|
993
1188
|
|
|
994
1189
|
Extensions with category \`bot_module\` export handler wrappers that register triggers. Each wrapper defines when the handler fires and what context it receives.
|
|
@@ -1087,6 +1282,37 @@ export const toxicFilter = moderate({
|
|
|
1087
1282
|
});
|
|
1088
1283
|
\`\`\`
|
|
1089
1284
|
|
|
1285
|
+
### \`defineModerate(handler)\`
|
|
1286
|
+
|
|
1287
|
+
Convenience wrapper for system-style moderation handlers. The handler receives a unified context with the chat message and returns a moderation action.
|
|
1288
|
+
|
|
1289
|
+
\`\`\`typescript
|
|
1290
|
+
import { defineModerate } from "@zaflun/lumio-sdk/server";
|
|
1291
|
+
|
|
1292
|
+
export default defineModerate(async (ctx) => {
|
|
1293
|
+
if (ctx.message.text.includes("spam")) {
|
|
1294
|
+
return { action: "delete" };
|
|
1295
|
+
}
|
|
1296
|
+
return { action: "allow" };
|
|
1297
|
+
});
|
|
1298
|
+
\`\`\`
|
|
1299
|
+
|
|
1300
|
+
### \`defineTimer(handler)\`
|
|
1301
|
+
|
|
1302
|
+
Convenience wrapper for timer-based handlers (e.g., timed messages). Fires on a recurring interval configured in the extension's install config.
|
|
1303
|
+
|
|
1304
|
+
\`\`\`typescript
|
|
1305
|
+
import { defineTimer } from "@zaflun/lumio-sdk/server";
|
|
1306
|
+
|
|
1307
|
+
export default defineTimer(async (ctx) => {
|
|
1308
|
+
const messages = (ctx.installConfig.messages as string || "").split("\\n").filter(Boolean);
|
|
1309
|
+
if (messages.length === 0) return null;
|
|
1310
|
+
const idx = parseInt(await ctx.cache.get("idx") || "0", 10);
|
|
1311
|
+
await ctx.cache.set("idx", String((idx + 1) % messages.length));
|
|
1312
|
+
return { reply: messages[idx] };
|
|
1313
|
+
});
|
|
1314
|
+
\`\`\`
|
|
1315
|
+
|
|
1090
1316
|
### Handler Return Actions
|
|
1091
1317
|
|
|
1092
1318
|
All bot module handlers return an action object:
|
|
@@ -1110,7 +1336,7 @@ The extension must declare the matching permission in \`lumio.config.json\`. For
|
|
|
1110
1336
|
| \`chat:delete\` | Delete chat messages | Enhanced |
|
|
1111
1337
|
| \`chat:ban\` | Ban, unban, and timeout users | Enhanced |
|
|
1112
1338
|
| \`events:read\` | Subscribe to platform events | Standard |
|
|
1113
|
-
| \`events:
|
|
1339
|
+
| \`events:emit\` | Emit custom events | Standard |
|
|
1114
1340
|
`;
|
|
1115
1341
|
|
|
1116
1342
|
// src/content/cli-workflows.ts
|
|
@@ -1170,15 +1396,14 @@ lumio init my-widget
|
|
|
1170
1396
|
# ? Category: widget
|
|
1171
1397
|
# ? Targets: editor, layer
|
|
1172
1398
|
# ? Server functions: yes
|
|
1173
|
-
# ?
|
|
1399
|
+
# ? How should the editor UI be built? config_schema only / editor.tsx only / Both
|
|
1174
1400
|
# ? Owner: My Stream (personal) / Awesome Team <- team selection if applicable
|
|
1175
1401
|
#
|
|
1176
1402
|
# Creating extension on Lumio...
|
|
1177
1403
|
# Extension-ID: 88b2478a-39c7-468e-bebd-14e52fd97ac0
|
|
1178
1404
|
# Slug: k8f2x-my-widget
|
|
1179
1405
|
#
|
|
1180
|
-
# Writing lumio.config.json...
|
|
1181
|
-
# Writing src/editor.tsx...
|
|
1406
|
+
# Writing lumio.config.json (with config_schema)...
|
|
1182
1407
|
# Writing src/layer.tsx...
|
|
1183
1408
|
# Writing server/schema.ts...
|
|
1184
1409
|
# Writing server/functions.ts...
|
|
@@ -1186,6 +1411,32 @@ lumio init my-widget
|
|
|
1186
1411
|
# Done! Run \`lumio dev\` to start developing.
|
|
1187
1412
|
\`\`\`
|
|
1188
1413
|
|
|
1414
|
+
### Automation Node Example
|
|
1415
|
+
|
|
1416
|
+
\`\`\`bash
|
|
1417
|
+
lumio init my-webhook-action
|
|
1418
|
+
|
|
1419
|
+
# Interactive prompts:
|
|
1420
|
+
# ? Extension name: My Webhook Action
|
|
1421
|
+
# ? Description: Posts data to a webhook
|
|
1422
|
+
# ? Category: automation_node
|
|
1423
|
+
# ? Node type: action
|
|
1424
|
+
# ? Server functions: yes (required for automation nodes)
|
|
1425
|
+
# ? Owner: My Stream (personal) / Awesome Team
|
|
1426
|
+
#
|
|
1427
|
+
# Creating extension on Lumio...
|
|
1428
|
+
# Extension-ID: a1b2c3d4-...
|
|
1429
|
+
# Slug: x9k1z-my-webhook-action
|
|
1430
|
+
#
|
|
1431
|
+
# Writing lumio.config.json...
|
|
1432
|
+
# Writing src/editor.tsx...
|
|
1433
|
+
# Writing server/handler.ts...
|
|
1434
|
+
#
|
|
1435
|
+
# Done! Run \`lumio dev\` to start developing.
|
|
1436
|
+
\`\`\`
|
|
1437
|
+
|
|
1438
|
+
Automation nodes always generate \`server/handler.ts\` (not \`server/functions.ts\`) and \`src/editor.tsx\` for the config panel. The \`node_type\` can be \`action\`, \`trigger\`, or \`logic\`.
|
|
1439
|
+
|
|
1189
1440
|
### Templates
|
|
1190
1441
|
|
|
1191
1442
|
| Template | Targets | Server | Description |
|
|
@@ -1195,6 +1446,9 @@ lumio init my-widget
|
|
|
1195
1446
|
| Full-Stack | editor + layer | Yes | Complete extension with server functions |
|
|
1196
1447
|
| Interactive Game | editor + layer + interactive | Yes | Viewer interaction game |
|
|
1197
1448
|
| Bot Module | server-only | Yes | Chat command module |
|
|
1449
|
+
| Automation Action | server + editor | Yes | Automation workflow action node |
|
|
1450
|
+
| Automation Trigger | server + editor | Yes | Automation workflow trigger node |
|
|
1451
|
+
| Automation Logic | server + editor | Yes | Automation workflow logic/branching node |
|
|
1198
1452
|
| Integration | server | Yes | External API integration |
|
|
1199
1453
|
| Theme | editor | No | Style variables only |
|
|
1200
1454
|
|
|
@@ -1215,27 +1469,59 @@ lumio dev
|
|
|
1215
1469
|
# Press 'q' to quit.
|
|
1216
1470
|
\`\`\`
|
|
1217
1471
|
|
|
1472
|
+
### Automation Node Development
|
|
1473
|
+
|
|
1474
|
+
For automation node extensions, the dev server auto-detects \`category: "automation_node"\` and launches the automation simulator:
|
|
1475
|
+
|
|
1476
|
+
\`\`\`bash
|
|
1477
|
+
lumio dev
|
|
1478
|
+
# or explicitly:
|
|
1479
|
+
lumio dev --automation-node
|
|
1480
|
+
|
|
1481
|
+
# Output:
|
|
1482
|
+
# Lumio Dev Server running at http://localhost:5173
|
|
1483
|
+
# Config Panel: http://localhost:5173/editor
|
|
1484
|
+
#
|
|
1485
|
+
# Automation Simulator ready. Commands:
|
|
1486
|
+
# /execute [JSON] \u2014 Run the action handler with input data
|
|
1487
|
+
# /webhook [JSON] \u2014 Simulate an incoming webhook (trigger nodes)
|
|
1488
|
+
# /poll \u2014 Trigger a poll cycle (trigger nodes)
|
|
1489
|
+
# /config [JSON] \u2014 Update node config
|
|
1490
|
+
# /quit \u2014 Stop the dev server
|
|
1491
|
+
\`\`\`
|
|
1492
|
+
|
|
1218
1493
|
Features:
|
|
1219
1494
|
- Hot reload on file changes
|
|
1220
1495
|
- Mock event system (simulated chat, follows, subs)
|
|
1221
1496
|
- Sandbox environment matching production constraints
|
|
1222
1497
|
- Server function execution with local DB
|
|
1498
|
+
- Automation simulator with /execute, /webhook, /poll, /config commands
|
|
1223
1499
|
|
|
1224
1500
|
## lumio build
|
|
1225
1501
|
|
|
1226
|
-
Bundle the extension
|
|
1502
|
+
Bundle the extension and create source archive.
|
|
1227
1503
|
|
|
1228
1504
|
\`\`\`bash
|
|
1229
1505
|
lumio build
|
|
1230
1506
|
|
|
1231
|
-
# Output:
|
|
1232
|
-
#
|
|
1233
|
-
#
|
|
1234
|
-
#
|
|
1235
|
-
#
|
|
1236
|
-
#
|
|
1507
|
+
# Output for widgets:
|
|
1508
|
+
# Built: dist/editor.js
|
|
1509
|
+
# Built: dist/layer.js
|
|
1510
|
+
# Compiled: dist/styles.css (if Tailwind detected)
|
|
1511
|
+
# Archive: dist/source.tar.gz
|
|
1512
|
+
#
|
|
1513
|
+
# Output for bot modules (server: true):
|
|
1514
|
+
# Bot module \u2014 no UI targets to bundle.
|
|
1515
|
+
# Built: dist/server.js
|
|
1516
|
+
# Archive: dist/source.tar.gz
|
|
1237
1517
|
\`\`\`
|
|
1238
1518
|
|
|
1519
|
+
Build steps:
|
|
1520
|
+
1. UI targets (\`editor\`, \`layer\`, \`interactive\`, \`designer\`) \u2014 esbuild IIFE bundles
|
|
1521
|
+
2. Server handler (\`server: true\`) \u2014 compiles \`server/handler.ts\` to \`dist/server.js\` (ESM)
|
|
1522
|
+
3. CSS \u2014 Tailwind v4 auto-detected and compiled, or plain CSS copied
|
|
1523
|
+
4. Source archive \u2014 \`dist/source.tar.gz\`
|
|
1524
|
+
|
|
1239
1525
|
Warns if \`CHANGELOG.md\` is missing.
|
|
1240
1526
|
|
|
1241
1527
|
## lumio deploy
|
|
@@ -1259,6 +1545,11 @@ lumio deploy --breaking -v 2.0.0
|
|
|
1259
1545
|
lumio deploy --draft
|
|
1260
1546
|
\`\`\`
|
|
1261
1547
|
|
|
1548
|
+
Pre-flight checks before upload:
|
|
1549
|
+
- Sound files validated (format, existence in \`dist/sounds/\`, per-file size against limit)
|
|
1550
|
+
- Sound count, per-file size, and total storage checked against resolved limits from API
|
|
1551
|
+
- Manifest validated against schema
|
|
1552
|
+
|
|
1262
1553
|
After deploy:
|
|
1263
1554
|
1. Version status = \`draft\`
|
|
1264
1555
|
2. Submit for review via dashboard (\`/dashboard/extensions/{slug}/submit\`)
|
|
@@ -1427,7 +1718,7 @@ AuthContext::ExtensionToken {
|
|
|
1427
1718
|
### Token Lifecycle
|
|
1428
1719
|
|
|
1429
1720
|
1. User opens extension in dashboard/overlay \u2014 Lumio server generates \`lm_ext_*\` token
|
|
1430
|
-
2. Token
|
|
1721
|
+
2. Token handed to the extension inside the sandbox; the Extension Runtime iframe runs on the extension's own origin \`<label>.runtime.ext.lumio.vision\` (\`<label>\` = extension UUID as 32 lowercase hex). The extension \`slug\` includes the \`short_id\` prefix (e.g. \`k8f2x-my-extension\`); the store URL is \`/store/k8f2x-my-extension\`
|
|
1431
1722
|
3. SDK uses token for all API calls (server functions, storage, events)
|
|
1432
1723
|
4. Token auto-refreshes via postMessage every 12 minutes (before 15-min expiry) \u2014 the Supervisor mediates the refresh
|
|
1433
1724
|
5. On page reload, a new token is generated
|
|
@@ -1504,11 +1795,11 @@ Extensions run inside a 3-layer iframe sandbox. Understanding the layers is impo
|
|
|
1504
1795
|
|
|
1505
1796
|
\`\`\`
|
|
1506
1797
|
Host (lumio.vision / browser source)
|
|
1507
|
-
\u2514\u2500\u2500 Supervisor iframe (supervisor.
|
|
1798
|
+
\u2514\u2500\u2500 Supervisor iframe (supervisor.ext.lumio.vision)
|
|
1508
1799
|
Purpose: validates postMessage traffic, enforces egress allowlist,
|
|
1509
1800
|
mediates token refresh, proxies LumioComponentNode[] to host
|
|
1510
|
-
\u2514\u2500\u2500 Extension Runtime iframe (ext.lumio.vision)
|
|
1511
|
-
Purpose: runs extension code on
|
|
1801
|
+
\u2514\u2500\u2500 Extension Runtime iframe (<label>.runtime.ext.lumio.vision)
|
|
1802
|
+
Purpose: runs extension code on its own origin (one per extension)
|
|
1512
1803
|
Storage: localStorage/sessionStorage BLOCKED (cross-origin isolation)
|
|
1513
1804
|
\u2514\u2500\u2500 Web Worker
|
|
1514
1805
|
Purpose: V8 isolate for server function execution
|
|
@@ -1521,8 +1812,9 @@ Host (lumio.vision / browser source)
|
|
|
1521
1812
|
- Mediates \`lm_ext_*\` token refresh (12 min interval, before 15 min expiry)
|
|
1522
1813
|
- Forwards serialized \`LumioComponentNode[]\` to the Host renderer
|
|
1523
1814
|
|
|
1524
|
-
**Extension Runtime (ext.lumio.vision) origin isolation:**
|
|
1815
|
+
**Extension Runtime (<label>.runtime.ext.lumio.vision) origin isolation:**
|
|
1525
1816
|
- Runs on a separate origin from the Host \u2014 this prevents direct DOM access and localStorage sharing
|
|
1817
|
+
- Every extension gets its own origin (\`<label>\` = extension UUID as 32 lowercase hex), so two extensions never share browser state
|
|
1526
1818
|
- \`localStorage\` and \`sessionStorage\` throw \`DOMException: Access is denied\` \u2014 use \`useExtensionStorage()\` instead
|
|
1527
1819
|
- \`document.cookie\` is unavailable
|
|
1528
1820
|
- Cross-origin postMessage to the Host goes through the Supervisor
|
|
@@ -1697,22 +1989,46 @@ Lumio.render(<Overlay />, { target: "layer" });
|
|
|
1697
1989
|
|
|
1698
1990
|
## Config Schema
|
|
1699
1991
|
|
|
1700
|
-
Use \`config_schema\` in \`lumio.config.json\` for user-customizable settings. The
|
|
1992
|
+
Use \`config_schema\` in \`lumio.config.json\` for user-customizable settings. The widget editor auto-generates form fields from the schema \u2014 no custom \`editor.tsx\` needed for simple settings. For advanced UIs, use \`editor.tsx\` alongside (or instead of) config_schema.
|
|
1993
|
+
|
|
1994
|
+
**Three valid combos:**
|
|
1995
|
+
- **config_schema only** \u2014 auto-generated editor form, \`targets: ["layer"]\`
|
|
1996
|
+
- **editor.tsx only** \u2014 fully custom React editor UI, \`targets: ["layer", "editor"]\`
|
|
1997
|
+
- **Both** \u2014 schema fields render first, editor.tsx renders below for advanced controls
|
|
1998
|
+
|
|
1999
|
+
**Supported field types:** \`string\`, \`number\`, \`boolean\`, \`color\`, \`select\`, \`textarea\`, \`slider\`, \`font\`, \`icon\`, \`image\`, \`sound\`, \`multiselect\`, \`group\`, \`divider\`, \`info\`, \`channel\`, \`designer\`, \`presets\`
|
|
1701
2000
|
|
|
1702
2001
|
\`\`\`json
|
|
1703
2002
|
{
|
|
1704
2003
|
"config_schema": [
|
|
1705
|
-
{ "key": "title", "type": "string", "label": "Widget Title", "default_value": "My Widget", "required": true },
|
|
1706
|
-
{ "key": "
|
|
2004
|
+
{ "key": "title", "type": "string", "label": "Widget Title", "default_value": "My Widget", "required": true, "placeholder": "Enter title..." },
|
|
2005
|
+
{ "key": "fontSize", "type": "number", "label": "Font Size", "default_value": 16, "required": false, "min": 8, "max": 200, "unit": "px" },
|
|
2006
|
+
{ "key": "volume", "type": "slider", "label": "Volume", "default_value": 50, "required": false, "min": 0, "max": 100, "unit": "%" },
|
|
2007
|
+
{ "key": "theme", "type": "select", "label": "Color Theme", "options": ["dark", "light"], "default_value": "dark", "required": false },
|
|
2008
|
+
{ "key": "primaryColor", "type": "color", "label": "Primary Color", "default_value": "#43F7CD", "required": false },
|
|
1707
2009
|
{ "key": "showBorder", "type": "boolean", "label": "Show Border", "default_value": true, "required": false },
|
|
2010
|
+
{ "key": "targetDate", "type": "string", "label": "Target Date", "default_value": "", "required": false, "visible_when": { "key": "mode", "equals": "countdown" } },
|
|
1708
2011
|
{
|
|
1709
|
-
"key": "
|
|
1710
|
-
"
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
}
|
|
2012
|
+
"key": "twitch_events", "type": "group", "label": "Twitch Events", "required": false, "collapsed": true,
|
|
2013
|
+
"fields": [
|
|
2014
|
+
{ "key": "includeFollowers", "type": "boolean", "label": "Followers", "default_value": true, "required": false },
|
|
2015
|
+
{ "key": "includeSubs", "type": "boolean", "label": "Subscriptions", "default_value": true, "required": false }
|
|
2016
|
+
]
|
|
2017
|
+
},
|
|
2018
|
+
{ "key": "separator", "type": "divider", "label": "", "required": false },
|
|
2019
|
+
{ "key": "help_text", "type": "info", "label": "", "required": false, "content": "Configure the events you want to display." }
|
|
2020
|
+
]
|
|
2021
|
+
}
|
|
2022
|
+
\`\`\`
|
|
2023
|
+
|
|
2024
|
+
**Designer Schema:** Extensions can define a \`designer_schema\` alongside \`config_schema\` for a fullpage visual design editor. The designer opens as a fullpage overlay with the schema fields on the left and a live preview on the right. Designer values are stored under \`config.design\`.
|
|
2025
|
+
|
|
2026
|
+
\`\`\`json
|
|
2027
|
+
{
|
|
2028
|
+
"designer_schema": [
|
|
2029
|
+
{ "key": "bubbleColor", "type": "color", "label": "Chat Bubble Color", "default_value": "#9146FF", "required": false },
|
|
2030
|
+
{ "key": "nameFont", "type": "font", "label": "Username Font", "default_value": "Inter", "required": false },
|
|
2031
|
+
{ "key": "badgeSize", "type": "slider", "label": "Badge Size", "default_value": 18, "required": false, "min": 12, "max": 32, "unit": "px" }
|
|
1716
2032
|
]
|
|
1717
2033
|
}
|
|
1718
2034
|
\`\`\`
|
|
@@ -1722,7 +2038,7 @@ Read config values via \`useLumioConfig()\`:
|
|
|
1722
2038
|
\`\`\`tsx
|
|
1723
2039
|
const [config] = useLumioConfig();
|
|
1724
2040
|
const title = config.title ?? "My Widget";
|
|
1725
|
-
const
|
|
2041
|
+
const fontSize = config.fontSize ?? 16;
|
|
1726
2042
|
\`\`\`
|
|
1727
2043
|
|
|
1728
2044
|
**When to use config_schema vs storage:**
|
|
@@ -1770,7 +2086,7 @@ For beta testing before public launch:
|
|
|
1770
2086
|
| Large inline images | Use URL-based images via \`Image\` component |
|
|
1771
2087
|
| Missing \`Lumio.render()\` | Must be called within 10s of page load |
|
|
1772
2088
|
| Hardcoded strings | Use \`config_schema\` fields for user-customizable text |
|
|
1773
|
-
| Using localStorage directly | Use \`useExtensionStorage()\` (localStorage blocked
|
|
2089
|
+
| Using localStorage directly | Use \`useExtensionStorage()\` (localStorage blocked on the Extension Runtime origin) |
|
|
1774
2090
|
| Raw CSS \`@keyframes\` animations | Use \`useAnimation()\` + \`<Animated>\` SDK presets |
|
|
1775
2091
|
|
|
1776
2092
|
## Bot Module Security
|
|
@@ -1914,7 +2230,7 @@ var troubleshootingContent = `# Troubleshooting
|
|
|
1914
2230
|
| \`BUNDLE_TOO_LARGE\` | Bundle exceeds size limit | Built bundle exceeds \`max_bundle_size_mb\` | Reduce bundle size or request limit increase |
|
|
1915
2231
|
| \`SUPERVISOR_BLOCKED\` | Supervisor rejected postMessage | Message origin or schema invalid | Ensure you are in the correct iframe layer; do not post to Host directly |
|
|
1916
2232
|
| \`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 |
|
|
1917
|
-
| \`STORAGE_DOM_EXCEPTION\` | localStorage/sessionStorage access attempt | Extension Runtime (ext.lumio.vision) blocks storage APIs | Replace \`localStorage.setItem\` with \`useExtensionStorage()\` |
|
|
2233
|
+
| \`STORAGE_DOM_EXCEPTION\` | localStorage/sessionStorage access attempt | Extension Runtime (<label>.runtime.ext.lumio.vision) blocks storage APIs | Replace \`localStorage.setItem\` with \`useExtensionStorage()\` |
|
|
1918
2234
|
| \`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\` |
|
|
1919
2235
|
|
|
1920
2236
|
## Common Issues
|
|
@@ -1952,7 +2268,7 @@ The SDK auto-refreshes \`lm_ext_*\` tokens via postMessage. The Supervisor media
|
|
|
1952
2268
|
### Supervisor blocked messages (\`SUPERVISOR_BLOCKED\`)
|
|
1953
2269
|
The Supervisor validates all postMessage traffic between Extension Runtime and Host. If you see this error:
|
|
1954
2270
|
1. Do not post messages directly from extension code to \`window.parent.parent\` (Host) \u2014 always use SDK hooks
|
|
1955
|
-
2. Verify the extension iframe is loaded inside \`supervisor.
|
|
2271
|
+
2. Verify the extension iframe is loaded inside \`supervisor.ext.lumio.vision\`, not directly in the Host
|
|
1956
2272
|
3. Check that the message schema matches the \`@zaflun/lumio-protocol\` wire format (SDK handles this automatically)
|
|
1957
2273
|
|
|
1958
2274
|
### Worker creation failures (\`WORKER_CREATION_FAILED\`)
|
|
@@ -1962,7 +2278,7 @@ The V8 isolate Web Worker for server functions failed to start:
|
|
|
1962
2278
|
3. Check \`lumio logs\` for the underlying JS error from the Worker initializer
|
|
1963
2279
|
|
|
1964
2280
|
### Storage DOMException errors (\`STORAGE_DOM_EXCEPTION\`)
|
|
1965
|
-
\`localStorage\` and \`sessionStorage\` are blocked on the Extension Runtime origin (
|
|
2281
|
+
\`localStorage\` and \`sessionStorage\` are blocked on the Extension Runtime origin (\`<label>.runtime.ext.lumio.vision\`):
|
|
1966
2282
|
1. Replace all \`localStorage.setItem\` / \`localStorage.getItem\` calls with \`useExtensionStorage()\`
|
|
1967
2283
|
2. Replace all \`sessionStorage\` usage with \`useVisionState()\` for transient values
|
|
1968
2284
|
3. For server-side persistence, use \`ctx.db\` in server functions
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zaflun/lumio-agent-skill",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"description": "AI agent skill files for building Lumio Extensions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -27,10 +27,10 @@
|
|
|
27
27
|
},
|
|
28
28
|
"license": "MIT",
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"@types/node": "^
|
|
30
|
+
"@types/node": "^26.1.2",
|
|
31
31
|
"tsup": "^8.4.0",
|
|
32
|
-
"typescript": "^
|
|
33
|
-
"vitest": "^
|
|
32
|
+
"typescript": "^6.0.3",
|
|
33
|
+
"vitest": "^4.1.10"
|
|
34
34
|
},
|
|
35
35
|
"repository": {
|
|
36
36
|
"type": "git",
|