@tomorrowos/sdk 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,348 @@
1
+ # Build Guardrails for TomorrowOS CMS Projects
2
+
3
+ > **These are not suggestions. Every CMS generated from `@tomorrowos/sdk` must include each component in this document. The SDK assumes they exist. If any are missing, pairing will fail, content will not deploy, and the LLM that generated the CMS has produced broken code.**
4
+
5
+ This document exists because an LLM given a blank canvas will produce a CMS that looks right and doesn't work. This file is the fence that stops that. Read each section. Include each component. Do not skip any.
6
+
7
+ ---
8
+
9
+ ## 1. Server-side WebSocket host
10
+
11
+ **File:** `server.ts` (or `server.js`)
12
+
13
+ **Must contain:**
14
+
15
+ ```typescript
16
+ import { TomorrowOS } from '@tomorrowos/sdk';
17
+ import brand from './brand.json';
18
+
19
+ const tomorrowos = new TomorrowOS({ brand });
20
+
21
+ tomorrowos.listen({
22
+ port: Number(process.env.PORT) || 3000,
23
+ host: '0.0.0.0',
24
+ });
25
+ ```
26
+
27
+ **Why this matters:**
28
+
29
+ - `TomorrowOS` is a class from the SDK that wraps WebSocket handling, pairing, command envelopes, lifecycle tracking, and event publishing. Do not reimplement any of this.
30
+ - `tomorrowos.listen()` binds a WebSocket server that every player connects to. Without this call, no screen can ever pair.
31
+ - `host: '0.0.0.0'` is required for Replit / Claude Code / containerised hosting. Do not use `localhost` or `127.0.0.1` in the starter.
32
+
33
+ **Must not contain:**
34
+
35
+ - Raw `ws` or `socket.io` imports — the SDK handles WebSocket transport
36
+ - Manual pairing code generation — the SDK provides `tomorrowos.pairing.createCode()`
37
+ - Manual JWT signing for device tokens — the SDK mints and verifies these
38
+
39
+ ---
40
+
41
+ ## 2. Pairing page
42
+
43
+ **Route:** `/pair`
44
+
45
+ **Must include:**
46
+
47
+ - An input field accepting a 6-digit numeric code (input pattern `\d{6}`)
48
+ - A submit button that calls `tomorrowos.pairing.verify(code)` via the SDK
49
+ - Success state showing the newly-paired device's name and ID
50
+ - Error state for expired, invalid, or already-paired codes
51
+ - A link back to the main screens list
52
+
53
+ **Example flow:**
54
+
55
+ ```typescript
56
+ import { useTomorrowOS } from '@tomorrowos/sdk/react';
57
+
58
+ function PairPage() {
59
+ const { pairing } = useTomorrowOS();
60
+ const [code, setCode] = useState('');
61
+ const [result, setResult] = useState(null);
62
+
63
+ async function handleSubmit() {
64
+ try {
65
+ const device = await pairing.verify(code);
66
+ setResult({ success: true, device });
67
+ } catch (err) {
68
+ setResult({ success: false, error: err.message });
69
+ }
70
+ }
71
+
72
+ return (/* UI here */);
73
+ }
74
+ ```
75
+
76
+ **Why mandatory:**
77
+
78
+ Without a pairing page, a device showing an activation code on its screen has no way to tell the CMS "I am device X, please accept me." The CMS will never gain a paired device. Every CMS needs this page regardless of use case.
79
+
80
+ ---
81
+
82
+ ## 3. Screens list page
83
+
84
+ **Route:** `/` or `/screens`
85
+
86
+ **Must include:**
87
+
88
+ - Live list of paired devices from `tomorrowos.devices.list()`
89
+ - Online / offline indicator per device (driven by `device.heartbeat` events)
90
+ - Last-seen timestamp per device (from heartbeat stream)
91
+ - Device name, model, and platform per row
92
+ - Click-through to `/screens/[deviceId]`
93
+
94
+ **Real-time updates:**
95
+
96
+ The SDK emits `device.online`, `device.offline`, and `device.heartbeat` events. Subscribe to these via `tomorrowos.on('device.online', handler)` to keep the list live without polling.
97
+
98
+ **Why mandatory:**
99
+
100
+ This is the operator's primary workspace. Without it, the CMS has paired devices it cannot show.
101
+
102
+ ---
103
+
104
+ ## 4. Per-device control panel
105
+
106
+ **Route:** `/screens/[deviceId]`
107
+
108
+ **Must include the following controls, wired to SDK methods:**
109
+
110
+ ### Device info panel (top of page)
111
+
112
+ Display output of `tomorrowos.device(deviceId).info.get()`:
113
+
114
+ - Device name
115
+ - Platform and platform version
116
+ - Hardware model and serial
117
+ - Current resolution
118
+ - Paired since date
119
+
120
+ ### Control buttons
121
+
122
+ Each button calls the corresponding SDK method via `tomorrowos.device(deviceId).sendCommand()`:
123
+
124
+ | Label | SDK call | Notes |
125
+ |------------------|--------------------------------------------------|------------------------------------------------|
126
+ | Reboot | `device.power.reboot()` | Confirm dialog before sending |
127
+ | Deploy content | Opens URL input → `device.content.setPolicy()` | Wrap URL as single-item policy; see below |
128
+ | Clear content | `device.content.clear()` | Confirm dialog |
129
+ | Screenshot | `device.display.screenshot()` | Only show button if capability supports it |
130
+
131
+ ### Current content indicator
132
+
133
+ Poll `tomorrowos.device(deviceId).content.current()` every 10 seconds, or subscribe to `content.started` / `content.finished` events for push updates.
134
+
135
+ ### Deploy content — correct shape
136
+
137
+ When the user enters a URL to deploy, the SDK call must wrap it as a content policy:
138
+
139
+ ```typescript
140
+ await device.sendCommand('device.content.setPolicy', {
141
+ policy: {
142
+ revision: Date.now(), // monotonic
143
+ playlists: [{
144
+ playlistId: crypto.randomUUID(),
145
+ priority: 10,
146
+ items: [{
147
+ contentId: crypto.randomUUID(),
148
+ url: userProvidedUrl,
149
+ durationSec: 30,
150
+ transition: 'cut',
151
+ }],
152
+ loop: true,
153
+ schedule: { startDate: null, endDate: null },
154
+ }],
155
+ priorityResolution: 'highest_wins',
156
+ },
157
+ });
158
+ ```
159
+
160
+ **Do not call** `device.content.deploy(url)` — this method does not exist in V1. Content is declarative policy, not imperative deploy.
161
+
162
+ **Why mandatory:**
163
+
164
+ Without the control panel, the CMS is a pretty dashboard with no function. Every TomorrowOS demo, test, and user showcase depends on this page working.
165
+
166
+ ---
167
+
168
+ ## 5. Brand application via `brand.json`
169
+
170
+ **Brand data MUST flow through `brand.json`**, never hard-coded in components.
171
+
172
+ ### Read brand at startup
173
+
174
+ ```typescript
175
+ import brand from './brand.json';
176
+ ```
177
+
178
+ ### Apply brand via CSS custom properties
179
+
180
+ Generate a `brand.css` file (or equivalent) on server start that derives CSS variables from `brand.json`:
181
+
182
+ ```css
183
+ :root {
184
+ --color-primary: #FF8A3D; /* from brand.primaryColor */
185
+ --color-background: #FAFAF9; /* from brand.backgroundColor */
186
+ --color-text: #0A0908; /* from brand.textColor */
187
+ --font-sans: 'Inter', sans-serif; /* from brand.fontFamily */
188
+ }
189
+ ```
190
+
191
+ ### Use the `useBrand()` hook in React components
192
+
193
+ ```typescript
194
+ import { useBrand } from '@tomorrowos/sdk/react';
195
+
196
+ function Header() {
197
+ const brand = useBrand();
198
+ return (
199
+ <header>
200
+ <img src={brand.logoPath} alt={brand.name} />
201
+ <h1>{brand.name} Signage</h1>
202
+ </header>
203
+ );
204
+ }
205
+ ```
206
+
207
+ **Must not:**
208
+
209
+ - Hard-code the brand name, logo path, or colours in any component
210
+ - Use inline `style={{ color: '#FF8A3D' }}` — always use the CSS variable
211
+ - Reference the TomorrowOS amber or the starter's placeholder colour in production components
212
+
213
+ **Why mandatory:**
214
+
215
+ Hard-coded branding means the user has to edit every file to change a colour. `brand.json`-driven branding means one file change propagates everywhere — including into the device's activation screen (baked into the `.wgt` at build time by `tomorrowos build`).
216
+
217
+ ---
218
+
219
+ ## 6. Command envelope
220
+
221
+ **Every mutating command must go through `sdk.sendCommand(method, params)`.**
222
+
223
+ The SDK wraps your call with the standard command envelope:
224
+
225
+ ```json
226
+ {
227
+ "commandId": "uuid-generated",
228
+ "method": "device.power.reboot",
229
+ "params": {},
230
+ "issuedAt": "2026-05-14T10:30:00Z",
231
+ "ttlSec": 300,
232
+ "idempotencyKey": "uuid-generated",
233
+ "requiresAck": true
234
+ }
235
+ ```
236
+
237
+ **Must not:**
238
+
239
+ - Write to the WebSocket directly. If you find yourself typing `ws.send()` or `socket.emit()` in your CMS code, stop — you are bypassing the SDK and your commands will be rejected by the player.
240
+ - Construct your own commandId or idempotency logic. The SDK does this.
241
+ - Assume a command is complete when `sendCommand` resolves. `sendCommand` returns when the command is `accepted`. Listen for `command.verified` or `command.failed` events to know the final outcome.
242
+
243
+ ---
244
+
245
+ ## 7. No platform-specific branching in CMS code
246
+
247
+ The CMS must be platform-agnostic. The SDK + capability declarations abstract every platform difference.
248
+
249
+ **Do not write:**
250
+
251
+ ```typescript
252
+ // WRONG
253
+ if (device.platform === 'tizen') {
254
+ await device.sendCommand('device.power.reboot');
255
+ } else if (device.platform === 'brightsign') {
256
+ await device.sendCommand('device.power.restart');
257
+ }
258
+ ```
259
+
260
+ **Write:**
261
+
262
+ ```typescript
263
+ // CORRECT
264
+ await device.sendCommand('device.power.reboot');
265
+ ```
266
+
267
+ If the device doesn't support reboot, the SDK returns a `CAPABILITY_NOT_SUPPORTED` error. Handle that error — do not pre-branch on platform.
268
+
269
+ **Do not:**
270
+
271
+ - Check `device.platform` or `device.class` to change which methods you call
272
+ - Call raw native APIs (there are none exposed to the CMS — only SDK methods)
273
+ - Import any `@tomorrowos/player-*` packages into the CMS — those are player-side, not CMS-side
274
+
275
+ ---
276
+
277
+ ## 8. Event subscription for real-time UI
278
+
279
+ Subscribe to events for live dashboards. Do not poll unless explicitly required.
280
+
281
+ ```typescript
282
+ tomorrowos.on('device.online', (event) => {
283
+ // update UI
284
+ });
285
+
286
+ tomorrowos.on('device.heartbeat', (event) => {
287
+ // update last-seen
288
+ });
289
+
290
+ tomorrowos.on('command.verified', (event) => {
291
+ // mark command as successful in UI
292
+ });
293
+
294
+ tomorrowos.on('command.failed', (event) => {
295
+ // show error to operator
296
+ });
297
+
298
+ tomorrowos.on('content.error', (event) => {
299
+ // alert operator of content playback failure
300
+ });
301
+ ```
302
+
303
+ ---
304
+
305
+ ## Optional but strongly recommended
306
+
307
+ The following are not required but improve the CMS substantially. Include them if the user is at expectedScreens > 10 or if the use case warrants it.
308
+
309
+ ### Build player button
310
+
311
+ A button in the CMS admin area that runs `npx tomorrowos build --platform tizen` (or other) and links the user to the generated `.wgt` file in `dist/`. Saves the user a terminal step.
312
+
313
+ ### Settings page for `brand.json`
314
+
315
+ A `/settings` page that lets the user edit brand fields in-UI and writes back to `brand.json`. On save, the CMS reloads the brand.
316
+
317
+ ### Fleet broadcast
318
+
319
+ A bulk-command UI that uses `tomorrowos.fleet.broadcast(method, params, targets)` to send the same command to many devices at once. Essential at > 50 screens.
320
+
321
+ ### Proof-of-play log
322
+
323
+ A `/playback` page showing which content played on which screen at what time. Reads from `tomorrowos.device(id).proofOfPlay.getLog()`.
324
+
325
+ ---
326
+
327
+ ## Summary — the "did I build this right?" checklist
328
+
329
+ Before shipping a generated CMS, verify:
330
+
331
+ - `server.ts` imports `TomorrowOS` from `@tomorrowos/sdk` and calls `listen()` — ☐
332
+ - `/pair` page exists and calls `pairing.verify(code)` — ☐
333
+ - `/screens` page shows live device list with online/offline status — ☐
334
+ - `/screens/[id]` page has reboot, deploy, clear, current-content at minimum — ☐
335
+ - Deploy uses `device.content.setPolicy` with a wrapped policy object, not `content.deploy` — ☐
336
+ - `brand.json` exists at project root and drives all UI branding via CSS variables — ☐
337
+ - No hard-coded colours, logos, or brand names in components — ☐
338
+ - All mutating commands use `sdk.sendCommand()` — no raw WebSocket writes — ☐
339
+ - No platform branching in CMS code — ☐
340
+ - Event subscriptions for `device.online`, `device.offline`, `device.heartbeat` active — ☐
341
+
342
+ If every box is checked, the CMS will work on first connection. If any box is unchecked, pairing or control will fail in ways the user cannot diagnose.
343
+
344
+ ---
345
+
346
+ ## Protocol compliance
347
+
348
+ This guardrails document is version `1.0`, aligned with TomorrowOS wire protocol `1.0`. Future SDK versions may add or strengthen guardrails; they will not relax them without a major-version bump.
package/LLM_PROMPT.md ADDED
@@ -0,0 +1,171 @@
1
+ # Elicitation Protocol for TomorrowOS CMS Projects
2
+
3
+ > **This file is the contract between TomorrowOS and any LLM generating code using this package. You must follow it literally. Do not skip questions, do not infer answers, do not start writing code until the protocol is complete.**
4
+
5
+ You are generating a digital signage CMS using `@tomorrowos/sdk`. The user has asked you to build something like "a CMS for my restaurant" or "a signage dashboard for my retail network". Before you write any code, you must gather five pieces of information from the user.
6
+
7
+ Ask each question in order. If the user answers multiple at once, record those and skip ahead. If the user hasn't given you an answer to a specific field, do not invent one — ask the question explicitly.
8
+
9
+ ---
10
+
11
+ ## Question 1 — Target platform
12
+
13
+ **Ask exactly:**
14
+
15
+ > "Which screen platform will you manage? TomorrowOS V1 fully supports Samsung Tizen (commercial signage displays) and BrightSign (media players). Which would you like to target?"
16
+
17
+ **Valid answers:** `tizen`, `brightsign`, or `both`
18
+
19
+ **If the user names a platform not in V1** (webOS, Android, ChromeOS, Windows), respond:
20
+
21
+ > "That platform is planned for a later release. For V1, the supported options are Tizen and BrightSign. Which would you like to use for now?"
22
+
23
+ **Store answer as:** `targetPlatforms` array in `brand.json`
24
+
25
+ ---
26
+
27
+ ## Question 2 — Brand identity
28
+
29
+ **Ask exactly:**
30
+
31
+ > "What's the name of the product or company this CMS is for? I'll also need:
32
+ >
33
+ > - A logo file (SVG preferred, PNG acceptable — or a URL I can fetch)
34
+ > - A primary brand colour (hex format, e.g. `#FF8A3D`)
35
+ > - Optional: a secondary/accent colour"
36
+
37
+ **Required fields:**
38
+
39
+ - `brand.name` — string
40
+ - `brand.primaryColor` — hex string matching `^#[0-9A-Fa-f]{6}$`
41
+ - `brand.logoPath` — relative path to logo file the user will save to the project
42
+
43
+ **If the user does not provide a logo**, proceed with a placeholder comment in the generated code noting that the logo must be added before production use. Do not fabricate a logo.
44
+
45
+ **If the user provides a brand name but no colour**, ask:
46
+
47
+ > "What's the primary colour for your brand? I need it as a hex code like `#FF8A3D`."
48
+
49
+ Store answers in `brand.json`.
50
+
51
+ ---
52
+
53
+ ## Question 3 — Use case
54
+
55
+ **Ask exactly:**
56
+
57
+ > "What's the primary use case for this CMS? For example: restaurant menu boards, retail promotions, corporate lobby displays, hospital wayfinding, transit information."
58
+
59
+ **Use the answer to:**
60
+
61
+ - Tailor the example content shown in the starter template (e.g. menu items vs retail promo templates)
62
+ - Set the dashboard's default greeting and page titles
63
+ - Suggest sensible default playlist names
64
+
65
+ **Do NOT use the answer to:**
66
+
67
+ - Change the SDK method signatures
68
+ - Add or remove required components from `BUILD_GUARDRAILS.md`
69
+ - Skip any mandatory pages
70
+
71
+ The SDK contract is identical regardless of use case. Use case only affects content and copy.
72
+
73
+ **Store answer as:** `cms.useCase` in `brand.json` (one of: `restaurant`, `retail`, `corporate`, `wayfinding`, `transit`, `other`)
74
+
75
+ ---
76
+
77
+ ## Question 4 — Hosting target
78
+
79
+ **Ask exactly:**
80
+
81
+ > "Where will the CMS server run? Options:
82
+ >
83
+ > - `here` — you're in Replit, Claude Code, or another hosted AI workspace (I'll configure it automatically)
84
+ > - `vercel` — deploy to Vercel
85
+ > - `railway` — deploy to Railway
86
+ > - `self-hosted` — you'll run it on your own infrastructure"
87
+
88
+ **If the user says `here`:**
89
+
90
+ - Use environment variable `process.env.PORT` for the server port
91
+ - Configure the WebSocket handler to bind to `0.0.0.0`
92
+ - Include a comment in `server.ts` noting that Replit/Claude Code handles the hosting and HTTPS termination
93
+ - Do NOT commit any secrets or keys to the generated code
94
+
95
+ **If the user says `vercel`:**
96
+
97
+ - Note that Vercel free tier has WebSocket limitations; recommend Railway for production
98
+ - Generate `vercel.json` with appropriate WebSocket routing
99
+
100
+ **If the user says `railway`:**
101
+
102
+ - Generate `railway.toml` with port configuration
103
+ - Include Dockerfile if appropriate
104
+
105
+ **Store answer as:** `cms.hostingTarget` in `brand.json`
106
+
107
+ ---
108
+
109
+ ## Question 5 — Expected number of screens
110
+
111
+ **Ask exactly:**
112
+
113
+ > "How many screens will you manage initially? (1-10 is fine for testing. For larger deployments I'll add pagination and grouping features.)"
114
+
115
+ **If answer is 1-10:** generate simple list views without pagination.
116
+
117
+ **If answer is 11-100:** include pagination on the screens list page (page size 20).
118
+
119
+ **If answer is 100+:** include pagination, grouping, and tag filtering on the screens list. Note to user that fleet-level features (bulk commands, group broadcast) are recommended.
120
+
121
+ **Store answer as:** `cms.expectedScreens` in `brand.json`
122
+
123
+ ---
124
+
125
+ ## After all five answers collected
126
+
127
+ Follow this sequence strictly:
128
+
129
+ 1. **Write `brand.json`** at the project root using the answers. Validate against `brand.schema.json`.
130
+ 2. **Run `npx tomorrowos init [dir]`** or copy `templates/cms-starter/` from the SDK package into the user's project as the seed.
131
+ 3. **Customise the starter:**
132
+ - Replace placeholder brand name, colour, logo references
133
+ - Rename example content to match the use case
134
+ - Remove any platform-specific notes for platforms the user didn't select
135
+ 4. **Read `BUILD_GUARDRAILS.md`** and verify every mandatory component is present in the generated code. Do not skip any.
136
+ 5. **Run `npm install`** and confirm dependencies resolve.
137
+ 6. **Start the dev server** (`npm run dev`) and confirm it runs without errors.
138
+ 7. **Show the user `PLAYER_INSTALL.md`** and explain what to do next to get a screen paired and controllable.
139
+
140
+ ---
141
+
142
+ ## What not to ask
143
+
144
+ Do not ask the user:
145
+
146
+ - How WebSocket connections should be handled (the SDK handles this)
147
+ - How to implement pairing (the SDK handles this)
148
+ - How to format command messages (the SDK handles this)
149
+ - Which WebSocket library to use (the SDK bundles this)
150
+ - What TCP port to use (use `process.env.PORT` or `3000`)
151
+ - Whether to use TypeScript or JavaScript (default to TypeScript; offer JS only if user explicitly asks)
152
+
153
+ These are implementation details the SDK controls. The user cares about their brand, their platform, their use case. Everything else is the SDK's job.
154
+
155
+ ---
156
+
157
+ ## When to re-prompt
158
+
159
+ If the user's initial prompt already contains answers to some questions (e.g. "build me a CMS for my Acme Burger restaurant menu boards on Samsung Tizen"), record those answers and only ask the remaining questions. Do not re-ask what the user has already told you.
160
+
161
+ If the user says "just build it, don't ask me questions," respond:
162
+
163
+ > "I'd love to, but I need five quick answers to make sure the CMS you get actually works with your screens and looks like your brand. It'll take about sixty seconds. Ready?"
164
+
165
+ Do not proceed without the answers. The SDK will not function without at minimum: `targetPlatforms`, `brand.name`, `brand.primaryColor`, and `cms.hostingTarget`.
166
+
167
+ ---
168
+
169
+ ## Protocol version
170
+
171
+ This elicitation protocol is version `1.0`, aligned with TomorrowOS wire protocol `1.0`. Future SDK versions may add questions; they will not remove existing ones without a major-version bump.