@tomorrowos/sdk 0.9.22 → 0.9.23

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 **infer from a website URL**). 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.2` — pairs with `@tomorrowos/sdk` packages that ship `templates/cms-starter` and this file.
9
9
 
10
10
  ## User trigger (copy-paste)
11
11
 
@@ -17,6 +17,7 @@ Ask only the questions listed in VERCEL_SETUP.md, in order. Do not use LLM_PROMP
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
19
  Question 3 only updates brand.json. 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,10 @@ 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.
87
108
 
88
109
  **Question order:** Q1 → Q2 → Q3 → execution checklist.
89
110
 
@@ -91,12 +112,13 @@ Then ask **Question 1**.
91
112
 
92
113
  ## Runtime & Vercel deploy rules (mandatory)
93
114
 
94
- ### Runtime
115
+ ### Production runtime (Publish)
95
116
 
96
117
  - Serve with **`tsx server.ts`** (or compiled Node that still calls `TomorrowOS.listen` with `staticRoot`).
97
118
  - Keep **`@tomorrowos/sdk`** and put **`tsx` in `dependencies`** (not only `devDependencies`) if production runs via `tsx`.
98
119
  - Node **20+**.
99
120
  - **Do not** replace the SDK with a hand-rolled server that drops WebSocket upgrades.
121
+ - **Forbidden as the Production Deploy entrypoint:** `next start`, static export only, or any command that does not start `TomorrowOS.listen(...)`.
100
122
 
101
123
  ### Expected `package.json` scripts
102
124
 
@@ -104,14 +126,21 @@ Then ask **Question 1**.
104
126
  {
105
127
  "scripts": {
106
128
  "dev": "tsx watch server.ts",
107
- "start": "tsx server.ts"
129
+ "dev:preview": "concurrently -k \"npm:dev:tomorrowos\" \"npm:dev:next\"",
130
+ "dev:tomorrowos": "cross-env PORT=3001 TOMORROWOS_INTERNAL_PORT=3001 tsx watch server.ts",
131
+ "dev:next": "next dev -p 3000",
132
+ "start": "tsx server.ts",
133
+ "build": "echo \"Production is Node — no Next production build required\""
108
134
  }
109
135
  }
110
136
  ```
111
137
 
112
- **Forbidden as the Deploy entrypoint:** `next start`, static export only, or any command that does not start `TomorrowOS.listen(...)`.
138
+ Adapt script names / process runners as needed. **Invariant:**
139
+
140
+ - **`npm start` / Production** = TomorrowOS Node only.
141
+ - **Preview / v0** = Next on public port + TomorrowOS on internal port + proxy.
113
142
 
114
- ### Minimal dependencies
143
+ ### Minimal dependencies (Production)
115
144
 
116
145
  ```json
117
146
  {
@@ -137,9 +166,11 @@ Then ask **Question 1**.
137
166
 
138
167
  Use the latest published `@tomorrowos/sdk` when scaffolding (do not invent versions).
139
168
 
140
- Add `@vercel/blob` to `dependencies` only when the user chooses **Vercel Blob** in Question 2.
169
+ Add `@vercel/blob` only when the user chooses **Vercel Blob** in Question 2.
141
170
 
142
- ### `server.ts` pattern (Vercel-adapted)
171
+ 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.
172
+
173
+ ### `server.ts` pattern (Vercel-adapted — Production)
143
174
 
144
175
  ```ts
145
176
  import "dotenv/config";
@@ -162,8 +193,13 @@ const store = createTomorrowOSStore({
162
193
 
163
194
  const tomorrowos = new TomorrowOS({ brand, store });
164
195
 
196
+ const port =
197
+ Number(process.env.TOMORROWOS_INTERNAL_PORT) ||
198
+ Number(process.env.PORT) ||
199
+ 3000;
200
+
165
201
  const server = tomorrowos.listen({
166
- port: Number(process.env.PORT) || 3000,
202
+ port,
167
203
  host: "0.0.0.0",
168
204
  staticRoot: join(__dirname, "public")
169
205
  });
@@ -172,9 +208,37 @@ const server = tomorrowos.listen({
172
208
  export default server;
173
209
  ```
174
210
 
211
+ - **Production:** `PORT` from Vercel → TomorrowOS listens on that port (no Next).
212
+ - **Preview:** set `TOMORROWOS_INTERNAL_PORT=3001` (or similar) so Next can own `:3000` and proxy inward.
213
+
175
214
  Load env with `dotenv` for local/`vercel dev`. On production Deploy, Vercel injects Environment Variables natively.
176
215
 
177
- ### `vercel.json` (recommended minimum)
216
+ ### Preview adapter (v0 / Next reverse proxy) — required when Preview is Next-hardwired
217
+
218
+ Detect Preview / v0 (Next already present, or Preview blank while TomorrowOS logs “listening”). Then:
219
+
220
+ 1. Keep a **minimal** Next app (App Router or Pages — whichever the sandbox expects).
221
+ 2. TomorrowOS listens on **`127.0.0.1:TOMORROWOS_INTERNAL_PORT`** (default **3001**).
222
+ 3. Add a **catch-all** Next route (or middleware) that proxies HTTP to TomorrowOS:
223
+
224
+ ```ts
225
+ // Example: app/api/tomorrowos-proxy/[...path]/route.ts or a rewrite + route handler
226
+ // Forward method, headers, and body to http://127.0.0.1:3001/<path>
227
+ // Return the upstream status, headers, and body to the browser.
228
+ ```
229
+
230
+ Preferred UX: proxy **`/`** itself (rewrites) so the Preview URL shows the Control Panel at `/`, not only under `/api/...`.
231
+
232
+ 4. Start **both** processes for Preview (`dev:preview` pattern above).
233
+ 5. Document in the final summary: **Preview = Next shell + proxy; Publish = Node only.**
234
+
235
+ **Forbidden:**
236
+
237
+ - Making Next the Production CMS or the only server after Publish.
238
+ - Deleting TomorrowOS `server.ts` because Preview needed Next.
239
+ - Claiming setup complete if Production entrypoint is still `next start`.
240
+
241
+ ### `vercel.json` (recommended minimum — Production)
178
242
 
179
243
  Adapt to the project’s current Vercel Node-server / Fluid conventions. At minimum ensure:
180
244
 
@@ -186,23 +250,25 @@ Adapt to the project’s current Vercel Node-server / Fluid conventions. At mini
186
250
 
187
251
  Also:
188
252
 
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).
253
+ 1. Project → **Fluid compute** enabled (required for WebSockets on Publish).
254
+ 2. Framework Preset for **Production:** **Other** (or Node) — **not** Next.js as the live CMS.
255
+ 3. Install / Start commands for **Production** must end in running the CMS Node server (e.g. `npm run start` `tsx server.ts`).
192
256
  4. Do **not** enable “Output Directory” static-only hosting for this CMS.
257
+ 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
258
 
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.
259
+ 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
260
 
196
261
  ### WebSocket / multi-instance note
197
262
 
198
263
  - Device sockets are pinned to **one** function/instance. TomorrowOS device maps are **in-memory**.
199
264
  - 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
265
  - Always use **Postgres** (Supabase or Neon) so pairings/playlists survive cold starts and deploys.
266
+ - **Pair TVs to the Production HTTPS origin**, not the v0 Preview URL.
201
267
 
202
268
  ### What not to create
203
269
 
204
270
  - ❌ `.replit`, `.replit-artifact`
205
- - ❌ Next.js `app/` / `pages/` CMS
271
+ - ❌ Next.js as the **Production** CMS (App Router replacing TomorrowOS)
206
272
  - ❌ Separate “API project” without the Control Panel
207
273
  - ❌ Committing database passwords, Cloudinary secrets, or Blob tokens
208
274
 
@@ -309,16 +375,47 @@ Only proceed after the user explicitly confirms.
309
375
  >
310
376
  > Reply with **1**, **2**, or **3** (or “Cloudinary” / “Vercel Blob” / “local”).
311
377
 
312
- ### Step B — If **1 / Cloudinary** (same Question 2)
378
+ ### Step B — If **1 / Cloudinary** (same Question 2 — sequential prompts)
313
379
 
314
- Immediately collect and store as **Vercel Environment Variables**:
380
+ > **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.
315
381
 
316
- - `CLOUDINARY_CLOUD_NAME`
317
- - `CLOUDINARY_API_KEY`
318
- - `CLOUDINARY_API_SECRET`
319
- - Optional: `CLOUDINARY_FOLDER` (e.g. `tomorrowos`)
382
+ #### B1 — Cloud name (ask alone)
320
383
 
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.
384
+ **Ask exactly:**
385
+
386
+ > Paste your Cloudinary **Cloud name** only (`CLOUDINARY_CLOUD_NAME`).
387
+ > Find it at [cloudinary.com/console](https://cloudinary.com/console) → Dashboard.
388
+
389
+ Save as Vercel Env Var `CLOUDINARY_CLOUD_NAME`. Do **not** ask for key or secret yet.
390
+
391
+ #### B2 — API key (ask alone — after B1 is saved)
392
+
393
+ **Ask exactly:**
394
+
395
+ > Paste your Cloudinary **API Key** only (`CLOUDINARY_API_KEY`).
396
+ > Do not paste the API Secret in this step.
397
+
398
+ Save as Vercel Env Var `CLOUDINARY_API_KEY`. Do **not** ask for the secret in this message.
399
+
400
+ #### B3 — API secret (ask alone — after B2 is saved)
401
+
402
+ **Ask exactly:**
403
+
404
+ > Paste your Cloudinary **API Secret** only (`CLOUDINARY_API_SECRET`).
405
+ > This is the last Cloudinary credential.
406
+
407
+ Save as Vercel Env Var `CLOUDINARY_API_SECRET`.
408
+
409
+ Optional fourth message (only if useful):
410
+
411
+ > Optional: Cloudinary folder name (`CLOUDINARY_FOLDER`, e.g. `tomorrowos`). Press Enter to skip.
412
+
413
+ **You must then:**
414
+
415
+ 1. Confirm all three required Env Vars exist: `CLOUDINARY_CLOUD_NAME`, `CLOUDINARY_API_KEY`, `CLOUDINARY_API_SECRET`.
416
+ 2. **Do not** invent placeholders.
417
+ 3. **Do not** proceed to Question 3 until all three exist.
418
+ 4. **Do not** run a formal Cloudinary upload test unless the user asks.
322
419
 
323
420
  The SDK auto-detects these env vars.
324
421
 
@@ -430,7 +527,7 @@ Validate mentally against `brand.schema.json`. Minimum:
430
527
  }
431
528
  ```
432
529
 
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://`).
530
+ 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
531
 
435
532
  ---
436
533
 
@@ -446,11 +543,12 @@ npx @tomorrowos/sdk@latest init .
446
543
 
447
544
  Then:
448
545
 
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.
546
+ 1. Keep TomorrowOS `server.ts` as the **Production** app (**export `listen()` HTTP server**).
547
+ 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.
548
+ 3. Add/adjust `vercel.json` with **`fluid: true`** (or enable Fluid in the dashboard) for Publish.
549
+ 4. Ensure Production start command is **`npm run start` → `tsx server.ts`** (or equivalent Node), **not** `next start`.
550
+ 5. Do **not** add `.replit*`.
551
+ 6. If Q2 = Vercel Blob: install `@vercel/blob` and wire the upload bridge.
454
552
 
455
553
  ### B. Environment Variables (Vercel dashboard + local)
456
554
 
@@ -460,9 +558,12 @@ Then:
460
558
  | `DATABASE_URL` | If Q1 = Neon | **Pooled** Neon string |
461
559
  | `TOMORROWOS_STORE` | Yes | `supabase`, `postgres`, or `sqlite` |
462
560
  | `DATABASE_SSL` | Yes (typical) | `true` |
463
- | `CLOUDINARY_*` | If Q2 = Cloudinary | Three keys |
561
+ | `CLOUDINARY_CLOUD_NAME` | If Q2 = Cloudinary | Collected alone (B1) |
562
+ | `CLOUDINARY_API_KEY` | If Q2 = Cloudinary | Collected alone after cloud name (B2) |
563
+ | `CLOUDINARY_API_SECRET` | If Q2 = Cloudinary | Collected alone after API key (B3) |
464
564
  | `BLOB_READ_WRITE_TOKEN` | If Q2 = Vercel Blob | From Vercel Blob store |
465
- | `PORT` | Optional | Vercel injects as needed |
565
+ | `TOMORROWOS_INTERNAL_PORT` | Preview only | e.g. `3001` when Next owns `:3000` |
566
+ | `PORT` | Optional | Vercel injects for Production |
466
567
 
467
568
  Mirror non-secrets in `.env` for local `npm run dev` if helpful; never commit secrets.
468
569
 
@@ -471,34 +572,46 @@ Mirror non-secrets in `.env` for local `npm run dev` if helpful; never commit se
471
572
  ```bash
472
573
  npm install
473
574
  npm run start
474
- # or: vercel dev
575
+ # Preview / v0: npm run dev:preview (Next + TomorrowOS internal)
475
576
  ```
476
577
 
477
578
  Confirm logs: `[TomorrowOS] listening on http://0.0.0.0:…`
478
- Confirm browser: Control Panel at `/`.
579
+ Confirm browser:
479
580
 
480
- ### D. Deploy
581
+ - **Preview:** Control Panel visible through the Next proxy at `/`
582
+ - **Production start:** Control Panel served directly by TomorrowOS at `/`
583
+
584
+ ### D. Deploy (Publish — Node only)
481
585
 
482
586
  ```bash
483
587
  vercel --prod
484
588
  # or git push → Vercel Git integration
485
589
  ```
486
590
 
591
+ Before finishing, verify Production settings:
592
+
593
+ 1. Start / entry = **Node `TomorrowOS.listen`** (`npm run start` / exported `server`)
594
+ 2. **Not** `next start` as Production
595
+ 3. Fluid compute on
596
+ 4. Env Vars present for Production environment
597
+
487
598
  Tell the user:
488
599
 
489
- - **Control Panel:** `https://YOUR-PROJECT.vercel.app`
600
+ - **Control Panel (Publish):** `https://YOUR-PROJECT.vercel.app`
490
601
  - **TV CMS endpoint:** same HTTPS origin (players use `wss://`)
602
+ - **Preview:** Next shell is for v0 only — do not pair devices to Preview
491
603
 
492
604
  ### E. Minimal verification only
493
605
 
494
606
  **Do:**
495
607
 
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
608
+ 1. Preview: Control Panel HTML at `/` (via Next proxy if applicable)
609
+ 2. Publish: Control Panel HTML at `/` from **Node** TomorrowOS
610
+ 3. Server status: Media OK when Cloudinary or Blob configured
611
+ 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
612
+ 5. Fluid compute on for Production
500
613
 
501
- **Do not block** on long WebSocket/device pairing tests unless the user asks.
614
+ **Do not block** on long WebSocket/device pairing tests unless the user asks. Prefer pairing against **Publish**, not Preview.
502
615
 
503
616
  ---
504
617
 
@@ -506,12 +619,15 @@ Tell the user:
506
619
 
507
620
  | Symptom | Likely cause | Fix |
508
621
  |---------|--------------|-----|
622
+ | Preview blank but logs show TomorrowOS listening | v0 Next intercepts public port | Add Next reverse proxy → internal TomorrowOS port |
623
+ | Preview works, Publish broken / Next-only | Production still on Next | Switch Production entry to `tsx server.ts` / `export default server` |
509
624
  | `ENETUNREACH` / IPv6 / `:5432` | Direct Postgres URL | Use **pooled** URL (Supabase `:6543` or Neon pooler) |
510
625
  | Control Panel OK, devices never stay paired after restart | SQLite / ephemeral disk | Use Supabase or Neon |
511
626
  | Uploads break / broken thumbs in prod | Local `public/uploads` | Cloudinary or Vercel Blob |
512
627
  | 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 |
628
+ | WebSocket fails on Publish | Fluid off / static-only / Next as Production | Enable Fluid; sole Node `listen()` for Publish |
629
+ | Devices fail only on Preview | Next proxy cannot upgrade `wss` | Expected pair on Publish URL |
630
+ | Agent mixed Cloudinary key+secret in one prompt | Violated Step B | Re-ask key alone, then secret alone |
515
631
  | Secrets in git | Mistake | Rotate keys; move to Vercel Env Vars |
516
632
  | Replit files present | Copied wrong protocol | Delete `.replit*`; use this file |
517
633
  | Brand colours wrong from URL | SPA / blocked fetch | Manual Option B for colours + logo |
@@ -527,7 +643,8 @@ Tell the user:
527
643
  | `hostingTarget` | `"here"` | `"vercel"` |
528
644
  | Database | Supabase (primary) | Supabase **or Neon** (pooled); SQLite demo only |
529
645
  | Media | Cloudinary or Replit Object Storage | Cloudinary **or Vercel Blob** |
530
- | WebSocket | Long-lived Node (Autoscale) | Fluid compute + Node server export |
646
+ | Preview | Replit Preview / Autoscale Node | **Next shell + proxy** when v0 hardwires Next |
647
+ | Publish | Long-lived Node | **Node `TomorrowOS.listen` only** (not Next) |
531
648
  | Brand from URL | Not in Replit protocol | **Option A** in Question 3 — infer from website |
532
649
 
533
650
  ---
@@ -536,12 +653,19 @@ Tell the user:
536
653
 
537
654
  After setup, report:
538
655
 
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
656
+ 1. **Live URL** (Production Publish)
657
+ 2. **Preview note:** Next proxy shell used / not needed
658
+ 3. **Env Vars set** (names only never values)
659
+ 4. **Store:** Supabase pooler / Neon pooled / SQLite (with warning)
660
+ 5. **Media:** Cloudinary, Vercel Blob, or local (with warning)
661
+ 6. **Brand:** name + `hostingTarget: vercel` (+ note if derived from website URL)
662
+ 7. **Runtime split:** Preview = ; Publish = Node TomorrowOS (confirm **not** Next)
663
+ 8. **Open action:** if Database still ERROR, paste the pooler instruction again
664
+
665
+ Protocol complete when:
666
+
667
+ - **Preview** shows the Control Panel at `/` (via Next proxy if required), **and**
668
+ - **Publish** is configured for pure Node `TomorrowOS.listen` with Fluid, **and**
669
+ - Q1–Q3 configuration is saved
546
670
 
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.
671
+ — 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.23",
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.23",
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.23",
17
17
  "dotenv": "^17.2.3",
18
18
  "tsx": "^4.19.0"
19
19
  },