When to use
Use this Skill when a user asks for a film-themed light show, soundtrack visualisation, or a private screening with Yeelight lights. It is a local, single-user experience; it never starts a public server, performs a cloud login, or asks the user to run a shell command.
Host workflow
The AI host owns service lifecycle. Execute the wrapper itself, parse its single
JSON-line response, reuse only a healthy instance with the requested mode and
Runtime context, and open the returned openUrl with the host browser tool.
Never tell the user to run a command, paste localhost, choose a port, or edit
an environment variable. The following are host-internal actions, not user
instructions:
# mock preview (the default for "预览/模拟/先看看")
sh scripts/invoke.sh start
# explicit live preflight (only after the user names a protected context)
sh scripts/invoke.sh start --mode live --profile <profile> --region <cn|sg|us|eu> --house-id <house-id>
# prefer the household gateway when protected Runtime metadata provides it
sh scripts/invoke.sh start --mode live --profile <profile> --region <cn|sg|us|eu> --house-id <house-id> --control-mode local-preferred --gateway-ip <private-gateway-ip>
The wrapper emits one JSON line with status, serviceStatus, mode,
openUrl, and healthUrl. PowerShell hosts use scripts/invoke.ps1 with the
same actions. If a running service has a different mode or live context, the
host runs stop and then the requested start; it does not silently reuse the
wrong instance. YEELIGHT_CINEMA_MODE=live and the active/default profile are
never enough to enable live mode.
Classify natural-language requests before starting:
- Mock preview: requests such as “preview, simulate, take a look first” use the default mock start. It never calls the Yeelight Runtime and may be opened immediately.
- Live preflight: requests such as “real, physical, EU home” require an explicit profile, region, and house selected by the host from protected local Runtime metadata. The host must verify the requested region/profile, discover the complete device list, and show the user the exact candidate names, room, count, online/capability evidence, and read-only pre-state.
- Live execution: never begin a physical write from an ambiguous request. Present the exact target set and recovery plan, then require the user to confirm the scoped test in the conversation. After the exact confirmation, the host uses the internal host-validation wrapper to prepare and consume a one-time grant. The page proof alone cannot prepare or run a physical test. A missing, stale, duplicated, offline, or capability-unknown target is a hard stop.
The host must keep the returned URL and service state in its own tool context;
the user only interacts with the opened page. status and stop are host
actions for health/recovery, not commands to copy into chat.
The first page runs in deterministic mock mode, so catalog, audio, lighting,
stop, and error states are safe to explore without hardware. A live run is
opt-in and fails closed unless the installed yeelight-home Runtime passes a
read-only preflight with an explicit profile/region/house. The only production
path is:
browser -> loopback service -> semantic Runtime request -> yeelight-home invoke --stdin
The browser does not receive Runtime identities, credentials, request payloads, headers, house/profile/region values, or artwork URLs. A session receives opaque display handles for the user's current device selection. Handles are bound to one immutable target snapshot and cannot be reused after stop, replacement, timeout, or service restart.
Directing a screening
- Search for a film and choose an official poster and exactly one soundtrack. Apple Music is the primary playback entry when the catalog returns an official track link; the selected catalog track is enough to prepare.
- Select one or more discovered lights. Every selected light is assigned once
to
AccentorAmbient; those names describe musical roles, not bulb counts. A single light still receives the complete composite track. - Use
Open Apple Music windowto open the selected track in one small, resizable top-level player; after selecting a different soundtrack, clicking the button reuses that window and navigates it to the new track instead of opening another window. The page also exposes a matching draggable Apple Music companion in the lower-right corner. It stays available while the Cinema Director page is used, and itsOpen player,Load selected track, andBring player forwardactions reuse the same top-level window as the panel button. This avoids third-party iframe preview limits. The initial open uses the companion's current position as a best-effort anchor. After the window has loaded, the companion remains an independent, non-disruptive control surface: browsers block web pages from moving an already-loaded cross-origin player, and the page never closes or restarts playback just to follow a drag. The page cannot force the player to remain always-on-top above Cinema Director. YouTube appears only as a secondary fallback when the selected track has no official Apple Music link. If a browser player should drive the lights, use the native audio-share picker to share the tab or window playing the soundtrack and enable its audio; the page cannot choose a source on the user's behalf. Local audio remains the fallback. - Prepare the console to capture a read-only target snapshot, then start it. In live mode, Prepare remains read-only before host validation; Start stays blocked until the bounded physical validation succeeds. The console shows live spectrum, conservative lyric cues, target roles, dispatch acknowledgements, and per-target results.
- Pause, replace, clear, or stop at any time. Stop invalidates the generation
first, fades the frozen target snapshot, briefly powers it off, restores the
recorded live pre-state, and reads back each target. A partial, timed-out,
or restore-mismatched result is reported as
uncertain.
The browser sends only one live frame request at a time. A busy or cadence
skip means no new physical write was accepted and does not create a recovery
queue. Every live frame covers the complete frozen selected set, regardless of
whether the user selected one, four, eighteen, or another supported number of
lights. Runtime writes use a bounded pool of twelve workers, so a large home is
parallel without creating an unbounded process burst. The recovery journal is
persisted once for the complete frame before any worker writes; a fatal error or
cancellation stops new work and drains already-started workers before Stop or
restore begins. Live Stop queries the frozen target set once before writing to
select targets with in-session write evidence, then reuses that selection for
fade/off/restore. It does not query again before restore; one final readback is
the physical result, and mismatches remain recoverable.
A live partial or uncertain result is recorded and the next frame continues
when every failed row is retryable: either an exact target-bound verification
mismatch or a Runtime response explicitly marked safeToRetry. This also
applies when every selected light in the frame fails, so a flaky light or a
short-lived backend issue cannot strand the remaining frame loop. The page
labels the failure class, and affected targets remain pending in the screening
journal until Stop verifies their pre-state. Consecutive all-failed frames have
a bounded 300-frame grace budget (about 3 minutes at the default cadence); when
it is exhausted, playback stops and keeps the touched scope recoverable.
Explicit Runtime timeout, unavailable, process-exit, or malformed-response
failures are recorded as failed rows and continue with the next frame within
that budget. They may represent a write whose result is unknown, so they never
claim physical verification and Stop/readback remains authoritative. A
cancellation, journal/recovery, validation, session, or unknown/unbound failure
remains terminal. The current POST is never replayed. A browser request with no
HTTP response is treated as a lost connection: the page waits and sends the
next frame so a completed server-side tick does not strand the session. Parsed
HTTP errors remain terminal.
Flow receipts mean that a semantic request was acknowledged. They do not claim
that a physical light has changed. lighting.design.apply is reserved for
low-frequency initialisation and termination because Runtime applies and reads
back its attributes serially. High-frequency frames use a capability-gated
single-target Flow call or a bounded twelve-worker compatibility pool over the
complete selected set.
When protected Runtime metadata includes a household gateway, pass
controlMode=local-preferred with either gatewayIp or a validated
lanEndpoint. The service strips ambient LAN settings, then supplies only the
explicit context to the Runtime child. local-preferred lets yeelight-home
fall back according to its own policy; local-only is available when cloud
fallback is not acceptable. LAN endpoints must be local/private or link-local
HTTP(S) /mcp URLs without credentials, queries, or fragments. The browser
never selects or receives this endpoint.
Boundaries
- The service listens only on
127.0.0.1, rejects foreign Host/Origin and cross-site POSTs, uses a short-lived page proof, and returns no CORS grant. - Requests are JSON with bounded size and closed fields/enums. User text is
rendered as text, not executable markup. The parent page has a fixed local
script policy; YouTube is limited to a validated in-page iframe and explicit
browser audio capture. Apple Music opens only from a server-projected,
validated
music.apple.comlink in a user-triggered top-level window. The page requests an initial companion-anchored position, while the browser controls any later native window movement, popup sizing, and audio-sharing permission; the Skill never closes a playing cross-origin window to fake repositioning. - Artwork is fetched only from the server's signed opaque handle and exact HTTPS host allowlist. Redirects, private DNS, oversized responses, and raw upstream errors are rejected.
- No QR login, token profile, raw URL, raw header, MCP session, or arbitrary Runtime intent is part of this Skill. A YouTube Data API key is optional and remains process-local. Without a key, the service uses a fixed server-side YouTube Web JSON search fallback; it never scrapes HTML or sends browser cookies/login state. This fallback is an undocumented public web contract and may return no candidates when YouTube changes or blocks it; local audio remains the reliable fallback.
- The Skill does not call OpenAI, Anthropic, Gemini, or the host model from the
browser. AI capability comes from the host AI reading this Skill, classifying
the user's natural language, running the local wrapper, opening the page, and
presenting Runtime results. The page itself uses browser
AudioContext, fixed catalog adapters, deterministic hue/lyric rules, and loopback HTTP only. Deterministic catalog fixtures exist only in tests and are never loaded by the production launcher. - Live context is host-owned and explicit:
profile,region,house-id, and optional local gateway fields are passed only to the local Runtime process. They are never accepted from browser JSON, returned as page data, or placed in logs. Live startup rejectsdev, missing context, ambient API-base overrides, and gateway endpoints that are not local/private/mcpURLs. - The initial defensive limit is 160 selected lights per screening. It is a resource guard, not a topology or room assumption; 161 is rejected before any Runtime call. Every frame still covers all selected lights under this limit.
Real-light validation gate
The first physical run is a separate, bounded verification rather than an ordinary high-frequency screening. The host must:
- Run read-only
auth status,home list,entity.list, and the dedicated exact-targetstate.batch.querypreflight through the protected Runtime context. Use single-target reads only for failed/uncertain write readback or explicit recovery; the final validation restore uses one batch read. Do not change the active profile or write credentials. - Bind the exact upcoming screening scope by display name/room and Runtime evidence (18 lights here, or any other explicitly selected N). Choose four distinct lights from that bound scope as the physical sample. The number four is a test scope, not a product limit; never infer “the first four devices” when discovery returns more.
- Record a redacted, per-target pre-state containing power, brightness, color, color temperature, online status, and capability evidence. If any field is missing or a target is not independently confirmed as a light, stop.
- Ask for the explicit conversational confirmation
“Confirm execution of the above short-duration verification of 4 lights”. Before asking, use the host-only
validation wrapper with the four opaque handles and keep its short-lived
ASCII grant in host context. Only after the user confirms, call the host-only
run action with that grant. The server caps brightness at 10% and runs one
bounded validation sequence per target in series. A sequence may use
separate semantic brightness and power writes; it never replays the design
step, and a power-only fallback is limited to one additional direct power
call after trusted readback. Successful receipts skip an immediate readback;
failed or uncertain writes still pay for a single-target readback. After a
verified fade/off receipt, restore writes the recorded state directly without
a pre-restore state query, then one final batch read confirms the result. The
host wrapper receives both the four sample handles and the full
scopeHandlesset; success authorizes only that exact set. The conversational Chinese phrase is checked by the host wrapper and is never copied into a request header. Do not use the ordinary high-frequency page Start control for this first run; live Start stays blocked until validation succeeds. - Report each target's write receipt, readback, restore result, and any
partial/uncertainstate. A failed restore is not success; stop further writes and keep the remaining recovery list visible to the host. A formally bound partial Runtime receipt is accepted only after exact verified readback; a power-only mismatch may use one direct power fallback only after the phase brightness is confirmed, and never retries a design write.
See references/lighting-model.md, references/runtime-execution.md, and
references/setup.md and references/live-validation.md for the detailed
contracts.