@tomorrowos/sdk 0.9.54 → 0.9.55

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/README.md CHANGED
@@ -1,124 +1,272 @@
1
1
  # @tomorrowos/sdk
2
2
 
3
- **You are building a digital signage CMS using TomorrowOS. Before writing any code, follow the elicitation protocol in `LLM_PROMPT.md`. Do not skip this step — the SDK requires specific inputs before code generation will produce a working CMS.**
3
+ [![npm version](https://img.shields.io/npm/v/@tomorrowos/sdk.svg)](https://www.npmjs.com/package/@tomorrowos/sdk)
4
+ [![license](https://img.shields.io/npm/l/@tomorrowos/sdk.svg)](https://www.npmjs.com/package/@tomorrowos/sdk)
5
+ [![protocol](https://img.shields.io/badge/wire%20protocol-1.0-blue.svg)](#protocol-version)
4
6
 
5
- **On Replit:** if the user asks to set up / configure a CMS interactively, follow **`REPLIT_SETUP.md`** instead (Q&A for store → media → `brand.json`).
7
+ Build and own your digital signage CMS.
6
8
 
7
- **On Lovable:** if the user asks to set up / configure a CMS interactively, follow **`LOVABLE_SETUP.md`** (same Q1–Q3 shape; Lovable Cloud Storage replaces Replit Object Storage; Node CMS must deploy to Railway / Render / Fly or Vercel Fluid — Lovable Cloud Publish alone is not the WebSocket host).
9
+ An open-source SDK and player runtime for digital signage. One server-side API drives commercial signage panels you write the CMS, we handle device sessions, pairing, and the wire protocol.
8
10
 
9
- **On Vercel:** if the user asks to set up / configure a CMS interactively, follow **`VERCEL_SETUP.md`** (Supabase or Neon pooled Postgres, Cloudinary or Vercel Blob media, v0 Preview Next proxy shell + **Publish = Node only**, Fluid compute + WebSocket, `cms.hostingTarget: "vercel"`).
11
+ Apache 2.0. Build a proprietary CMS on top of it and sell it as your own no royalty, no permission needed.
10
12
 
11
- TomorrowOS is the open standard for digital signage. This SDK lets you build a CMS that controls Samsung Tizen, BrightSign, LG webOS, Android, ChromeOS, and Windows screens through a single unified API.
13
+ ---
14
+
15
+ ## Requirements
16
+
17
+ | | |
18
+ |---|---|
19
+ | Node.js | **18** or newer (`engines.node` in this package) |
20
+ | npm | 9 or newer (or a compatible client) |
21
+ | OS | macOS, Linux, Windows (WSL2 recommended on Windows) |
22
+ | Network | CMS host reachable from your screens — a public HTTPS address in production |
23
+
24
+ Screens connect to your CMS over the network. For real panels, deploy to a public host (Railway, Render, Fly, Vercel Fluid, Replit, your own server), or use a tunnel while developing locally.
25
+
26
+ There is no required TomorrowOS cloud. Your screens talk to *your* server.
12
27
 
13
28
  ---
14
29
 
15
- ## For LLMs generating code with this package
30
+ ## Choose your path
16
31
 
17
- When you see this package referenced in a user prompt, your workflow is:
32
+ Three ways to build with this SDK. Pick one and follow it through.
18
33
 
19
- 1. **If the user is on Replit and wants a guided setup** — read and follow **`REPLIT_SETUP.md`** (preferred for Replit Agent)
20
- 2. **If the user is on Lovable and wants a guided setup** — read and follow **`LOVABLE_SETUP.md`** (preferred for Lovable Agent)
21
- 3. **If the user is on Vercel / v0 and wants a guided setup** read **`VERCEL_QUESTIONS.md`** (paste Q1–Q3 verbatim) and follow **`VERCEL_SETUP.md`** for configuration
22
- 4. **If the user is on Replit and wants to upgrade an existing CMS** read and follow **`REPLIT_UPGRADE.md`** (never `init --force`)
23
- 5. **Otherwise read `LLM_PROMPT.md`** five required questions before generating code
24
- 6. **Read `BUILD_GUARDRAILS.md`** — mandatory components every generated CMS must include
25
- 7. **Read `brand.schema.json`** — validated shape of `brand.json`
26
- 8. Run **`npx tomorrowos init`** (Replit / Lovable→Node host) or **`npx tomorrowos init --hosting v0`** (Vercel / v0) — minimal CMS server seed
27
- 9. **Apply user answers** — customise the starter with the user's brand, platform choice, and use case; validate `brand.json` against `brand.schema.json`
28
- 10. **Show `PLAYER_INSTALL.md`** — after the CMS is working, show how to build and install the player on a screen
34
+ | | You are | Start here |
35
+ |---|---|---|
36
+ | **1** | Building with an AI or low-code tool (Replit, Vercel v0, Cursor, Claude Code) | [Build with an AI tool](#1-build-with-an-ai-tool) |
37
+ | **2** | Writing the CMS yourself | [Build it yourself](#2-build-it-yourself) |
38
+ | **3** | Adding screen management to an existing SaaS or web app | [Add to an existing app](#3-add-to-an-existing-app) |
29
39
 
30
- You must not write WebSocket transport, pairing wire format, or platform-specific player bridge code in the CMS the SDK handles server-side device sessions and HTTP helpers. You customise UI, branding, and how you call the SDK.
40
+ All three end in the same place: a running CMS with a paired screen. From there, go to [Connect a screen](#connect-a-screen).
31
41
 
32
42
  ---
33
43
 
34
- ## For human developers
44
+ ## 1. Build with an AI tool
45
+
46
+ The SDK ships an elicitation protocol so an agent asks the right questions before it writes anything. Skipping it reliably produces a CMS that doesn't run.
47
+
48
+ ### Replit
49
+
50
+ Paste into Replit Agent:
51
+
52
+ ```text
53
+ Follow @tomorrowos/sdk REPLIT_SETUP.md and set up my TomorrowOS CMS.
54
+ Ask me the questions in order. Do not skip steps.
55
+ ```
56
+
57
+ On Replit, Supabase must use the `SUPABASE_URL` secret. `DATABASE_URL` is reserved by Replit for its built-in PostgreSQL.
58
+
59
+ Upgrading an existing Replit CMS instead:
60
+
61
+ ```text
62
+ Follow @tomorrowos/sdk REPLIT_UPGRADE.md to upgrade my CMS with the latest SDK.
63
+ ```
64
+
65
+ That protocol diffs and merges against the current templates. It must never run `init --force` or delete `data/` or `public/uploads/`.
66
+
67
+ ### Vercel / v0
68
+
69
+ Paste into Vercel Agent:
70
+
71
+ ```text
72
+ Follow @tomorrowos/sdk VERCEL_SETUP.md and set up my TomorrowOS CMS.
73
+ If any step contradicts Vercel, adapt to settings Vercel can accept.
74
+ ```
75
+
76
+ Agents must paste questions from [`VERCEL_QUESTIONS.md`](./VERCEL_QUESTIONS.md) verbatim. WebSockets require Fluid compute. The agent sets `cms.hostingTarget: "vercel"`.
77
+
78
+ ### Any other agent
79
+
80
+ Point it at [`LLM_PROMPT.md`](./LLM_PROMPT.md) — five required questions before code generation — then [`BUILD_GUARDRAILS.md`](./BUILD_GUARDRAILS.md) for the components every generated CMS must include.
81
+
82
+ <details>
83
+ <summary><strong>Agent workflow (for the agent, not you)</strong></summary>
84
+
85
+ 1. If on Replit / Vercel with a guided setup request — follow that host’s setup doc first (`REPLIT_SETUP.md`, or `VERCEL_SETUP.md` + `VERCEL_QUESTIONS.md`).
86
+ 2. Otherwise read `LLM_PROMPT.md` and ask the five questions.
87
+ 3. Read `BUILD_GUARDRAILS.md` for mandatory components.
88
+ 4. Read `brand.schema.json` for the validated shape of `brand.json`.
89
+ 5. Run `npx tomorrowos init` (or `npx tomorrowos init ./my-cms --hosting v0` for Vercel / v0).
90
+ 6. Apply the user’s answers — brand, platform, use case. Validate `brand.json`.
91
+ 7. Point the user at `PLAYER_INSTALL.md` (and the Control Panel **Download Players** UI) once the CMS is running.
35
92
 
36
- Install:
93
+ Do not write WebSocket transport, pairing wire format, or platform-specific player bridge code. The SDK handles server-side device sessions and HTTP helpers. Customise the UI, the branding, and how you call the SDK.
94
+
95
+ </details>
96
+
97
+ **Next:** [Connect a screen](#connect-a-screen).
98
+
99
+ ---
100
+
101
+ ## 2. Build it yourself
37
102
 
38
103
  ```bash
39
104
  npm install @tomorrowos/sdk
40
105
  ```
41
106
 
42
- Scaffold a new CMS project:
107
+ Scaffold, install, run:
43
108
 
44
109
  ```bash
45
- # Replit / Railway / self-host (default)
46
- npx tomorrowos init
110
+ # Replit / Railway / self-host (default template)
111
+ npx tomorrowos init my-cms
47
112
 
48
- # Vercel / v0 Publish (cms-panel + vercel.json + optional Next Preview)
49
- npx tomorrowos init --hosting v0
113
+ # Vercel / v0 Publish (cms-panel + vercel.json + optional Next preview)
114
+ npx tomorrowos init my-cms --hosting v0
50
115
 
51
- cd my-tomorrowos-cms
116
+ cd my-cms
52
117
  npm install
53
118
  npm run dev
54
119
  ```
55
120
 
56
- `tomorrowos init` creates `data/tomorrowos.db` with the TomorrowOS SQLite
57
- schema. The starter server uses that SQLite database by default, so pairings,
58
- playlists, and device assignments survive normal server restarts.
121
+ `init` creates `data/tomorrowos.db` with the TomorrowOS SQLite schema. The starter server uses it by default, so pairings, playlists, and device assignments survive restarts.
59
122
 
60
- ### Replit guided setup
123
+ You now have a CMS serving:
61
124
 
62
- In Replit Agent, paste:
125
+ - pairing HTTP helpers (`POST /pairing/verify`, `POST /pairing/unpair`)
126
+ - `GET /brand.json` for player branding
127
+ - a WebSocket device channel
128
+ - media upload helpers (`/media/upload`, and Cloudinary direct upload when configured)
129
+ - `GET /players/brightsign.zip` — BrightSign package with `config.js` `cmsEndpoint` set to this CMS origin
130
+ - a minimal admin panel (static UI under `staticRoot`)
63
131
 
64
- ```text
65
- Follow @tomorrowos/sdk REPLIT_SETUP.md and set up my TomorrowOS CMS.
66
- Ask me the questions in order. Do not skip steps.
67
- Q1 database order: Supabase (Recommended), then Built-in Replit PostgreSQL, then SQLite.
68
- If Supabase: SUPABASE_URL Secrets input immediately after choice (once only).
69
- If Cloudinary: all CLOUDINARY_* Secrets in one multi-field dialog immediately after choice.
70
- Q3: one stacked dialog with all seven branding fields (optional; blanks keep brand.json defaults).
71
- ```
132
+ Deploy it to a public HTTPS address before pairing a production screen. While developing locally, a tunnel is the fastest way to get one.
133
+
134
+ **Next:** [Connect a screen](#connect-a-screen).
72
135
 
73
- That wizard asks database choice (**Supabase recommended first**), prefers Cloudinary for media, and collects optional branding in **one multi-field dialog**. On Replit, Supabase must use Secret
74
- **`SUPABASE_URL`** (not the reserved `DATABASE_URL` — that is for built-in Replit PostgreSQL).
136
+ ---
75
137
 
76
- ### Lovable guided setup
138
+ ## 3. Add to an existing app
77
139
 
78
- In Lovable Agent, paste:
140
+ If you already have a SaaS or web app and want screen management as a feature inside it, you don't need the starter. Import the SDK into your existing server.
79
141
 
80
- ```text
81
- Follow @tomorrowos/sdk LOVABLE_SETUP.md and set up my TomorrowOS CMS.
82
- Ask only the questions listed in LOVABLE_SETUP.md, in order. Do not use LLM_PROMPT.md.
83
- Do not build a Vite/React mock of TomorrowOS — use @tomorrowos/sdk cms-starter (Node + WebSocket).
84
- Question 3 only updates brand.json.
85
- After setup questions (including Cloudinary Secrets if chosen), configure and deploy — minimal verification only.
86
- Never test Supabase connection — save SUPABASE_URL and move on. No Supabase or WebSocket tests.
87
- Do not skip steps.
142
+ ```bash
143
+ npm install @tomorrowos/sdk
88
144
  ```
89
145
 
90
- That wizard mirrors Replit Q1–Q3. Media alternative to Cloudinary is **Lovable Cloud Storage**
91
- (public bucket `tomorrowos-uploads`) instead of Replit Object Storage. The TV-facing CMS
92
- must still run on a **Node host** (Railway / Render / Fly, or Vercel Fluid via `VERCEL_SETUP.md`) —
93
- Lovable Cloud Publish alone is not a WebSocket CMS.
146
+ ```ts
147
+ import { createTomorrowOSStore, TomorrowOS } from "@tomorrowos/sdk";
94
148
 
95
- ### Vercel guided setup
149
+ const store = createTomorrowOSStore({
150
+ // Prefer SUPABASE_URL on Replit; DATABASE_URL elsewhere / fallback
151
+ databaseUrl: process.env.SUPABASE_URL || process.env.DATABASE_URL
152
+ });
96
153
 
97
- In Vercel Agent (or any AI setup prompt), paste:
154
+ const tomorrowos = new TomorrowOS({ brand, store });
98
155
 
99
- ```text
100
- Follow @tomorrowos/sdk VERCEL_SETUP.md and set up my TomorrowOS CMS.
101
- IF any step contradicts Vercel, adapt to settings that Vercel can accept.
156
+ const server = tomorrowos.listen({
157
+ port: Number(process.env.PORT) || 3000,
158
+ host: "0.0.0.0",
159
+ staticRoot: "./public" // optional Control Panel + uploads
160
+ });
102
161
  ```
103
162
 
104
- That wizard asks: database (**Supabase** **Neon** SQLite demo), media (**Cloudinary** **Vercel Blob** local),
105
- branding (manual fields or **website URL → `brand.json` only**, no login unless asked). Preview may use a thin **Next.js reverse proxy**;
106
- **Publish stays a Fluid Vercel Function** (`api/index.ts` + `export default server`, WebSockets per Vercel docs). Cloudinary uses **one Env popup** (all fields).
107
- Neon/Supabase: agent auto-sets `TOMORROWOS_STORE` + `DATABASE_SSL`. Sets `cms.hostingTarget` to `"vercel"`.
163
+ What you keep from your app: your auth, your users, your tenancy model, your UI. What the SDK adds: device sessions, the pairing handshake, playlist/policy delivery, and optional static CMS helpers.
108
164
 
109
- ### Replit upgrade (existing CMS)
165
+ Points to plan for:
110
166
 
111
- In Replit Agent, paste:
167
+ - **Multi-tenancy.** Devices belong to whatever tenant model you already have. The SDK does not impose one — map device IDs to your own tenant records.
168
+ - **Auth.** Pairing is a device-side flow. Your admin routes stay behind your existing auth.
169
+ - **Store.** Use `createTomorrowOSStore` / `TOMORROWOS_STORE` for SQLite, Postgres, or Supabase, or pass a custom `TomorrowOSStore`.
170
+ - **WebSockets.** Your host must hold long-lived connections. Most serverless platforms terminate idle sockets — check before you commit (Vercel needs Fluid compute).
112
171
 
113
- ```text
114
- Follow @tomorrowos/sdk REPLIT_UPGRADE.md to upgrade my CMS with the latest SDK.
172
+ Read [`BUILD_GUARDRAILS.md`](./BUILD_GUARDRAILS.md) for the components any CMS needs, whether generated or hand-integrated.
173
+
174
+ **Next:** [Connect a screen](#connect-a-screen).
175
+
176
+ ---
177
+
178
+ ## Architecture
179
+
180
+ ```
181
+ ┌─────────────────┐ WebSocket ┌─────────────────┐
182
+ │ Your CMS │ ◄────────────────────────►│ TomorrowOS │
183
+ │ (your server) │ │ Player on │
184
+ │ │ │ screen │
185
+ │ imports │ │ │
186
+ │ @tomorrowos │ │ unified API │
187
+ │ /sdk │ │ across panels │
188
+ └─────────────────┘ └─────────────────┘
115
189
  ```
116
190
 
117
- That protocol installs `@tomorrowos/sdk@latest`, backs up panel/server files,
118
- diffs `cms-starter` templates (merge — never blind overwrite), and must **not**
119
- run `init` / delete `data/` or `public/uploads/`.
191
+ You own the CMS: the UI, the branding, the business logic, the data. The SDK owns the device session, the pairing handshake, and the wire protocol.
192
+
193
+ You should not need to write WebSocket transport, pairing wire format, or platform-specific player bridge code. If you find yourself doing that, open an issue — it means the SDK has a gap.
194
+
195
+ ---
196
+
197
+ ## Connect a screen
198
+
199
+ Your CMS is running. This is what happens next, in order.
200
+
201
+ 1. **Deploy the CMS to a public HTTPS address** (or tunnel for local tests). Screens must reach it from their network.
202
+ 2. **Get a player package** for your platform (see below and [`PLAYER_INSTALL.md`](./PLAYER_INSTALL.md)).
203
+ Note: `npx tomorrowos build --platform …` is still a **placeholder** in this package — packaging lives in the player repos / prebuilt downloads, not in the CLI yet.
204
+ 3. **Install the player on the panel.**
205
+ 4. **Pair.** The player shows a 6-character code. Enter it in the Control Panel. The device appears in your device list.
206
+ 5. **Publish content.** Assign playlists / push a content policy. The screen updates without reloading the app.
207
+
208
+ ### Samsung Tizen
209
+
210
+ Supported target: **Tizen 6.5+** commercial displays (see Control Panel captions and [`PLAYER_INSTALL.md`](./PLAYER_INSTALL.md)).
211
+
212
+ **Fastest path for many setups — URL Launcher / prebuilt package**
213
+
214
+ - From a running Control Panel, use **Download Players → Samsung**, or fetch the published Tizen package from TomorrowOS distribution (`https://tmr.sh/app/tizen/…`).
215
+ - Or host / sideload a `.wgt` as described in [`PLAYER_INSTALL.md`](./PLAYER_INSTALL.md).
216
+
217
+ On the panel, enter the CMS HTTPS URL (or the player package URL your install path requires). The player shows a pairing code.
218
+
219
+ Signed packages need a Samsung distributor certificate for production distribution. Unsigned / URL Launcher paths are fine for testing when your environment allows them.
220
+
221
+ ### BrightSign
222
+
223
+ Supported target: **Series 5 / 6** (as labeled in the Control Panel). Older series may work but should be validated on your hardware before fleet rollout.
224
+
225
+ **Recommended download path**
226
+
227
+ 1. Open your live CMS Control Panel → **Download Players → BrightSign**.
228
+ 2. That hits `GET /players/brightsign.zip` on *this* CMS.
229
+ 3. The SDK fetches the mother zip and rewrites `config.js` so:
230
+
231
+ ```js
232
+ cmsEndpoint: "https://your-cms-host/"
233
+ ```
234
+
235
+ matches the CMS you downloaded from (for example `https://testcms2.replit.app/`).
236
+
237
+ 4. Unzip, copy the **contents** to a microSD card (files at the card root, including `autorun.brs` and `config.js`).
238
+ 5. Insert the card and power on. Pair with the on-screen code.
239
+
240
+ Override the mother zip URL with `TOMORROWOS_BRIGHTSIGN_ZIP_URL` if you host your own package.
241
+
242
+ ### Coming soon
243
+
244
+ Android, LG webOS, and Windows are on the roadmap. They are not validated for production in this SDK release.
245
+
246
+ If you are building for one of those platforms later: build your CMS against the same server API now. Server-side code does not need to change when a new player package lands — only the installable player does.
247
+
248
+ ---
249
+
250
+ ## Platform support
251
+
252
+ | Platform | Status | Install |
253
+ |---|---|---|
254
+ | Samsung Tizen 6.5+ | V1 supported | Control Panel download / `.wgt` / URL Launcher — see `PLAYER_INSTALL.md` |
255
+ | BrightSign Series 5, 6 | V1 supported | Control Panel `GET /players/brightsign.zip` → microSD autorun |
256
+ | BrightSign older series | Validate on hardware | Same autorun zip flow |
257
+ | Android | Roadmap | — |
258
+ | LG webOS | Roadmap | — |
259
+ | Windows | Roadmap | — |
260
+
261
+ Treat a platform as production-ready only after you have validated it on your panels.
262
+
263
+ ---
120
264
 
121
- To switch to Supabase/Postgres manually, edit `.env` / Secrets:
265
+ ## Configuration
266
+
267
+ ### Database
268
+
269
+ SQLite by default (no configuration required for the starter). To switch:
122
270
 
123
271
  ```bash
124
272
  TOMORROWOS_STORE=supabase
@@ -127,11 +275,11 @@ DATABASE_SSL=true
127
275
  # DATABASE_URL=... # optional fallback outside Replit
128
276
  ```
129
277
 
130
- For throwaway demos/tests, set `TOMORROWOS_STORE=memory`. The generated starter
131
- includes its own `README.md` beside `server.ts` with database selection,
132
- migration, and custom `TomorrowOSStore` examples.
278
+ Supported store drivers: `sqlite`, `postgres`, `supabase`, `memory`.
133
279
 
134
- Migrate database records between supported stores:
280
+ Use `memory` for throwaway demos only — nothing survives a restart.
281
+
282
+ ### Migrating between stores
135
283
 
136
284
  ```bash
137
285
  npx tomorrowos migrate \
@@ -141,83 +289,117 @@ npx tomorrowos migrate \
141
289
  --to-database-url "$DATABASE_URL"
142
290
  ```
143
291
 
144
- The migrate command supports `sqlite`, `postgres`, and `supabase` in either
145
- direction. It migrates TomorrowOS database records only; copy `public/uploads`
146
- or object-storage media separately.
292
+ Works in either direction across `sqlite`, `postgres`, and `supabase`. It moves database records only — copy `public/uploads` or object-storage / Cloudinary media separately.
147
293
 
148
- Build a player (placeholder in current SDK — see `PLAYER_INSTALL.md` for platform tooling):
294
+ ### Media
149
295
 
150
- ```bash
151
- npx tomorrowos build --platform tizen
296
+ - **Local / Object Storage:** `POST /media/upload` when `staticRoot` is set (files under `public/uploads`).
297
+ - **Cloudinary:** set `CLOUDINARY_CLOUD_NAME`, `CLOUDINARY_API_KEY`, `CLOUDINARY_API_SECRET` (optional `CLOUDINARY_FOLDER`). The Control Panel uses **browser → Cloudinary** direct upload (`/media/upload-sign` + `/media/register`) when Cloudinary is configured, which avoids host request-body limits on platforms like Replit.
298
+
299
+ Cloudinary account plans still enforce their own max file sizes (for example Free plan video caps).
300
+
301
+ ### Branding
302
+
303
+ Pass a brand object to the constructor (usually loaded from `brand.json`):
304
+
305
+ ```ts
306
+ const tomorrowos = new TomorrowOS({ brand, store });
152
307
  ```
153
308
 
154
- ---
309
+ It is served at `GET /brand.json` on the same host as `listen`. Players fetch it for colours, logos, and display defaults.
310
+
311
+ Validate against [`brand.schema.json`](./brand.schema.json). A complete example is in [`brand.example.json`](./brand.example.json).
155
312
 
156
- ## Quick architecture
313
+ ### Content policy and scheduling
157
314
 
158
315
  ```
159
- ┌─────────────────┐ WebSocket ┌─────────────────┐
160
- │ Your CMS │ ◄────────────────────────►│ TomorrowOS │
161
- │ (your server) │ │ Player on │
162
- │ │ │ screen │
163
- │ imports │ │ │
164
- │ @tomorrowos │ │ unified API │
165
- │ /sdk │ │ across OSes │
166
- └─────────────────┘ └─────────────────┘
316
+ POST /device/{deviceId}/content/set-policy
167
317
  ```
168
318
 
169
- TomorrowOS has no required cloud service. Your CMS talks to your screens over your network.
319
+ ```json
320
+ {
321
+ "policy": {
322
+ "playlists": [],
323
+ "fallback": { "type": "brand" }
324
+ }
325
+ }
326
+ ```
170
327
 
171
- **Player branding:** `GET /brand.json` returns the `brand` object passed to `new TomorrowOS({ brand })` (same host/port as `listen`). TomorrowOS players can fetch it to apply `backgroundColor`, logos, etc., without a separate static file server.
328
+ Each playlist may include optional `schedule`, evaluated in **device local time**. The run window is **one continuous period**:
172
329
 
173
- **Content policy (`device.content.setPolicy`):** POST `/device/{deviceId}/content/set-policy` with `{ "policy": { "playlists": [...], "fallback": { "type": "brand" } } }`. Each playlist may include optional `schedule` (device local time):
330
+ **from** `startDate` + `start` **→ until** `endDate` + `end` (until is exclusive).
174
331
 
175
332
  | Field | Format | Notes |
176
- |-------|--------|--------|
177
- | `startDate` / `endDate` | `YYYY-MM-DD` | Inclusive calendar range; omit either for open-ended |
178
- | `daysOfWeek` | `[0–6]` | `0` = Sunday `6` = Saturday |
179
- | `start` / `end` | `HH:MM` | Daily window; supports overnight (e.g. `22:00`–`06:00`) |
333
+ |---|---|---|
334
+ | `startDate` / `endDate` | `YYYY-MM-DD` | Combined with `start` / `end`; omit bounds for open-ended |
335
+ | `start` / `end` | `HH:mm` | Clock times on those dates |
180
336
 
181
- All provided constraints must match for the playlist to play. See `templates/cms-starter/policy.example.json`.
337
+ This is **not** a daily-repeat schedule inside a date range. Leave schedule empty for always-on. Example shape: [`templates/cms-starter/policy.example.json`](./templates/cms-starter/policy.example.json) (ignore legacy `daysOfWeek` if present).
182
338
 
183
339
  ---
184
340
 
185
- ## Supported platforms (roadmap)
341
+ ## Troubleshooting
342
+
343
+ **Screen won't pair.**
344
+ The player must reach your CMS. Confirm a public HTTPS (or reachable tunnel) URL, DNS, firewall, and that the panel has network access. A CMS only on `localhost` will not pair from a real display.
345
+
346
+ **Paired, but the screen is black (BrightSign).**
347
+ Common causes: media the player cannot decode (codec / profile / unexpected tracks), or HTML video surface alpha / HTML widget timing in `autorun.brs`. Re-encode to a supported profile and confirm `config.js` points at the correct CMS. Check player logs on the device.
348
+
349
+ **Paired, but the screen is black (other).**
350
+ Fetch `GET /brand.json` from the panel’s network. A live WebSocket with a black screen usually means no playable policy item — check published playlists, absolute media URLs, and fallback.
351
+
352
+ **Media 404s on the screen but loads in your browser.**
353
+ Policy URLs must be absolute and reachable from the screen. Relative paths or hosts only visible on your laptop will fail on the panel.
354
+
355
+ **Upload fails with HTTP 413 on Replit (or similar hosts).**
356
+ The file was posted through the CMS host body limit. With Cloudinary configured, use an SDK/Control Panel version that supports direct browser upload. Otherwise upload smaller files or raise the host limit.
186
357
 
187
- | Platform | Status | Player format |
188
- |-------------------|------------|---------------|
189
- | Samsung Tizen | V1 target | `.wgt` |
190
- | BrightSign | V1 target | autorun zip |
191
- | Others | See docs | varies |
358
+ **BrightSign zip has empty `cmsEndpoint`.**
359
+ You downloaded the mother zip from a static CDN instead of **this** CMS’s `/players/brightsign.zip`. Use Control Panel → Download Players → BrightSign on the deployed CMS.
192
360
 
193
- See `PLAYER_INSTALL.md` for installation notes.
361
+ **WebSocket disconnects on a serverless host.**
362
+ Most serverless platforms terminate idle connections. Vercel requires Fluid compute. Prefer a long-lived Node host (Railway, Render, Fly, Replit) if your platform cannot hold sockets.
363
+
364
+ **Database changes don't persist.**
365
+ You are probably on `TOMORROWOS_STORE=memory`. Switch to `sqlite` or Postgres/Supabase.
366
+
367
+ ---
368
+
369
+ ## Getting help
370
+
371
+ - **Package docs:** the Markdown files listed below ship inside `@tomorrowos/sdk`
372
+ - **npm:** [https://www.npmjs.com/package/@tomorrowos/sdk](https://www.npmjs.com/package/@tomorrowos/sdk)
373
+
374
+ If something in this README doesn't match what the package actually does, treat that as a bug and report it — documentation drift matters as much as a code defect.
194
375
 
195
376
  ---
196
377
 
197
378
  ## Documentation in this package
198
379
 
199
380
  | File | Purpose |
200
- |------|---------|
201
- | `LLM_PROMPT.md` | Five-question elicitation for LLMs |
202
- | `REPLIT_SETUP.md` | Replit Agent guided CMS setup |
203
- | `LOVABLE_SETUP.md` | Lovable Agent guided CMS setup (Node host + Lovable Cloud Storage) |
204
- | `VERCEL_SETUP.md` | Vercel / v0 guided CMS setup (configure after each answer) |
205
- | `VERCEL_QUESTIONS.md` | Verbatim Q1–Q3 text Agents must paste (do not paraphrase) |
206
- | `REPLIT_UPGRADE.md` | Replit Agent upgrade to latest SDK (no init) |
207
- | `BUILD_GUARDRAILS.md` | Mandatory CMS components |
208
- | `PLAYER_INSTALL.md` | Player build / install / pairing |
209
- | `brand.schema.json` | JSON Schema for `brand.json` |
210
- | `brand.example.json` | Full example `brand.json` |
211
- | `templates/cms-starter/` | Minimal Node + TypeScript server seed |
212
- | `templates/cms-starter/policy.example.json` | Example `setPolicy` payload with date schedule |
213
- | `templates/style-tokens/` | CSS tokens and UI pattern notes |
381
+ |---|---|
382
+ | [`LLM_PROMPT.md`](./LLM_PROMPT.md) | Five-question elicitation for AI agents |
383
+ | [`BUILD_GUARDRAILS.md`](./BUILD_GUARDRAILS.md) | Mandatory components in a generated CMS |
384
+ | [`PLAYER_INSTALL.md`](./PLAYER_INSTALL.md) | Player install and pairing notes |
385
+ | [`brand.schema.json`](./brand.schema.json) | JSON Schema for `brand.json` |
386
+ | [`brand.example.json`](./brand.example.json) | Complete example `brand.json` |
387
+ | [`REPLIT_SETUP.md`](./REPLIT_SETUP.md) | Replit Agent guided setup |
388
+ | [`REPLIT_UPGRADE.md`](./REPLIT_UPGRADE.md) | Upgrading an existing Replit CMS |
389
+ | [`VERCEL_SETUP.md`](./VERCEL_SETUP.md) | Vercel / v0 guided setup |
390
+ | [`VERCEL_QUESTIONS.md`](./VERCEL_QUESTIONS.md) | Verbatim Q1–Q3 text for Vercel agents |
391
+ | [`templates/cms-starter/`](./templates/cms-starter/) | Minimal Node + TypeScript server seed |
392
+ | [`templates/cms-starter-v0/`](./templates/cms-starter-v0/) | Vercel / v0 starter (Fluid + cms-panel) |
393
+ | [`templates/style-tokens/`](./templates/style-tokens/) | CSS tokens and UI pattern notes |
214
394
 
215
395
  ---
216
396
 
217
397
  ## License
218
398
 
219
- Apache 2.0.
399
+ Apache 2.0 (see `license` in `package.json`).
400
+
401
+ You can use TomorrowOS commercially, modify it, and build closed-source products on top of it. You can ship a proprietary CMS built on this SDK and sell it under your own brand. You do not owe a fee, a licence, or a mention.
220
402
 
221
403
  ## Protocol version
222
404
 
223
- Wire protocol `1.0` (see TomorrowOS specification).
405
+ Wire protocol **1.0** (see `protocolVersion` in [`brand.example.json`](./brand.example.json) and the TomorrowOS player/CMS contract).
@@ -1,6 +1,6 @@
1
1
  # Vercel / v0 閳?Verbatim question bank
2
2
 
3
- > **Protocol:** `vercel-questions/1.0` (pairs with `VERCEL_SETUP.md` 1.9+ / `@tomorrowos/sdk` 0.9.54+)
3
+ > **Protocol:** `vercel-questions/1.0` (pairs with `VERCEL_SETUP.md` 1.9+ / `@tomorrowos/sdk` 0.9.55+)
4
4
  > **For Agents:** When asking the user a setup question, **copy-paste the block below for that step EXACTLY**.
5
5
  > Do **not** paraphrase, reorder, rename options, or invent alternatives (no S3, no Supabase Storage, no 閳ユ窂lob recommended閳? no 閳ユ竸isable media閳?.
6
6
  > After each answer, configure per `VERCEL_SETUP.md`, then paste the **next** block.
package/VERCEL_SETUP.md CHANGED
@@ -5,7 +5,7 @@
5
5
  >
6
6
  > **On Vercel, this file is the setup questionnaire** (not `REPLIT_SETUP.md`, not `LLM_PROMPT.md`).
7
7
  >
8
- > Protocol id: `vercel-setup/1.9` — pairs with `@tomorrowos/sdk` **0.9.54+** (`templates/cms-starter-v0`, Fluid Functions WebSockets).
8
+ > Protocol id: `vercel-setup/1.9` — pairs with `@tomorrowos/sdk` **0.9.55+** (`templates/cms-starter-v0`, Fluid Functions WebSockets).
9
9
  >
10
10
  > **Questions source of truth:** `VERCEL_QUESTIONS.md` ? Agents must **open that file and paste each Q block verbatim**. Do not paraphrase.
11
11
  > **Copy questions verbatim from `VERCEL_QUESTIONS.md`.** Do not invent alternate Q2 options (no ?Supabase Storage?? no S3, no ?disable media?? no making Blob the recommended option).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tomorrowos/sdk",
3
- "version": "0.9.54",
3
+ "version": "0.9.55",
4
4
  "description": "TomorrowOS CMS server SDK - WebSocket transport, pairing, device commands, optional static CMS UI. Includes CLI (tomorrowos init / build) and starter templates.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -20,7 +20,6 @@
20
20
  "README.md",
21
21
  "LLM_PROMPT.md",
22
22
  "REPLIT_SETUP.md",
23
- "LOVABLE_SETUP.md",
24
23
  "VERCEL_SETUP.md",
25
24
  "VERCEL_QUESTIONS.md",
26
25
  "REPLIT_UPGRADE.md",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "my-cms",
3
- "version": "0.9.54",
3
+ "version": "0.9.55",
4
4
  "description": "CMS server on @tomorrowos/sdk. Add your UI (React, static files, etc.) alongside this server.",
5
5
  "private": true,
6
6
  "type": "module",
@@ -13,7 +13,7 @@
13
13
  "build-player": "tomorrowos build --platform tizen"
14
14
  },
15
15
  "dependencies": {
16
- "@tomorrowos/sdk": "^0.9.54",
16
+ "@tomorrowos/sdk": "^0.9.55",
17
17
  "dotenv": "^17.2.3",
18
18
  "tsx": "^4.19.0"
19
19
  },
@@ -2,14 +2,13 @@
2
2
  "policy": {
3
3
  "playlists": [
4
4
  {
5
- "id": "weekday-promo",
6
- "name": "Weekday promo",
5
+ "id": "promo-window",
6
+ "name": "Promo window",
7
7
  "schedule": {
8
- "startDate": "2026-05-01",
9
- "endDate": "2026-05-31",
10
- "daysOfWeek": [1, 2, 3, 4, 5],
11
- "start": "09:00",
12
- "end": "17:00"
8
+ "startDate": "2026-05-05",
9
+ "start": "13:00",
10
+ "endDate": "2026-05-08",
11
+ "end": "14:00"
13
12
  },
14
13
  "items": [
15
14
  {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "my-cms",
3
- "version": "0.9.54",
3
+ "version": "0.9.55",
4
4
  "description": "TomorrowOS CMS for Vercel / v0 Publish (Fluid Function + WebSockets + optional Next Preview).",
5
5
  "private": true,
6
6
  "type": "module",
@@ -17,7 +17,7 @@
17
17
  "build-player": "tomorrowos build --platform tizen"
18
18
  },
19
19
  "dependencies": {
20
- "@tomorrowos/sdk": "^0.9.54",
20
+ "@tomorrowos/sdk": "^0.9.55",
21
21
  "dotenv": "^17.2.3",
22
22
  "tsx": "^4.19.0"
23
23
  },
@@ -2,14 +2,13 @@
2
2
  "policy": {
3
3
  "playlists": [
4
4
  {
5
- "id": "weekday-promo",
6
- "name": "Weekday promo",
5
+ "id": "promo-window",
6
+ "name": "Promo window",
7
7
  "schedule": {
8
- "startDate": "2026-05-01",
9
- "endDate": "2026-05-31",
10
- "daysOfWeek": [1, 2, 3, 4, 5],
11
- "start": "09:00",
12
- "end": "17:00"
8
+ "startDate": "2026-05-05",
9
+ "start": "13:00",
10
+ "endDate": "2026-05-08",
11
+ "end": "14:00"
13
12
  },
14
13
  "items": [
15
14
  {
package/LOVABLE_SETUP.md DELETED
@@ -1,462 +0,0 @@
1
- # Lovable Setup Protocol for TomorrowOS CMS
2
-
3
- > **This file is a contract for Lovable AI / Lovable Agent.**
4
- > Follow it literally. Ask questions in order. Do not invent answers. Do not write or rewrite application logic until the Q&A is complete. Prefer configuring Secrets, `.env`, `brand.json`, and `server.ts` store wiring over inventing a custom CMS.
5
- >
6
- > **On Lovable, this file is the ONLY questionnaire.**
7
-
8
- ## User trigger (copy-paste)
9
-
10
- When a human pastes a prompt like this, **read this file and start Question 1 immediately**:
11
-
12
- ```text
13
- Follow @tomorrowos/sdk LOVABLE_SETUP.md and set up my TomorrowOS CMS.
14
- Ask only the questions listed in LOVABLE_SETUP.md, in order. Do not use LLM_PROMPT.md.
15
- Do not build a Vite/React mock of TomorrowOS 閳?use @tomorrowos/sdk cms-starter (Node + WebSocket).
16
- Question 3 only updates brand.json.
17
- After setup questions (including Cloudinary Secrets if chosen), configure and deploy 閳?minimal verification only.
18
- Never test Supabase connection 閳?save SUPABASE_URL and move on. No Supabase or WebSocket tests.
19
- Do not skip steps.
20
- ```
21
-
22
- Alternate triggers that also activate this protocol:
23
-
24
- - 閳ユ藩et up TomorrowOS on Lovable using LOVABLE_SETUP.md閳?
25
- - 閳ユ窅onfigure my TomorrowOS CMS (Lovable wizard)閳?
26
-
27
- When any of these triggers fire, **ignore `LLM_PROMPT.md` entirely** for the Q&A phase.
28
-
29
- **Before Question 1:** read **IRON RULE: real Node CMS only** below. If the project is already a default Lovable Vite + React app with no `@tomorrowos/sdk` server, **do not** keep building that as the TV CMS 閳?seed or replace with the SDK `cms-starter` layout first.
30
-
31
- ---
32
-
33
- ## IRON RULE: real Node CMS only (Lovable Cloud Publish is not enough)
34
-
35
- > **This is the #1 deployability rule. Non-negotiable.**
36
-
37
- TomorrowOS CMS needs a **Node.js process** that:
38
-
39
- - installs **`@tomorrowos/sdk`**
40
- - runs `tsx server.ts` / `TomorrowOS.listen(...)`
41
- - serves the Control Panel at **`GET /`**
42
- - keeps **WebSocket** connections open for Tizen / BrightSign players
43
-
44
- Lovable閳ユ獨 default app is **Vite + React + Lovable Cloud** (Postgres / Auth / Storage / Edge Functions). That stack **cannot** replace `@tomorrowos/sdk`閳ユ獨 long-lived WebSocket CMS by itself.
45
-
46
- | Required | Forbidden |
47
- |----------|-----------|
48
- | Project seeded from **`npx @tomorrowos/sdk init`** (or `templates/cms-starter`) | Rebuilding pairing / WebSocket / `setPolicy` in React or Edge Functions |
49
- | Live Node entry: `npm run start` 閳?`tsx server.ts` | Claiming Lovable Cloud **Publish** alone is the TV CMS endpoint |
50
- | Control Panel HTML at `/` via `staticRoot: public` | Vite-only SPA that 閳ユ笓ooks like閳?a CMS but has no TomorrowOS server |
51
- | Durable DB via Supabase / Lovable Cloud Postgres URL | Inventing a hand-rolled store protocol |
52
-
53
- ### How Publish works on Lovable (read carefully)
54
-
55
- 1. **Lovable Agent** = wizard + editor (ask Q1閳ユ彌3, write Secrets / `brand.json` / `server.ts`).
56
- 2. **GitHub sync** = required so the Node CMS can be hosted where Node + WebSocket work.
57
- 3. **TV-facing CMS host** (pick one; do not invent a fourth):
58
- - **Railway** (recommended companion) 閳?long-lived Node, `npm run start`
59
- - **Render / Fly.io** 閳?same pattern as Railway
60
- - **Vercel Fluid** 閳?only if following **`VERCEL_SETUP.md`** / `cms-starter-v0` (do not mix Replit-style root `server.ts` with broken Vercel static deploy)
61
-
62
- **Do not tell the user 閳ユ阀ublish on Lovable Cloud閳?is done** unless the **Node CMS URL** (Railway / Render / Fly / Vercel Fluid) is live and serves the Control Panel at `/`.
63
-
64
- Lovable Cloud remains useful for:
65
-
66
- - **Secrets** UI
67
- - **Postgres** (same family as Supabase 閳?use as `SUPABASE_URL` / database URL)
68
- - **Lovable Cloud Storage** (Object Storage replacement 閳?see Question 2)
69
-
70
- ---
71
-
72
- ## Questionnaire scope (STRICT 閳?read before asking anything)
73
-
74
- Lovable Agent must ask **only** the questions defined in **this file**, in **this exact order**:
75
-
76
- | Step | Section | When |
77
- |------|---------|------|
78
- | 1 | **Question 1** 閳?Supabase / Lovable Cloud Postgres connection string | Always |
79
- | 2 | **Question 2** 閳?Media storage (+ Cloudinary Secrets if chosen) | Always (after Q1) |
80
- | 3 | **Question 3** 閳?Brand / TomorrowOS app look | Always last, before execution |
81
-
82
- **That is the complete list.** There are no other setup questions. **Do not** ask how many screens / devices. There is **no** 閳ユ法uestion 2b閳?閳?Cloudinary credentials are collected **inside Question 2**, immediately after the user chooses Cloudinary.
83
-
84
- **Critical:** Question 2 is **not complete** when the user says 閳ユ窅loudinary閳?or 閳ユ脯es閳? You **must** immediately collect `CLOUDINARY_CLOUD_NAME`, `CLOUDINARY_API_KEY`, and `CLOUDINARY_API_SECRET` **before** Question 3. Do **not** skip to branding with placeholder or invented credentials.
85
-
86
- If the user volunteers extra info early (e.g. brand name before Q3), **record it** and still ask the current question閳ユ獨 required fields you do not yet have. Do not skip ahead to execution until Q1閳ユ彌3 are complete (Question 2 includes Cloudinary Secrets when applicable).
87
-
88
- If the user says 閳ユ笍ust set it up, don閳ユ獩 ask questions,閳?respond:
89
-
90
- > I need a few quick answers from LOVABLE_SETUP.md (Postgres connection string, media storage 閳?plus Cloudinary credentials if you choose Cloudinary 閳?and branding). It takes about a minute and ensures the CMS deploys correctly with Lovable + a Node host.
91
-
92
- Then ask **Question 1** 閳?do not switch to `LLM_PROMPT.md`.
93
-
94
- ---
95
-
96
- ## Hard rules
97
-
98
- 1. **Ask only LOVABLE_SETUP.md questions 1閳?.** Never use `LLM_PROMPT.md` on Lovable for this wizard.
99
- 2. **One question at a time.** Wait for the user閳ユ獨 answer before asking the next (unless they already answered several in one message). **Exception:** If the user chooses Cloudinary in Question 2, **stay on Question 2** and immediately collect Cloudinary Secrets 閳?do **not** announce a separate 閳?b閳?
100
- 3. **Do not invent** Cloudinary credentials, database URLs, or brand colours. **Do not ask** screen / device count.
101
- 4. **Prefer `SUPABASE_URL`** for the Postgres connection string Secret name (same as Replit). Lovable Cloud / linked Supabase both work. Do not commit passwords into git.
102
- 5. **Prefer `npx @tomorrowos/sdk init`** (or copy `templates/cms-starter`) as the project seed. Do not rebuild pairing, WebSocket, or playlist APIs from scratch.
103
- 6. **Never commit secrets.** Put credentials in Lovable **Cloud 閳?Secrets** (and the Node host閳ユ獨 env vars: Railway / Render / Fly / Vercel).
104
- 7. After Q&A, **configure and deploy** 閳?do **not** run a long test suite (see **Post-Q&A: minimal verification only**).
105
- 8. **Supabase / Lovable Postgres: configure only 閳?never test.** Save `SUPABASE_URL` + `TOMORROWOS_STORE=supabase` and proceed. Do not ping Postgres or treat preview DB errors as a failed setup.
106
- 9. **Do not** replace `@tomorrowos/sdk` with Edge Functions, React state, or a fake WebSocket.
107
- 10. **Media durability:** Lovable has **no Replit Object Storage mount** on a local `public/uploads` disk. Use **Cloudinary** or **Lovable Cloud Storage** (see Question 2). Plain local `public/uploads` is ephemeral on most Node hosts unless a volume is attached.
108
-
109
- **Question order (always):**
110
-
111
- - Q1 閳?Q2 閳?Q3 閳?execution checklist
112
-
113
- ---
114
-
115
- ## Question 1 閳?Supabase / Lovable Cloud Postgres (always)
116
-
117
- > **This is the first setup question.** Always configure durable Postgres. **Do not** ask how many screens / devices.
118
-
119
- **Ask exactly:**
120
-
121
- > Paste your Postgres connection string. I will store it as Secret **`SUPABASE_URL`** (works for Lovable Cloud DB or a linked Supabase project).
122
- >
123
- > Example shape: `postgresql://postgres.[PROJECT]:[PASSWORD]@aws-0-[REGION].pooler.supabase.com:6543/postgres`
124
- >
125
- > If you use **Lovable Cloud**, open **Cloud 閳?Database** (or project connection settings) and copy the Postgres URI. Also confirm SSL (usually **yes**).
126
-
127
- **You must then:**
128
-
129
- 1. Save Lovable Cloud Secret: `SUPABASE_URL=<user value>`
130
- 2. Save also:
131
- - `TOMORROWOS_STORE=supabase`
132
- - `DATABASE_SSL=true` (unless the user explicitly says SSL is off)
133
- 3. Mirror the same env vars on the **Node host** (Railway / Render / Fly / Vercel) 閳?Lovable Secrets alone do not reach a Railway process.
134
- 4. Wire `server.ts` like the Replit starter:
135
-
136
- ```ts
137
- import "dotenv/config";
138
- import { readFileSync } from "fs";
139
- import { fileURLToPath } from "url";
140
- import { dirname, join } from "path";
141
- import { createTomorrowOSStore, TomorrowOS } from "@tomorrowos/sdk";
142
-
143
- const __dirname = dirname(fileURLToPath(import.meta.url));
144
- const brand = JSON.parse(readFileSync(join(__dirname, "brand.json"), "utf8"));
145
-
146
- const store = createTomorrowOSStore({
147
- databaseUrl: process.env.SUPABASE_URL || process.env.DATABASE_URL,
148
- sqlitePath: join(__dirname, "data", "tomorrowos.db")
149
- });
150
-
151
- const tomorrowos = new TomorrowOS({ brand, store });
152
-
153
- tomorrowos.listen({
154
- port: Number(process.env.PORT) || 3000,
155
- host: "0.0.0.0",
156
- staticRoot: join(__dirname, "public")
157
- });
158
- ```
159
-
160
- 5. **Do not** write the real password into committed files.
161
- 6. **Do not** test the database connection after saving 閳?configuration only.
162
- 7. **Do not** proceed to Question 2 until `SUPABASE_URL` is saved.
163
-
164
- If the user refuses Postgres, warn that fleets need a durable store, then offer SQLite only after they explicitly confirm (SQLite is a poor fit on ephemeral Lovable/Railway disks).
165
-
166
- **Later in Question 3:** set `cms.hostingTarget` to `"self-hosted"` (Railway / Render / Fly) or `"vercel"` if using Vercel Fluid. Do **not** ask for `expectedScreens`; default e.g. `5`.
167
-
168
- ---
169
-
170
- ## Question 2 閳?Media storage (uploads / thumbnails)
171
-
172
- ### Step A 閳?Ask storage choice
173
-
174
- **Ask exactly:**
175
-
176
- > How should playlist media (images/videos) be stored?
177
- >
178
- > **Recommended: Cloudinary** 閳?public HTTPS URLs; `@tomorrowos/sdk` auto-detects `CLOUDINARY_*` Secrets and `/media/upload` works out of the box.
179
- >
180
- > Alternatives:
181
- > - **Lovable Cloud Storage** (replaces Replit Object Storage) 閳?public Storage bucket on Lovable Cloud / Supabase Storage; durable HTTPS URLs for players.
182
- > - **Local disk only** 閳?`public/uploads` on the Node host (fine for quick tests; files may disappear on redeploy without a volume).
183
- >
184
- > Do you want me to set up **Cloudinary**? (yes / no 閳?if no, say whether you want **Lovable Cloud Storage** or local only)
185
-
186
- ### Step B 閳?If YES / Cloudinary (same Question 2 閳?no 閳?b閳?
187
-
188
- **Do this immediately** when the user chooses Cloudinary. **Go straight to collecting Secrets.**
189
-
190
- **Preferred:** Lovable **Cloud 閳?Secrets** fields for:
191
-
192
- - `CLOUDINARY_CLOUD_NAME`
193
- - `CLOUDINARY_API_KEY`
194
- - `CLOUDINARY_API_SECRET`
195
- - Optional: `CLOUDINARY_FOLDER` (e.g. `tomorrowos`)
196
-
197
- **If Secrets UI is unavailable, ask exactly:**
198
-
199
- > Paste your Cloudinary credentials (from [cloudinary.com/console](https://cloudinary.com/console) 閳?Dashboard 閳?**API Keys**). I will store them as Secrets:
200
- >
201
- > 1. **`CLOUDINARY_CLOUD_NAME`**
202
- > 2. **`CLOUDINARY_API_KEY`**
203
- > 3. **`CLOUDINARY_API_SECRET`**
204
- >
205
- > Optional: **`CLOUDINARY_FOLDER`**
206
-
207
- **You must then:**
208
-
209
- 1. Save those Secrets in Lovable **and** on the Node host.
210
- 2. **Do not** invent or placeholder credentials.
211
- 3. **Do not** proceed to Question 3 until all three required Secrets exist.
212
- 4. **Do not** run a Cloudinary upload test 閳?configuration only.
213
-
214
- The SDK auto-detects these env vars; new uploads return `https://res.cloudinary.com/...` URLs.
215
-
216
- ### If NO 閳?Lovable Cloud Storage (Object Storage replacement)
217
-
218
- This is the Lovable equivalent of **Replit Object Storage**: durable files with public HTTPS URLs, not an ephemeral disk folder.
219
-
220
- **Do this:**
221
-
222
- 1. Open / use **Cloud 閳?Storage**.
223
- 2. Create a **public** bucket named **`tomorrowos-uploads`** (or confirm it exists). Public read is required so Tizen / BrightSign players can fetch media by URL.
224
- 3. Save Secrets (Lovable + Node host) as needed for Storage access used by your upload path, typically:
225
- - `SUPABASE_URL` (already from Q1 閳?API URL form may differ from Postgres URI; if the project exposes a separate API URL, store it as `SUPABASE_API_URL` only when required)
226
- - Service role or upload-capable key **only in server-side Secrets**, never in client bundles
227
- 4. Tell the user clearly:
228
- - Playlist items must use **absolute `https://...` Storage (or Cloudinary) URLs**.
229
- - `@tomorrowos/sdk` built-in `/media/upload` persists to **Cloudinary** or **local `public/uploads`**. It does **not** natively write to Lovable Storage yet.
230
- - Therefore: **prefer Cloudinary for one-click Control Panel uploads**, or upload files into the `tomorrowos-uploads` bucket and paste/publicize the HTTPS object URLs into playlists.
231
- 5. **Do not** claim that `public/uploads` on Lovable is durable like Replit Object Storage.
232
- 6. **Do not** invent Cloudinary credentials.
233
-
234
- ### If NO 閳?local disk only
235
-
236
- 1. Ensure `public/uploads` exists on the Node host (`mkdir -p public/uploads`).
237
- 2. Warn: redeploys may wipe files unless the host has a persistent volume.
238
- 3. Prefer Cloudinary or Lovable Cloud Storage for production.
239
-
240
- ---
241
-
242
- ## Question 3 閳?Brand / TomorrowOS app look (`brand.json` only)
243
-
244
- > **Scope:** Question 3 answers **only** update **`brand.json`**.
245
- > **Do not** change deploy host, Secrets, or `server.ts` from branding answers.
246
-
247
- **Ask exactly (one message; user may answer in one reply):**
248
-
249
- > Let閳ユ獨 brand your TomorrowOS experience. Please provide:
250
- >
251
- > 1. **Product / venue name** (shown on screens and the Control Panel)
252
- > 2. **Tagline** (optional)
253
- > 3. **Primary colour** (hex, e.g. `#FF8A3D`)
254
- > 4. **Background colour** (hex, optional 閳?default `#FAFAF9`)
255
- > 5. **Text colour** (hex, optional 閳?default `#0A0908`)
256
- > 6. **Secondary / accent colour** (hex, optional)
257
- > 7. **Logo** 閳?upload an SVG/PNG into the project, or give a URL I can fetch into `./assets/`
258
-
259
- If the user only gives a name and primary colour, use defaults for the rest and say what you assumed.
260
-
261
- **Then write / update `brand.json` only** (validate mentally against `brand.schema.json`). Minimum example:
262
-
263
- ```json
264
- {
265
- "name": "<user name>",
266
- "tagline": "<user tagline or Digital signage>",
267
- "targetPlatforms": ["tizen"],
268
- "primaryColor": "#FF8A3D",
269
- "secondaryColor": "#F5F3EF",
270
- "backgroundColor": "#FAFAF9",
271
- "textColor": "#0A0908",
272
- "logoPath": "./assets/logo.svg",
273
- "fontFamily": "Inter",
274
- "cms": {
275
- "useCase": "other",
276
- "hostingTarget": "self-hosted",
277
- "expectedScreens": 5,
278
- "features": {
279
- "bulkCommands": false,
280
- "proofOfPlay": false,
281
- "contentScheduling": true,
282
- "userManagement": false
283
- }
284
- },
285
- "protocolVersion": "1.0"
286
- }
287
- ```
288
-
289
- - Default `cms.hostingTarget` to `"self-hosted"` for Railway / Render / Fly.
290
- - Use `"vercel"` only when following the Vercel Fluid path.
291
- - Set `cmsEndpoint` only if the user already knows the public `wss://閳ヮ泦 URL; otherwise leave it out and tell them to point players at the published HTTPS CMS URL (player converts `https://` 閳?`wss://`).
292
-
293
- ---
294
-
295
- ## After all answers 閳?execution checklist
296
-
297
- ### A. Seed the TomorrowOS CMS (not a Lovable React mock)
298
-
299
- ```bash
300
- npx @tomorrowos/sdk@latest init .
301
- # If the directory is not empty and the user confirms overwrite of starter files only:
302
- # npx @tomorrowos/sdk@latest init . --force
303
- ```
304
-
305
- If Lovable already generated a Vite app:
306
-
307
- 1. **Keep** Lovable only as the editor / Secrets / Storage UI if useful.
308
- 2. **Add** the cms-starter Node CMS at repo root (or a clear `cms/` folder that is the deploy root).
309
- 3. **Do not** implement TomorrowOS protocol inside React pages.
310
-
311
- ### B. Apply store + media Secrets
312
-
313
- **Postgres (always from Question 1):**
314
-
315
- ```env
316
- TOMORROWOS_STORE=supabase
317
- SUPABASE_URL=...
318
- DATABASE_SSL=true
319
- ```
320
-
321
- **Cloudinary (if chosen):**
322
-
323
- ```env
324
- CLOUDINARY_CLOUD_NAME=...
325
- CLOUDINARY_API_KEY=...
326
- CLOUDINARY_API_SECRET=...
327
- ```
328
-
329
- Copy the same keys to the **Node host** env.
330
-
331
- ### C. Wire `server.ts`
332
-
333
- - `host: "0.0.0.0"`
334
- - `port: Number(process.env.PORT) || 3000`
335
- - `staticRoot: public`
336
- - Supabase path: `databaseUrl: process.env.SUPABASE_URL || process.env.DATABASE_URL`
337
- - Keep starter event handlers (paired / online policy push)
338
-
339
- ### D. Runtime files for Node publish
340
-
341
- ```json
342
- {
343
- "type": "module",
344
- "engines": { "node": ">=20" },
345
- "scripts": {
346
- "dev": "tsx watch server.ts",
347
- "start": "tsx server.ts"
348
- },
349
- "dependencies": {
350
- "@tomorrowos/sdk": "^0.9.54",
351
- "dotenv": "^17.2.3",
352
- "tsx": "^4.19.0"
353
- }
354
- }
355
- ```
356
-
357
- - Keep **`tsx` in `dependencies`** (not only `devDependencies`).
358
- - Bump `@tomorrowos/sdk` to latest when scaffolding.
359
-
360
- ### E. GitHub sync + deploy Node CMS
361
-
362
- 1. Enable **GitHub sync** from Lovable (or push the cms-starter repo).
363
- 2. Create a **Railway** (or Render / Fly) service from that repo.
364
- 3. Set Start Command: `npm run start`
365
- 4. Paste Secrets from Q1閳ユ彌2 into the host.
366
- 5. Deploy and open the public HTTPS URL.
367
-
368
- **If the user insists on Vercel:** stop using this Railway-oriented finish path and follow **`VERCEL_SETUP.md`** (`npx tomorrowos init --hosting v0`) instead 閳?do not half-migrate.
369
-
370
- ### F. Minimal verification only
371
-
372
- 1. `GET /` shows TomorrowOS Control Panel HTML.
373
- 2. Host logs show `[TomorrowOS] listening on http://0.0.0.0:...`
374
- 3. **Do not** run Supabase connection tests, Cloudinary upload tests, or WebSocket console gates.
375
-
376
- ### G. Tell the user next steps (no code)
377
-
378
- 1. Keep the **Node CMS host** running (Railway Always On / paid plan as needed).
379
- 2. **Control Panel URL:** `https://YOUR-CMS-HOST`
380
- 3. On the TomorrowOS player, enter CMS URL: `https://YOUR-CMS-HOST/`
381
- 4. Enter the 6-character pairing code into the Control Panel.
382
- 5. Create a playlist, add media (Cloudinary upload or Lovable Storage HTTPS URLs), Publish to the device.
383
- 6. Point them at `PLAYER_INSTALL.md` for player install.
384
-
385
- ---
386
-
387
- ## Post-Q&A: minimal verification only
388
-
389
- ### Do (maximum)
390
-
391
- 1. Confirm real **`@tomorrowos/sdk`** server (not a React mock).
392
- 2. Write Secrets / `.env` / `brand.json` / `server.ts` from answers.
393
- 3. Deploy Node host 閳?Preview Control Panel at `/`.
394
-
395
- ### Do NOT run after setup (unless user asks or deploy fails)
396
-
397
- - 閴?Supabase connection test
398
- - 閴?Cloudinary upload test
399
- - 閴?Formal WebSocket console gates
400
- - 閴?Long troubleshooting when `/` already shows the Control Panel
401
-
402
- ---
403
-
404
- ## What not to ask / change
405
-
406
- - **`LLM_PROMPT.md`** 閳?not used for this Lovable wizard
407
- - Standalone screen-count / platform / hosting questionnaires outside Q1閳ユ彌3
408
- - Replacing `@tomorrowos/sdk` with Edge Functions or React
409
- - Saying **Lovable Cloud Publish** alone is enough for TVs
410
- - Treating **Lovable Cloud Storage** as automatic `public/uploads` persistence (it is **not** a disk mount like Replit Object Storage)
411
- - Skipping Cloudinary Secrets after the user chooses Cloudinary
412
- - Changing deploy wiring based on Question 3 branding answers
413
-
414
- ---
415
-
416
- ## SQLite fallback 閳?only if user refuses Postgres
417
-
418
- ```ts
419
- const store = createTomorrowOSStore({
420
- sqlitePath: join(__dirname, "data", "tomorrowos.db")
421
- });
422
- ```
423
-
424
- ```env
425
- TOMORROWOS_STORE=sqlite
426
- ```
427
-
428
- Warn that SQLite on ephemeral hosts loses pairings on redeploy.
429
-
430
- ---
431
-
432
- ## Failure recovery cheat sheet
433
-
434
- | Symptom | Likely cause | Fix |
435
- |---------|--------------|-----|
436
- | 閳ユ窅MS閳?is a React app with no pairing | Agent built a Lovable mock | Re-seed with `npx @tomorrowos/sdk init`; deploy Node host |
437
- | Lovable Publish URL does not pair TVs | Expected 閳?no WebSocket CMS there | Deploy Railway / Render / Fly / Vercel Fluid; use that URL |
438
- | `tsx: not found` on host | `tsx` only in devDependencies | Move `tsx` to `dependencies` |
439
- | Uploads vanish after redeploy | Local `public/uploads` | Cloudinary or Lovable Cloud Storage public URLs |
440
- | Storage files 403 on player | Private bucket | Make `tomorrowos-uploads` **public** read |
441
- | Supabase errors in preview | Preview network / wrong URI | Config-only; validate on published Node host |
442
- | Pairing works on one host not another | Different CMS process / Secrets | Compare env and `listening` logs |
443
-
444
- ---
445
-
446
- ## Replit 閳?Lovable mapping (for Agents)
447
-
448
- | Replit | Lovable |
449
- |--------|---------|
450
- | Replit Secrets | Lovable **Cloud 閳?Secrets** (+ copy to Node host) |
451
- | Replit Object Storage / `public/uploads` | **Lovable Cloud Storage** public bucket `tomorrowos-uploads` (or Cloudinary) |
452
- | `.replit` + Autoscale Publish | **GitHub sync + Railway/Render/Fly** `npm run start` (or **VERCEL_SETUP.md**) |
453
- | Artifact `kind = web` | Real cms-starter Node app with Control Panel at `/` |
454
- | `cms.hostingTarget: "here"` | `"self-hosted"` or `"vercel"` |
455
-
456
- ---
457
-
458
- ## Protocol version
459
-
460
- `lovable-setup/1.0` 閳?aligned with TomorrowOS protocol `1.0` and `@tomorrowos/sdk` store drivers `sqlite` | `supabase` | `postgres` | `memory`.
461
-
462
- **Changelog 1.0:** Initial Lovable wizard mirroring REPLIT_SETUP Q1閳ユ彌3; IRON RULE that Lovable Cloud Publish is not the WebSocket CMS host; Replit Object Storage replaced by Lovable Cloud Storage (+ Cloudinary still recommended for `/media/upload`).