@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 +4 -3
- package/VERCEL_SETUP.md +209 -66
- package/package.json +1 -1
- package/templates/cms-starter/package.json +2 -2
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 **
|
|
76
|
-
`
|
|
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.
|
|
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:
|
|
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
|
|
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
|
-
**
|
|
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.
|
|
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
|
|
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. **
|
|
86
|
-
12.
|
|
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
|
-
###
|
|
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
|
-
"
|
|
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
|
-
|
|
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`
|
|
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
|
|
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
|
-
###
|
|
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
|
|
191
|
-
3. Install / Start commands must end in running the CMS server (e.g. `npm run start`
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
317
|
-
|
|
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
|
-
|
|
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
|
|
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. **
|
|
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
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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
|
|
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.
|
|
450
|
-
2.
|
|
451
|
-
3. Add/adjust `vercel.json` with **`fluid: true`** (or enable Fluid in the dashboard).
|
|
452
|
-
4.
|
|
453
|
-
5.
|
|
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
|
-
| `
|
|
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
|
-
| `
|
|
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
|
-
#
|
|
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:
|
|
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.
|
|
498
|
-
3.
|
|
499
|
-
4.
|
|
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
|
|
514
|
-
|
|
|
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
|
-
|
|
|
531
|
-
|
|
|
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. **
|
|
541
|
-
3. **
|
|
542
|
-
4. **
|
|
543
|
-
5. **
|
|
544
|
-
6. **
|
|
545
|
-
7. **
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
16
|
+
"@tomorrowos/sdk": "^0.9.24",
|
|
17
17
|
"dotenv": "^17.2.3",
|
|
18
18
|
"tsx": "^4.19.0"
|
|
19
19
|
},
|