@tomorrowos/sdk 0.9.22 → 0.9.24

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
@@ -4,7 +4,7 @@
4
4
 
5
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`).
6
6
 
7
- **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, Fluid compute + WebSocket, `cms.hostingTarget: "vercel"`).
7
+ **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"`).
8
8
 
9
9
  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.
10
10
 
@@ -72,8 +72,9 @@ IF any step contradicts Vercel, adapt to settings that Vercel can accept.
72
72
  ```
73
73
 
74
74
  That wizard asks: database (**Supabase** → **Neon** → SQLite demo), media (**Cloudinary** → **Vercel Blob** → local),
75
- branding (manual fields or **infer from a website URL**), enables Fluid compute for WebSockets, and sets
76
- `cms.hostingTarget` to `"vercel"`.
75
+ branding (manual fields or **website URL `brand.json` only**, no login unless asked). Preview may use a thin **Next.js reverse proxy**;
76
+ **Publish stays pure Node** `TomorrowOS.listen` (not Next). Cloudinary credentials are collected one-at-a-time
77
+ (cloud name → API key → API secret). Sets `cms.hostingTarget` to `"vercel"`.
77
78
 
78
79
  ### Replit upgrade (existing CMS)
79
80
 
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.1` — pairs with `@tomorrowos/sdk` packages that ship `templates/cms-starter` and this file.
8
+ > Protocol id: `vercel-setup/1.3` — pairs with `@tomorrowos/sdk` packages that ship `templates/cms-starter` and this file.
9
9
 
10
10
  ## User trigger (copy-paste)
11
11
 
@@ -16,7 +16,8 @@ Follow @tomorrowos/sdk VERCEL_SETUP.md and set up my TomorrowOS CMS.
16
16
  Ask only the questions listed in VERCEL_SETUP.md, in order. Do not use LLM_PROMPT.md or REPLIT_SETUP.md.
17
17
  If any step contradicts Vercel, adapt to settings Vercel can accept (document the adaptation).
18
18
  After all setup questions are answered (including Cloudinary or Vercel Blob Env Vars if you chose those), configure and deploy — minimal verification only (Control Panel at /). Prefer Supabase Session pooler URL or Neon pooled URL. Do not treat sandbox ENETUNREACH / IPv6 :5432 errors as a hard setup failure when a pooler URL is configured for production.
19
- Question 3 only updates brand.json. Do not skip steps.
19
+ Question 3 only updates brand.json from colours/logo/name (a website URL is brand reference only — do not add CMS login/auth unless the user explicitly asks). Do not skip steps.
20
+ Use Next.js only as a v0/Preview shell with a reverse proxy; Production Publish must remain pure Node TomorrowOS.listen (not Next).
20
21
  ```
21
22
 
22
23
  Alternate triggers:
@@ -28,7 +29,7 @@ When any of these fire, **ignore `REPLIT_SETUP.md` and `LLM_PROMPT.md`** for the
28
29
 
29
30
  ---
30
31
 
31
- ## IRON RULE: long-lived Node + WebSocket (not classic static / short Functions)
32
+ ## IRON RULE: Preview vs Publish (dual runtime)
32
33
 
33
34
  > **This is the #1 deployability rule on Vercel. Non-negotiable.**
34
35
 
@@ -37,16 +38,35 @@ TomorrowOS CMS is **one Node process** that serves:
37
38
  - Browser Control Panel at `GET /` (`public/` via `staticRoot`)
38
39
  - Device **WebSocket** on the same origin (`https` → players use `wss`)
39
40
 
41
+ ### Production / Publish — Node only (no Next.js)
42
+
40
43
  | Required | Forbidden |
41
44
  |----------|-----------|
42
- | Single **Node HTTP server** entry (`tsx server.ts` / exported `http.Server` from `listen()`) | Next.js / Remix / static-only site as the CMS |
45
+ | Single **Node HTTP server** entry (`tsx server.ts` / exported `http.Server` from `listen()`) | **Next.js as the Production CMS** (`next start`, Next App Router as the live app) |
43
46
  | **Fluid compute** enabled (needed for WebSockets on Vercel) | Classic short-lived serverless with no WebSocket / no Fluid |
44
47
  | Durable store = **Postgres** (Supabase or Neon, pooled URL) | **SQLite / `data/*.db` as production store** on Vercel (ephemeral filesystem) |
45
48
  | Media = **Cloudinary** or **Vercel Blob** (durable HTTPS URLs) | Relying on local `public/uploads` alone for production fleets |
46
49
  | Env vars in **Vercel Project → Settings → Environment Variables** | Committing passwords / API secrets / `.env` with secrets |
47
50
  | `cms.hostingTarget`: `"vercel"` in `brand.json` | Creating `.replit` / Replit artifact files |
48
51
 
49
- **Do not** scaffold a leftover Next.js app “and put TomorrowOS beside it.” Remove Next scaffolds so TomorrowOS `server.ts` is the sole app.
52
+ **Publish / Production Deploy** must run **`TomorrowOS.listen(...)`** directly. Do **not** put Next.js in front of Production traffic.
53
+
54
+ ### Preview (v0 / Vercel Agent sandbox) — Next.js shell allowed
55
+
56
+ v0 Preview is often a **Next.js runtime** hardwired to `next dev`. A raw `tsx server.ts` may listen successfully while Preview still shows a blank page, because the sandbox intercepts the public port with Next before requests reach TomorrowOS.
57
+
58
+ **For Preview only**, you **must** keep a thin Next.js host that **reverse-proxies** HTTP to TomorrowOS on an **internal** port.
59
+
60
+ | Preview requirement | Detail |
61
+ |---------------------|--------|
62
+ | Next.js remains the Preview host | Satisfies v0 / sandbox expectations |
63
+ | TomorrowOS on internal port | e.g. `TOMORROWOS_INTERNAL_PORT=3001` (not the public Preview port) |
64
+ | Transparent HTTP reverse proxy | Next route/middleware forwards `/` (and Control Panel paths) → `http://127.0.0.1:3001` |
65
+ | Start both processes in Preview | Next + `tsx server.ts` (or a `dev:preview` script that starts both) |
66
+
67
+ **Preview WebSocket note:** HTTP proxy is enough for Control Panel HTML/API smoke checks. Device `wss` pairing may not work through a Next proxy in Preview — that is acceptable. **Pair devices against the Production Publish URL**, not v0 Preview.
68
+
69
+ **Do not** replace TomorrowOS with a Next-only CMS. Next is a **Preview adapter**, not the product.
50
70
 
51
71
  ---
52
72
 
@@ -60,7 +80,7 @@ Ask **only** these questions, in **this exact order**:
60
80
  | 2 | **Question 2** — Media storage (Cloudinary → Vercel Blob → local) | Always (after Q1) |
61
81
  | 3 | **Question 3** — Brand / TomorrowOS look (`brand.json` only) | Always last |
62
82
 
63
- **That is the complete list.** Do **not** ask how many screens / devices. There is **no** “Question 2b” — Cloudinary / Vercel Blob credentials stay **inside Question 2**.
83
+ **That is the complete list.** Do **not** ask how many screens / devices.
64
84
 
65
85
  If the user says “just set it up, don’t ask questions,” respond:
66
86
 
@@ -73,7 +93,7 @@ Then ask **Question 1**.
73
93
  ## Hard rules
74
94
 
75
95
  1. **Ask only VERCEL_SETUP.md questions 1–3.** Never use `LLM_PROMPT.md` or `REPLIT_SETUP.md` as the questionnaire.
76
- 2. **One question at a time** (exception: after Cloudinary or Vercel Blob choice, immediately collect the required env vars in the same Question 2).
96
+ 2. **One question / one credential at a time.** For Cloudinary: ask **cloud name**, then **API key alone**, then **API secret alone** never one message that mixes key + secret.
77
97
  3. **Do not invent** Cloudinary credentials, database URLs, Vercel Blob tokens, or brand colours.
78
98
  4. **Postgres env naming:** prefer **`SUPABASE_URL`** when using Supabase; prefer **`DATABASE_URL`** (or `NEON_DATABASE_URL`) when using Neon. `createTomorrowOSStore` accepts `SUPABASE_URL` then `DATABASE_URL`.
79
99
  5. **Always prefer pooled connection strings** (Supabase Session pooler port **6543**; Neon pooled host). Direct `:5432` URLs commonly fail on Vercel/sandbox with **`ENETUNREACH`** (often IPv6).
@@ -81,9 +101,11 @@ Then ask **Question 1**.
81
101
  7. **Never commit secrets.** Use Vercel Environment Variables (+ optional non-secret flags in committed `.env.example`).
82
102
  8. **Skip Replit-only files:** do **not** create `.replit`, `.replit-artifact`, or Replit `deploymentTarget`.
83
103
  9. **SQLite is not a production store on Vercel.** Offer it only as option **3** after explicit warning. Keep `sqlitePath` as local-dev fallback in `createTomorrowOSStore`.
84
- 10. **Enable Fluid compute** for WebSockets. Confirm Project settings after scaffold.
85
- 11. **Export the HTTP server** Vercel can capture: `const server = tomorrowos.listen(...); export default server;` (or equivalent). Do not export only the `TomorrowOS` class instance unless the platform documents another capture path.
86
- 12. After Q&A: **configure install run/deploy minimal verify** (Control Panel at `/`). Do not run a long test suite.
104
+ 10. **Enable Fluid compute** for Production WebSockets. Confirm Project settings after scaffold.
105
+ 11. **Production:** export the HTTP server Vercel can capture: `const server = tomorrowos.listen(...); export default server;` (or equivalent). **Do not** make `next start` the Production entrypoint.
106
+ 12. **Preview:** if the environment is v0 / Next-hardwired, install the Next reverse-proxy shell (see **Preview adapter**). Do not leave Preview broken.
107
+ 13. After Q&A: **configure → install → run/deploy → minimal verify** (Control Panel at `/`). Do not run a long test suite.
108
+ 14. **No inventing CMS login.** A branding URL never means “build auth.” Only add login if the user **explicitly** requests it.
87
109
 
88
110
  **Question order:** Q1 → Q2 → Q3 → execution checklist.
89
111
 
@@ -91,12 +113,13 @@ Then ask **Question 1**.
91
113
 
92
114
  ## Runtime & Vercel deploy rules (mandatory)
93
115
 
94
- ### Runtime
116
+ ### Production runtime (Publish)
95
117
 
96
118
  - Serve with **`tsx server.ts`** (or compiled Node that still calls `TomorrowOS.listen` with `staticRoot`).
97
119
  - Keep **`@tomorrowos/sdk`** and put **`tsx` in `dependencies`** (not only `devDependencies`) if production runs via `tsx`.
98
120
  - Node **20+**.
99
121
  - **Do not** replace the SDK with a hand-rolled server that drops WebSocket upgrades.
122
+ - **Forbidden as the Production Deploy entrypoint:** `next start`, static export only, or any command that does not start `TomorrowOS.listen(...)`.
100
123
 
101
124
  ### Expected `package.json` scripts
102
125
 
@@ -104,14 +127,21 @@ Then ask **Question 1**.
104
127
  {
105
128
  "scripts": {
106
129
  "dev": "tsx watch server.ts",
107
- "start": "tsx server.ts"
130
+ "dev:preview": "concurrently -k \"npm:dev:tomorrowos\" \"npm:dev:next\"",
131
+ "dev:tomorrowos": "cross-env PORT=3001 TOMORROWOS_INTERNAL_PORT=3001 tsx watch server.ts",
132
+ "dev:next": "next dev -p 3000",
133
+ "start": "tsx server.ts",
134
+ "build": "echo \"Production is Node — no Next production build required\""
108
135
  }
109
136
  }
110
137
  ```
111
138
 
112
- **Forbidden as the Deploy entrypoint:** `next start`, static export only, or any command that does not start `TomorrowOS.listen(...)`.
139
+ Adapt script names / process runners as needed. **Invariant:**
140
+
141
+ - **`npm start` / Production** = TomorrowOS Node only.
142
+ - **Preview / v0** = Next on public port + TomorrowOS on internal port + proxy.
113
143
 
114
- ### Minimal dependencies
144
+ ### Minimal dependencies (Production)
115
145
 
116
146
  ```json
117
147
  {
@@ -137,9 +167,11 @@ Then ask **Question 1**.
137
167
 
138
168
  Use the latest published `@tomorrowos/sdk` when scaffolding (do not invent versions).
139
169
 
140
- Add `@vercel/blob` to `dependencies` only when the user chooses **Vercel Blob** in Question 2.
170
+ Add `@vercel/blob` only when the user chooses **Vercel Blob** in Question 2.
171
+
172
+ For Preview shell only, add as needed: `next`, `react`, `react-dom`, and a process runner (`concurrently` or equivalent). These must **not** become the Production CMS.
141
173
 
142
- ### `server.ts` pattern (Vercel-adapted)
174
+ ### `server.ts` pattern (Vercel-adapted — Production)
143
175
 
144
176
  ```ts
145
177
  import "dotenv/config";
@@ -162,8 +194,13 @@ const store = createTomorrowOSStore({
162
194
 
163
195
  const tomorrowos = new TomorrowOS({ brand, store });
164
196
 
197
+ const port =
198
+ Number(process.env.TOMORROWOS_INTERNAL_PORT) ||
199
+ Number(process.env.PORT) ||
200
+ 3000;
201
+
165
202
  const server = tomorrowos.listen({
166
- port: Number(process.env.PORT) || 3000,
203
+ port,
167
204
  host: "0.0.0.0",
168
205
  staticRoot: join(__dirname, "public")
169
206
  });
@@ -172,9 +209,37 @@ const server = tomorrowos.listen({
172
209
  export default server;
173
210
  ```
174
211
 
212
+ - **Production:** `PORT` from Vercel → TomorrowOS listens on that port (no Next).
213
+ - **Preview:** set `TOMORROWOS_INTERNAL_PORT=3001` (or similar) so Next can own `:3000` and proxy inward.
214
+
175
215
  Load env with `dotenv` for local/`vercel dev`. On production Deploy, Vercel injects Environment Variables natively.
176
216
 
177
- ### `vercel.json` (recommended minimum)
217
+ ### Preview adapter (v0 / Next reverse proxy) — required when Preview is Next-hardwired
218
+
219
+ Detect Preview / v0 (Next already present, or Preview blank while TomorrowOS logs “listening”). Then:
220
+
221
+ 1. Keep a **minimal** Next app (App Router or Pages — whichever the sandbox expects).
222
+ 2. TomorrowOS listens on **`127.0.0.1:TOMORROWOS_INTERNAL_PORT`** (default **3001**).
223
+ 3. Add a **catch-all** Next route (or middleware) that proxies HTTP to TomorrowOS:
224
+
225
+ ```ts
226
+ // Example: app/api/tomorrowos-proxy/[...path]/route.ts or a rewrite + route handler
227
+ // Forward method, headers, and body to http://127.0.0.1:3001/<path>
228
+ // Return the upstream status, headers, and body to the browser.
229
+ ```
230
+
231
+ Preferred UX: proxy **`/`** itself (rewrites) so the Preview URL shows the Control Panel at `/`, not only under `/api/...`.
232
+
233
+ 4. Start **both** processes for Preview (`dev:preview` pattern above).
234
+ 5. Document in the final summary: **Preview = Next shell + proxy; Publish = Node only.**
235
+
236
+ **Forbidden:**
237
+
238
+ - Making Next the Production CMS or the only server after Publish.
239
+ - Deleting TomorrowOS `server.ts` because Preview needed Next.
240
+ - Claiming setup complete if Production entrypoint is still `next start`.
241
+
242
+ ### `vercel.json` (recommended minimum — Production)
178
243
 
179
244
  Adapt to the project’s current Vercel Node-server / Fluid conventions. At minimum ensure:
180
245
 
@@ -186,23 +251,25 @@ Adapt to the project’s current Vercel Node-server / Fluid conventions. At mini
186
251
 
187
252
  Also:
188
253
 
189
- 1. Project → **Fluid compute** enabled (required for WebSockets).
190
- 2. Framework Preset: **Other** (or Node) — **not** Next.js unless wrapping is intentional and still exposes TomorrowOS `listen()`.
191
- 3. Install / Start commands must end in running the CMS server (e.g. `npm run start` or Vercel’s detected `server.ts` entry).
254
+ 1. Project → **Fluid compute** enabled (required for WebSockets on Publish).
255
+ 2. Framework Preset for **Production:** **Other** (or Node) — **not** Next.js as the live CMS.
256
+ 3. Install / Start commands for **Production** must end in running the CMS Node server (e.g. `npm run start` `tsx server.ts`).
192
257
  4. Do **not** enable “Output Directory” static-only hosting for this CMS.
258
+ 5. If the project also contains a Next Preview shell, configure Vercel so **Production Deploy ignores Next as the app** (Node `server.ts` / `export default server` remains the capture path). Document any dashboard setting you change.
193
259
 
194
- If Vercel’s UI and this file disagree on bundling, **prefer whatever keeps a single long-lived Node HTTP server with WebSocket upgrade on `/`**. Document the adaptation in the final summary.
260
+ If Vercel’s UI and this file disagree on bundling, **prefer whatever keeps a single long-lived Node HTTP server with WebSocket upgrade on `/` for Production**. Document the adaptation in the final summary.
195
261
 
196
262
  ### WebSocket / multi-instance note
197
263
 
198
264
  - Device sockets are pinned to **one** function/instance. TomorrowOS device maps are **in-memory**.
199
265
  - For large fleets on multi-instance Vercel, prefer **one stable instance / region**, or accept that reconnects may land on another instance until the product adds shared presence (out of scope for this setup).
200
266
  - Always use **Postgres** (Supabase or Neon) so pairings/playlists survive cold starts and deploys.
267
+ - **Pair TVs to the Production HTTPS origin**, not the v0 Preview URL.
201
268
 
202
269
  ### What not to create
203
270
 
204
271
  - ❌ `.replit`, `.replit-artifact`
205
- - ❌ Next.js `app/` / `pages/` CMS
272
+ - ❌ Next.js as the **Production** CMS (App Router replacing TomorrowOS)
206
273
  - ❌ Separate “API project” without the Control Panel
207
274
  - ❌ Committing database passwords, Cloudinary secrets, or Blob tokens
208
275
 
@@ -309,16 +376,47 @@ Only proceed after the user explicitly confirms.
309
376
  >
310
377
  > Reply with **1**, **2**, or **3** (or “Cloudinary” / “Vercel Blob” / “local”).
311
378
 
312
- ### Step B — If **1 / Cloudinary** (same Question 2)
379
+ ### Step B — If **1 / Cloudinary** (same Question 2 — sequential prompts)
313
380
 
314
- Immediately collect and store as **Vercel Environment Variables**:
381
+ > **Critical UX rule:** Collect Cloudinary credentials in **separate messages**. Never ask for API key and API secret in the same message or the same form field group.
382
+
383
+ #### B1 — Cloud name (ask alone)
384
+
385
+ **Ask exactly:**
315
386
 
316
- - `CLOUDINARY_CLOUD_NAME`
317
- - `CLOUDINARY_API_KEY`
318
- - `CLOUDINARY_API_SECRET`
319
- - Optional: `CLOUDINARY_FOLDER` (e.g. `tomorrowos`)
387
+ > Paste your Cloudinary **Cloud name** only (`CLOUDINARY_CLOUD_NAME`).
388
+ > Find it at [cloudinary.com/console](https://cloudinary.com/console) → Dashboard.
320
389
 
321
- Do **not** invent placeholders. Do **not** proceed to Question 3 until the three required vars exist. Do **not** run a formal Cloudinary upload test unless the user asks.
390
+ Save as Vercel Env Var `CLOUDINARY_CLOUD_NAME`. Do **not** ask for key or secret yet.
391
+
392
+ #### B2 — API key (ask alone — after B1 is saved)
393
+
394
+ **Ask exactly:**
395
+
396
+ > Paste your Cloudinary **API Key** only (`CLOUDINARY_API_KEY`).
397
+ > Do not paste the API Secret in this step.
398
+
399
+ Save as Vercel Env Var `CLOUDINARY_API_KEY`. Do **not** ask for the secret in this message.
400
+
401
+ #### B3 — API secret (ask alone — after B2 is saved)
402
+
403
+ **Ask exactly:**
404
+
405
+ > Paste your Cloudinary **API Secret** only (`CLOUDINARY_API_SECRET`).
406
+ > This is the last Cloudinary credential.
407
+
408
+ Save as Vercel Env Var `CLOUDINARY_API_SECRET`.
409
+
410
+ Optional fourth message (only if useful):
411
+
412
+ > Optional: Cloudinary folder name (`CLOUDINARY_FOLDER`, e.g. `tomorrowos`). Press Enter to skip.
413
+
414
+ **You must then:**
415
+
416
+ 1. Confirm all three required Env Vars exist: `CLOUDINARY_CLOUD_NAME`, `CLOUDINARY_API_KEY`, `CLOUDINARY_API_SECRET`.
417
+ 2. **Do not** invent placeholders.
418
+ 3. **Do not** proceed to Question 3 until all three exist.
419
+ 4. **Do not** run a formal Cloudinary upload test unless the user asks.
322
420
 
323
421
  The SDK auto-detects these env vars.
324
422
 
@@ -361,6 +459,11 @@ import { put } from "@vercel/blob";
361
459
  ## Question 3 — Brand (`brand.json` only)
362
460
 
363
461
  > Updates **only** `brand.json`. Does **not** change Vercel project settings, Env Vars, or `server.ts` store wiring beyond what Q1–Q2 already required.
462
+ >
463
+ > **IRON RULE — website URL ≠ build a website / login / auth CMS.**
464
+ > If the user pastes a URL (or says “make it look like this site”), that input is **reference material for `brand.json` only** (name, colours, fonts, logo, tagline).
465
+ > **Do not** scaffold a login page, signup, OAuth, gated dashboard, or copy the reference site’s IA/pages — **unless the user explicitly asks for CMS login / auth**.
466
+ > Default TomorrowOS Control Panel has **no login**. Keep it that way.
364
467
 
365
468
  ### Step A — Ask branding input
366
469
 
@@ -368,8 +471,9 @@ import { put } from "@vercel/blob";
368
471
 
369
472
  > Let’s brand your TomorrowOS experience. You can answer in either way:
370
473
  >
371
- > **Option A — Website URL only**
372
- > Paste **one public website URL** (your company / venue site). I will infer colours, fonts, name, tagline, and logo from that page and write `brand.json` to match its look.
474
+ > **Option A — Website URL only (brand reference)**
475
+ > Paste **one public website URL**. I will use it **only** to infer colours, fonts, name, tagline, and logo for **`brand.json`**.
476
+ > I will **not** rebuild that website, add a login page, or change Control Panel features — unless you explicitly ask for login/auth later.
373
477
  >
374
478
  > **Option B — Manual fields**
375
479
  > Provide:
@@ -386,7 +490,8 @@ import { put } from "@vercel/blob";
386
490
  **You must:**
387
491
 
388
492
  1. **Fetch and inspect** the page (HTTP GET the URL; follow one redirect if needed). Do not invent colours.
389
- 2. **Derive branding** using this priority order:
493
+ 2. **Write / update `brand.json` only** from what you infer. Touch nothing else for this step (no new pages, no auth, no Next marketing site, no login UI).
494
+ 3. **Derive branding** using this priority order:
390
495
  - **Name:** `<title>`, `og:site_name`, or prominent header / logo `alt` text (trim to ≤ 60 chars).
391
496
  - **Tagline:** `meta[name="description"]`, `og:description`, or first hero subtitle (≤ 120 chars).
392
497
  - **Primary colour:** `meta[name="theme-color"]`, CSS `--primary` / `--brand` variables, or dominant accent from linked stylesheets / inline styles (convert to `#RRGGBB`).
@@ -395,10 +500,20 @@ import { put } from "@vercel/blob";
395
500
  - **Secondary colour:** muted border / secondary button colour, or a tint of the primary.
396
501
  - **Font:** first `font-family` on `body` (strip quotes; default `Inter` if generic system stack).
397
502
  - **Logo:** prefer `og:image`, then `link[rel="icon"]` / apple-touch-icon, then header `<img>` logo. Download into `./assets/logo.png` or `./assets/logo.svg` and set `logoPath` accordingly. If only favicon exists, use it and note size in the summary.
398
- 3. **Contrast check:** if background is dark, set `activationScreen.theme` to `"dark"` and add `logoPathOnDark` when a light logo variant exists.
399
- 4. **Infer `cms.useCase`** from page content when obvious (restaurant / retail / corporate / etc.); otherwise `"other"`.
400
- 5. **Show a one-line summary** to the user (name + primary hex + logo path) before or while writing the file — do not ask a second branding question unless fetch failed.
401
- 6. If the URL is unreachable, blocked, or has no usable styles, say so and fall back to **Option B** manual questions for missing fields only.
503
+ 4. **Contrast check:** if background is dark, set `activationScreen.theme` to `"dark"` and add `logoPathOnDark` when a light logo variant exists.
504
+ 5. **Infer `cms.useCase`** from page content when obvious (restaurant / retail / corporate / etc.); otherwise `"other"`.
505
+ 6. **Show a one-line summary** to the user (name + primary hex + logo path) before or while writing the file — do not ask a second branding question unless fetch failed.
506
+ 7. If the URL is unreachable, blocked, or has no usable styles, say so and fall back to **Option B** manual questions for missing fields only.
507
+
508
+ **Explicitly forbidden when the user only gave a URL (no login request):**
509
+
510
+ - ❌ Login / signup / “Sign in to continue” screens
511
+ - ❌ Auth middleware, sessions, cookies, OAuth, password forms
512
+ - ❌ Cloning the reference site as a marketing landing or multi-page CMS
513
+ - ❌ Gating the Control Panel behind authentication
514
+ - ❌ Treating “make it like this site” as “rebuild this site”
515
+
516
+ If the reference site itself is a login page, **still only extract visual brand tokens** (colours/logo/name) into `brand.json` — do **not** recreate that login flow.
402
517
 
403
518
  ### Step C — Write `brand.json`
404
519
 
@@ -430,7 +545,7 @@ Validate mentally against `brand.schema.json`. Minimum:
430
545
  }
431
546
  ```
432
547
 
433
- Always set **`cms.hostingTarget`: `"vercel"`** without asking. Default `expectedScreens` to `5` unless the user already volunteered a number. Set `cmsEndpoint` only if they already know the public `https://…vercel.app` URL; otherwise tell them to pair TVs with the deployed HTTPS origin (player maps `https://` → `wss://`).
548
+ Always set **`cms.hostingTarget`: `"vercel"`** without asking. Default `expectedScreens` to `5` unless the user already volunteered a number. Set `cmsEndpoint` only if they already know the public `https://…vercel.app` URL; otherwise tell them to pair TVs with the **Published** HTTPS origin (player maps `https://` → `wss://`).
434
549
 
435
550
  ---
436
551
 
@@ -446,11 +561,12 @@ npx @tomorrowos/sdk@latest init .
446
561
 
447
562
  Then:
448
563
 
449
- 1. Remove leftover Next.js / other frameworks so TomorrowOS is sole app.
450
- 2. Apply Vercel-adapted `server.ts` (**export `listen()` HTTP server**).
451
- 3. Add/adjust `vercel.json` with **`fluid: true`** (or enable Fluid in the dashboard).
452
- 4. Do **not** add `.replit*`.
453
- 5. If Q2 = Vercel Blob: install `@vercel/blob` and wire the upload bridge.
564
+ 1. Keep TomorrowOS `server.ts` as the **Production** app (**export `listen()` HTTP server**).
565
+ 2. If the environment is **v0 / Next Preview**, add the **Preview adapter** (Next shell + reverse proxy + internal TomorrowOS port). Do **not** make Next the Production CMS.
566
+ 3. Add/adjust `vercel.json` with **`fluid: true`** (or enable Fluid in the dashboard) for Publish.
567
+ 4. Ensure Production start command is **`npm run start` → `tsx server.ts`** (or equivalent Node), **not** `next start`.
568
+ 5. Do **not** add `.replit*`.
569
+ 6. If Q2 = Vercel Blob: install `@vercel/blob` and wire the upload bridge.
454
570
 
455
571
  ### B. Environment Variables (Vercel dashboard + local)
456
572
 
@@ -460,9 +576,12 @@ Then:
460
576
  | `DATABASE_URL` | If Q1 = Neon | **Pooled** Neon string |
461
577
  | `TOMORROWOS_STORE` | Yes | `supabase`, `postgres`, or `sqlite` |
462
578
  | `DATABASE_SSL` | Yes (typical) | `true` |
463
- | `CLOUDINARY_*` | If Q2 = Cloudinary | Three keys |
579
+ | `CLOUDINARY_CLOUD_NAME` | If Q2 = Cloudinary | Collected alone (B1) |
580
+ | `CLOUDINARY_API_KEY` | If Q2 = Cloudinary | Collected alone after cloud name (B2) |
581
+ | `CLOUDINARY_API_SECRET` | If Q2 = Cloudinary | Collected alone after API key (B3) |
464
582
  | `BLOB_READ_WRITE_TOKEN` | If Q2 = Vercel Blob | From Vercel Blob store |
465
- | `PORT` | Optional | Vercel injects as needed |
583
+ | `TOMORROWOS_INTERNAL_PORT` | Preview only | e.g. `3001` when Next owns `:3000` |
584
+ | `PORT` | Optional | Vercel injects for Production |
466
585
 
467
586
  Mirror non-secrets in `.env` for local `npm run dev` if helpful; never commit secrets.
468
587
 
@@ -471,34 +590,46 @@ Mirror non-secrets in `.env` for local `npm run dev` if helpful; never commit se
471
590
  ```bash
472
591
  npm install
473
592
  npm run start
474
- # or: vercel dev
593
+ # Preview / v0: npm run dev:preview (Next + TomorrowOS internal)
475
594
  ```
476
595
 
477
596
  Confirm logs: `[TomorrowOS] listening on http://0.0.0.0:…`
478
- Confirm browser: Control Panel at `/`.
597
+ Confirm browser:
598
+
599
+ - **Preview:** Control Panel visible through the Next proxy at `/`
600
+ - **Production start:** Control Panel served directly by TomorrowOS at `/`
479
601
 
480
- ### D. Deploy
602
+ ### D. Deploy (Publish — Node only)
481
603
 
482
604
  ```bash
483
605
  vercel --prod
484
606
  # or git push → Vercel Git integration
485
607
  ```
486
608
 
609
+ Before finishing, verify Production settings:
610
+
611
+ 1. Start / entry = **Node `TomorrowOS.listen`** (`npm run start` / exported `server`)
612
+ 2. **Not** `next start` as Production
613
+ 3. Fluid compute on
614
+ 4. Env Vars present for Production environment
615
+
487
616
  Tell the user:
488
617
 
489
- - **Control Panel:** `https://YOUR-PROJECT.vercel.app`
618
+ - **Control Panel (Publish):** `https://YOUR-PROJECT.vercel.app`
490
619
  - **TV CMS endpoint:** same HTTPS origin (players use `wss://`)
620
+ - **Preview:** Next shell is for v0 only — do not pair devices to Preview
491
621
 
492
622
  ### E. Minimal verification only
493
623
 
494
624
  **Do:**
495
625
 
496
- 1. Control Panel HTML at `/`
497
- 2. Server status: Media OK when Cloudinary or Blob configured
498
- 3. Database: if ERROR shows **`ENETUNREACH` … IPv6 … `:5432`**, switch to **pooled** URL (Supabase `:6543` or Neon pooler) and redeploy — call this out explicitly to the user
499
- 4. Fluid compute on
626
+ 1. Preview: Control Panel HTML at `/` (via Next proxy if applicable)
627
+ 2. Publish: Control Panel HTML at `/` from **Node** TomorrowOS
628
+ 3. Server status: Media OK when Cloudinary or Blob configured
629
+ 4. Database: if ERROR shows **`ENETUNREACH` … IPv6 … `:5432`**, switch to **pooled** URL (Supabase `:6543` or Neon pooler) and redeploy — call this out explicitly to the user
630
+ 5. Fluid compute on for Production
500
631
 
501
- **Do not block** on long WebSocket/device pairing tests unless the user asks.
632
+ **Do not block** on long WebSocket/device pairing tests unless the user asks. Prefer pairing against **Publish**, not Preview.
502
633
 
503
634
  ---
504
635
 
@@ -506,15 +637,19 @@ Tell the user:
506
637
 
507
638
  | Symptom | Likely cause | Fix |
508
639
  |---------|--------------|-----|
640
+ | Preview blank but logs show TomorrowOS listening | v0 Next intercepts public port | Add Next reverse proxy → internal TomorrowOS port |
641
+ | Preview works, Publish broken / Next-only | Production still on Next | Switch Production entry to `tsx server.ts` / `export default server` |
509
642
  | `ENETUNREACH` / IPv6 / `:5432` | Direct Postgres URL | Use **pooled** URL (Supabase `:6543` or Neon pooler) |
510
643
  | Control Panel OK, devices never stay paired after restart | SQLite / ephemeral disk | Use Supabase or Neon |
511
644
  | Uploads break / broken thumbs in prod | Local `public/uploads` | Cloudinary or Vercel Blob |
512
645
  | Blob uploads 401 / missing token | Blob not linked | Vercel Storage → Blob + `BLOB_READ_WRITE_TOKEN` |
513
- | WebSocket fails / devices drop immediately | Fluid off / static-only / Next wrapper | Enable Fluid; sole Node `listen()` server |
514
- | Agent created Next.js + TomorrowOS | Wrong scaffold | Remove Next; keep cms-starter |
646
+ | WebSocket fails on Publish | Fluid off / static-only / Next as Production | Enable Fluid; sole Node `listen()` for Publish |
647
+ | Devices fail only on Preview | Next proxy cannot upgrade `wss` | Expected pair on Publish URL |
648
+ | Agent mixed Cloudinary key+secret in one prompt | Violated Step B | Re-ask key alone, then secret alone |
515
649
  | Secrets in git | Mistake | Rotate keys; move to Vercel Env Vars |
516
650
  | Replit files present | Copied wrong protocol | Delete `.replit*`; use this file |
517
651
  | Brand colours wrong from URL | SPA / blocked fetch | Manual Option B for colours + logo |
652
+ | Agent built a login page after user pasted a URL | Misread brand reference as product scope | Remove login; keep starter Control Panel; apply URL only to `brand.json` |
518
653
 
519
654
  ---
520
655
 
@@ -527,8 +662,9 @@ Tell the user:
527
662
  | `hostingTarget` | `"here"` | `"vercel"` |
528
663
  | Database | Supabase (primary) | Supabase **or Neon** (pooled); SQLite demo only |
529
664
  | Media | Cloudinary or Replit Object Storage | Cloudinary **or Vercel Blob** |
530
- | WebSocket | Long-lived Node (Autoscale) | Fluid compute + Node server export |
531
- | Brand from URL | Not in Replit protocol | **Option A** in Question 3 — infer from website |
665
+ | Preview | Replit Preview / Autoscale Node | **Next shell + proxy** when v0 hardwires Next |
666
+ | Publish | Long-lived Node | **Node `TomorrowOS.listen` only** (not Next) |
667
+ | Brand from URL | Not in Replit protocol | **Option A** — infer **`brand.json` only**; never invent login |
532
668
 
533
669
  ---
534
670
 
@@ -536,12 +672,19 @@ Tell the user:
536
672
 
537
673
  After setup, report:
538
674
 
539
- 1. **Live URL** (Production)
540
- 2. **Env Vars set** (names only never values)
541
- 3. **Store:** Supabase pooler / Neon pooled / SQLite (with warning)
542
- 4. **Media:** Cloudinary, Vercel Blob, or local (with warning)
543
- 5. **Brand:** name + `hostingTarget: vercel` (+ note if derived from website URL)
544
- 6. **Adaptations from Replit protocol:** list (e.g. skipped `.replit`, exported HTTP server, Fluid, pooler URL, Blob bridge)
545
- 7. **Open action:** if Database still ERROR, paste the pooler instruction again
675
+ 1. **Live URL** (Production Publish)
676
+ 2. **Preview note:** Next proxy shell used / not needed
677
+ 3. **Env Vars set** (names only never values)
678
+ 4. **Store:** Supabase pooler / Neon pooled / SQLite (with warning)
679
+ 5. **Media:** Cloudinary, Vercel Blob, or local (with warning)
680
+ 6. **Brand:** name + `hostingTarget: vercel` (+ note if derived from website URL)
681
+ 7. **Runtime split:** Preview = ; Publish = Node TomorrowOS (confirm **not** Next)
682
+ 8. **Open action:** if Database still ERROR, paste the pooler instruction again
683
+
684
+ Protocol complete when:
685
+
686
+ - **Preview** shows the Control Panel at `/` (via Next proxy if required), **and**
687
+ - **Publish** is configured for pure Node `TomorrowOS.listen` with Fluid, **and**
688
+ - Q1–Q3 configuration is saved
546
689
 
547
- Protocol complete when the Control Panel renders at `/` on the deployed (or local) server and Q1–Q3 configuration is saved — even if a sandbox DB probe still flakes **after** the pooled URL is correctly set for Production.
690
+ — even if a sandbox DB probe still flakes **after** the pooled URL is correctly set for Production.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tomorrowos/sdk",
3
- "version": "0.9.22",
3
+ "version": "0.9.24",
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",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "my-cms",
3
- "version": "0.9.22",
3
+ "version": "0.9.24",
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.22",
16
+ "@tomorrowos/sdk": "^0.9.24",
17
17
  "dotenv": "^17.2.3",
18
18
  "tsx": "^4.19.0"
19
19
  },