@tomorrowos/sdk 0.9.31 → 0.9.32

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/VERCEL_SETUP.md CHANGED
@@ -5,8 +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.10` — pairs with `@tomorrowos/sdk` **0.9.31+** (`cms-starter-v0`: Fluid Publish + root Next Preview proxy).
9
- > **IRON RULE — configure immediately:** After the user answers **each** question, **finish that question’s configuration before asking the next**. Never batch “answer Q1–Q3 first, then configure everything.”
8
+ > Protocol id: `vercel-setup/1.8` — pairs with `@tomorrowos/sdk` **0.9.32+** (`templates/cms-starter-v0`, Fluid Functions WebSockets).
10
9
 
11
10
  ## User trigger (copy-paste)
12
11
 
@@ -17,14 +16,12 @@ Follow @tomorrowos/sdk VERCEL_SETUP.md and set up my TomorrowOS CMS.
17
16
  Ask only the questions listed in VERCEL_SETUP.md, in order. Do not use LLM_PROMPT.md or REPLIT_SETUP.md.
18
17
  Do not ask for OpenAI / Anthropic / any LLM API keys — TomorrowOS CMS setup does not require them.
19
18
  If any step contradicts Vercel, adapt to settings Vercel can accept (document the adaptation).
20
- CRITICAL UX: After I answer each question, configure that step immediately (Env popup / brand.json write) BEFORE asking the next question. Do not wait until all three questions are answered.
21
- Question 2 options MUST be exactly: 1 Cloudinary (recommended), 2 Vercel Blob, 3 local diskNEVER invent Supabase Storage or “disable media uploads”.
22
- Question 3 must offer Option A (website URL brand.json) OR Option B (name + colours + background + logo) — NEVER ask only for display name.
23
- 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.
24
- Do not add CMS login/auth unless I explicitly ask. Do not skip steps.
25
- Use Next.js only as a v0/Preview shell with a reverse proxy; Production Publish must remain Fluid Function TomorrowOS (api/index.ts).
26
- Before claiming Publish works: Framework Other, Fluid on, GET /status JSON, GET / Control Panel, WebSocket upgrade 101 on / or /api.
27
- Do not put startCommand or processes in vercel.json. Use cms-starter-v0 layout. Do not create a second TomorrowOS in api/ws.ts.
19
+ 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.
20
+ Question 3 only updates brand.json from colours/logo/name (a website URL is brand reference onlydo not add CMS login/auth unless the user explicitly asks). Do not skip steps.
21
+ Use Next.js only as a v0/Preview shell with a reverse proxy; Production Publish must remain pure Node TomorrowOS.listen (not Next).
22
+ Before claiming Publish works: Framework Preset must be Other/null (not Next.js), Fluid on, GET /status on the production URL must return JSON (not HTML), GET / must return the Control Panel, and WebSocket upgrade on / or /api must return 101 (not 200 HTML).
23
+ Do not put startCommand or processes in vercel.json (v0 schema rejects them).
24
+ Use cms-starter-v0 layout: cms-app.ts + api/index.ts (export default server) + cms-panel/ + rewrites to /api. Do not create a second TomorrowOS instance in api/ws.ts.
28
25
  ```
29
26
 
30
27
  Alternate triggers:
@@ -131,25 +128,15 @@ v0 Preview is often a **Next.js runtime** hardwired to `next dev`. A raw `tsx se
131
128
 
132
129
  ## Questionnaire scope (STRICT)
133
130
 
134
- Ask **only** these questions, in **this exact order**. After each answer, **configure that step immediately**, then proceed:
131
+ Ask **only** these questions, in **this exact order**:
135
132
 
136
- | Step | Section | When done, before next question |
137
- |------|---------|----------------------------------|
138
- | 1 | **Question 1** — Database (Supabase → Neon → SQLite) | Collect DB URL / confirm SQLite → set Env Vars (`SUPABASE_URL` or `DATABASE_URL` + auto `TOMORROWOS_STORE` / `DATABASE_SSL`) |
139
- | 2 | **Question 2** — Media (**Cloudinary recommended** → Vercel Blob → local) | If Cloudinary/Blob: **open Env popup immediately** and save tokens before Q3 |
140
- | 3 | **Question 3** — Brand (`brand.json`: name, colours, background, logo, or website URL) | Write `brand.json` immediately, then run final deploy checklist |
133
+ | Step | Section | When |
134
+ |------|---------|------|
135
+ | 1 | **Question 1** — Database (Supabase → Neon → SQLite) | Always |
136
+ | 2 | **Question 2** — Media storage (Cloudinary → Vercel Blob → local) | Always (after Q1) |
137
+ | 3 | **Question 3** — Brand / TomorrowOS look (`brand.json` only) | Always last |
141
138
 
142
- **Forbidden invented Question 2 options (never offer these):**
143
-
144
- - ❌ “Supabase Storage” / S3 bucket / `SUPABASE_SERVICE_ROLE_KEY` for media
145
- - ❌ “No media storage” / “disable uploads” / “remote URLs only” as a numbered choice
146
- - ❌ Replacing Cloudinary with Blob as the **recommended** option
147
-
148
- **Forbidden Question 3 shortcuts:**
149
-
150
- - ❌ Asking **only** for brand / product display name
151
- - ❌ Skipping background colour, primary colour, and logo when the user chose manual fields
152
- - ❌ Skipping Option A (website URL → infer full `brand.json`)
139
+ **That is the complete list.** Do **not** ask how many screens / devices.
153
140
 
154
141
  **Forbidden extra questions (never ask, even if the user skips a step):**
155
142
 
@@ -173,25 +160,25 @@ Then ask **Question 1**.
173
160
  ## Hard rules
174
161
 
175
162
  1. **Ask only VERCEL_SETUP.md questions 1–3.** Never use `LLM_PROMPT.md` or `REPLIT_SETUP.md` as the questionnaire. **Never** ask for OpenAI or other LLM API keys.
176
- 2. **Configure immediately after each answer.** Flow is `ask user answers configure that step ask next`. Do **not** collect all answers then configure in a batch at the end.
177
- 3. **Cloudinary:** as soon as the user picks Cloudinary, open **one** Vercel Env configuration popup / form with **all** Cloudinary fields together (cloud name + API key + API secret). Do **not** quiz one credential at a time in chat. Do **not** skip Cloudinary in favor of inventing Supabase Storage.
178
- 4. **Do not invent** Cloudinary credentials, database URLs, Vercel Blob tokens, brand colours, **or LLM API keys**.
179
- 5. **Postgres env naming:** prefer **`SUPABASE_URL`** when using Supabase; prefer **`DATABASE_URL`** when using Neon. `createTomorrowOSStore` accepts `SUPABASE_URL` then `DATABASE_URL`.
180
- 6. **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).
181
- 7. **Prefer `npx @tomorrowos/sdk init --hosting v0`** (template `cms-starter-v0`). For Replit/Railway use default `init` (`cms-starter`). Do not rebuild pairing / WebSocket / playlist APIs.
182
- 8. **Never commit secrets.** Use Vercel Environment Variables (+ optional non-secret flags in committed `.env.example`).
183
- 9. **Skip Replit-only files:** do **not** create `.replit`, `.replit-artifact`, or Replit `deploymentTarget`.
184
- 10. **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`.
185
- 11. **Enable Fluid compute** for Production WebSockets ([docs](https://vercel.com/docs/functions/websockets)). Confirm Project settings after scaffold.
186
- 12. **Production:** one Vercel Function exports `http.Server` from `TomorrowOS.listen` (`api/index.ts`). **Do not** make `next start` the Production entrypoint. **Do not** spin a second TomorrowOS in `api/ws.ts`.
187
- 13. After Q1–Q3 each configured: **install → deploy → minimal verify** (Control Panel + WS 101). Do not run a long test suite.
163
+ 2. **Cloudinary:** open **one** Vercel Env configuration popup / form with **all** Cloudinary fields together (cloud name + API key + API secret). Do **not** quiz the user one credential at a time in chat.
164
+ 3. **Do not invent** Cloudinary credentials, database URLs, Vercel Blob tokens, brand colours, **or LLM API keys**.
165
+ 4. **Postgres env naming:** prefer **`SUPABASE_URL`** when using Supabase; prefer **`DATABASE_URL`** when using Neon. `createTomorrowOSStore` accepts `SUPABASE_URL` then `DATABASE_URL`.
166
+ 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).
167
+ 6. **Prefer `npx @tomorrowos/sdk init --hosting v0`** (template `cms-starter-v0`: `cms-app.ts`, `api/index.ts`, `cms-panel/`, Fluid + rewrites). For Replit/Railway use default `init` (`cms-starter`). Do not rebuild pairing / WebSocket / playlist APIs.
168
+ 7. **Never commit secrets.** Use Vercel Environment Variables (+ optional non-secret flags in committed `.env.example`).
169
+ 8. **Skip Replit-only files:** do **not** create `.replit`, `.replit-artifact`, or Replit `deploymentTarget`.
170
+ 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`.
171
+ 10. **Enable Fluid compute** for Production WebSockets ([docs](https://vercel.com/docs/functions/websockets)). Confirm Project settings after scaffold.
172
+ 11. **Production:** one Vercel Function exports `http.Server` from `TomorrowOS.listen` (`api/index.ts`). **Do not** make `next start` the Production entrypoint. **Do not** spin a second TomorrowOS in `api/ws.ts`.
173
+ 12. **Preview:** if the environment is v0 / Next-hardwired, install the Next reverse-proxy shell (see **Preview adapter**). Do not leave Preview broken.
174
+ 13. After Q&A: **configure install → run/deploy → minimal verify** (Control Panel at `/` + WS 101). Do not run a long test suite.
188
175
  14. **No inventing CMS login.** A branding URL never means “build auth.” Only add login if the user **explicitly** requests it.
189
176
  15. **Skip ≠ invent.** If the user skips media choice or a credential, do not replace it with OpenAI/auth/login questions. Stay on Q1–Q3 only.
190
177
  16. **Neon / Supabase helpers are agent-owned.** After the user pastes the DB URL, **you** set `TOMORROWOS_STORE` and `DATABASE_SSL` automatically — **do not** ask the user to fill those two.
191
178
  17. **Publish gate:** do not mark setup complete while Production returns `404: NOT_FOUND`, `/status` returns HTML, **or** WebSocket upgrade returns 200 HTML instead of 101.
192
179
  18. **Scheme A is doc-driven:** Production = Fluid Function + `export default server` per Vercel WebSockets docs — Replit starter stays unchanged.
193
180
 
194
- **Question order:** Q1 → **configure Q1** → Q2 → **configure Q2** → Q3 → **write brand.json** → deploy checklist.
181
+ **Question order:** Q1 → Q2 → Q3 → execution checklist.
195
182
 
196
183
  ---
197
184
 
@@ -205,10 +192,10 @@ Then ask **Question 1**.
205
192
 
206
193
  | Layer | Preview (v0) | Publish (Production) |
207
194
  |-------|----------------|----------------------|
208
- | Host | **Root Next** `app/[[...slug]]/route.ts` proxies TomorrowOS `:3001` | **Vercel Function** `api/index.ts` → `export default server` |
209
- | Static UI | Proxied from TomorrowOS `cms-panel/` | Served by **`staticRoot`** inside `listen()` |
210
- | WebSocket | Optional / may fail in Preview | **Required** — Fluid Function upgrade |
211
- | Next files | **Root** `app/` + `next.config.mjs` (required for v0) | Ignored when Framework = Other + no-op `build` + Function entry |
195
+ | Host | Next.js shell + HTTP proxy | **Vercel Function** `api/index.ts` → `export default server` |
196
+ | Static UI | Proxied from TomorrowOS | Served by **`staticRoot`** (`cms-panel/`) inside `listen()` — **not** Vercel Output Directory |
197
+ | WebSocket | Optional / may fail in Preview | **Required** — Fluid Function upgrade (`wss://` → `/` rewritten to `/api`, or direct `/api`) |
198
+ | Next files | Allowed under `preview/` | **Must not** live at project root |
212
199
 
213
200
  **Root cause of “Control Panel works, TV cannot connect”:** HTTP can be served without WebSocket upgrades. Classic root-only `server.ts` / plain serverless handlers often return **200 HTML** on `Upgrade` instead of **101**. Official pattern is a Function that exports `http.Server` with `ws` attached ([docs](https://vercel.com/docs/functions/websockets)).
214
201
 
@@ -217,41 +204,48 @@ Then ask **Question 1**.
217
204
  **Production (Vercel / v0 starter):**
218
205
 
219
206
  ```
220
- cms-app.ts
221
- api/index.ts ← Publish Function
222
- server.ts ← local listen entry
223
- app/ Preview ONLY (Next proxy catch-all)
224
- layout.tsx
225
- [[...slug]]/route.ts
226
- next.config.mjs ← Preview only
227
- vercel.json
228
- cms-panel/
207
+ cms-app.ts ← shared TomorrowOS.listen + export const server
208
+ api/index.ts ← export { server as default } from "../cms-app.js"
209
+ server.ts ← local/Preview: same default export (npm start / internal port)
210
+ vercel.json fluid + rewrites + functions.maxDuration
211
+ package.json
229
212
  brand.json
230
- package.json "dev": "npm run dev:preview"
213
+ cms-panel/ CMS static UI (NOT named public/ — avoids static-output trap)
214
+ preview/ ← Next proxy for v0 Preview only
215
+ ```
216
+
217
+ **Preview only — keep ALL Next under `preview/`:**
218
+
219
+ ```
220
+ preview/
221
+ next.config.mjs
222
+ app/
223
+
231
224
  ```
232
225
 
233
226
  **Do NOT:**
234
227
 
235
- - ❌ Add `app/page.tsx` that returns null (v0 shows “Your v0 generation will show here”)
228
+ - ❌ Leave `next.config.mjs` + `app/` at project root
236
229
  - ❌ Set Output Directory to `public`, `cms-panel`, or `.`
237
- - ❌ Create a **second** TomorrowOS instance in `api/ws.ts`
238
- - ❌ Use `next build` / `next start` for Production
239
- - ❌ Put `startCommand` / `processes` in `vercel.json`
230
+ - ❌ Create a **second** TomorrowOS instance in `api/ws.ts` (separate isolate = broken pairing memory)
231
+ - ❌ Use `(req, res) =>` serverless handlers that drop `upgrade`
232
+ - ❌ Put `startCommand` / `processes` in `vercel.json` (v0 schema rejects them)
240
233
 
241
234
  **`package.json` scripts:**
242
235
 
243
236
  ```json
244
237
  {
245
238
  "scripts": {
246
- "dev": "npm run dev:preview",
239
+ "dev": "tsx watch server.ts",
247
240
  "dev:preview": "concurrently -k \"npm:dev:tomorrowos\" \"npm:dev:next\"",
248
241
  "dev:tomorrowos": "cross-env TOMORROWOS_INTERNAL_PORT=3001 tsx watch server.ts",
249
- "dev:next": "next dev -p 3000",
242
+ "dev:next": "cd preview && next dev -p 3000",
250
243
  "start": "tsx server.ts",
251
244
  "build": "node -e \"process.exit(0)\""
252
245
  }
253
246
  }
254
247
  ```
248
+
255
249
  - **Local / Railway-style:** `npm start` → `tsx server.ts` → SDK calls `server.listen` (no `VERCEL` env).
256
250
  - **Vercel Production:** Function loads `api/index.ts`; SDK skips `listen` when `process.env.VERCEL` is set and Vercel owns the socket.
257
251
 
@@ -272,7 +266,7 @@ const server = tomorrowos.listen({
272
266
  export { server, tomorrowos };
273
267
  ```
274
268
 
275
- SDK behaviour (0.9.30+):
269
+ SDK behaviour (0.9.32+):
276
270
 
277
271
  - Accepts WebSocket upgrades on `/`, `/api`, `/api/ws` (players may use either)
278
272
  - When `VERCEL` is set: **`autoListen` defaults to false** — do not bind a port yourself
@@ -374,7 +368,7 @@ If (1)+(2) pass but (3) fails → Function is serving HTTP only. Re-check Fluid,
374
368
 
375
369
  - Serve TomorrowOS via **Vercel Function** `api/index.ts` → `export default server` (Fluid + WebSockets).
376
370
  - Keep root **`server.ts`** for **local** `npm start` / Preview internal port only (same `cms-app.ts`).
377
- - Keep **`@tomorrowos/sdk`** (0.9.30+) and put **`tsx` in `dependencies`** for local start.
371
+ - Keep **`@tomorrowos/sdk`** (0.9.32+) and put **`tsx` in `dependencies`** for local start.
378
372
  - Node **20+**.
379
373
  - **Do not** replace the SDK with a hand-rolled server that drops WebSocket upgrades.
380
374
  - **Forbidden as the Production CMS:** `next start`, static export only, or a bare `(req,res)` handler without `http.Server` + `ws`.
@@ -384,10 +378,10 @@ If (1)+(2) pass but (3) fails → Function is serving HTTP only. Re-check Fluid,
384
378
  ```json
385
379
  {
386
380
  "scripts": {
387
- "dev": "npm run dev:preview",
381
+ "dev": "tsx watch server.ts",
388
382
  "dev:preview": "concurrently -k \"npm:dev:tomorrowos\" \"npm:dev:next\"",
389
383
  "dev:tomorrowos": "cross-env PORT=3001 TOMORROWOS_INTERNAL_PORT=3001 tsx watch server.ts",
390
- "dev:next": "next dev -p 3000",
384
+ "dev:next": "cd preview && next dev -p 3000",
391
385
  "start": "tsx server.ts",
392
386
  "build": "node -e \"process.exit(0)\""
393
387
  }
@@ -396,10 +390,10 @@ If (1)+(2) pass but (3) fails → Function is serving HTTP only. Re-check Fluid,
396
390
 
397
391
  Adapt script names / process runners as needed. **Invariant:**
398
392
 
399
- - **Local / v0 Preview `npm run dev`** = TomorrowOS `:3001` + Next `:3000` proxy.
393
+ - **Local `npm start`** = TomorrowOS Node with `server.listen`.
400
394
  - **Vercel Production** = Function `api/index.ts` (SDK `autoListen=false` when `VERCEL` is set).
401
395
  - **`build`** = no-op exit 0 (never `next build` for Production).
402
- - **Do not** ship an empty `app/page.tsx` (v0 placeholder).
396
+ - **Preview / v0** = Next on public port + TomorrowOS on internal port + proxy.
403
397
 
404
398
  ### Minimal dependencies (Production)
405
399
 
@@ -477,37 +471,32 @@ export default server;
477
471
 
478
472
  Load env with `dotenv` for local/`vercel dev`. On production Deploy, Vercel injects Environment Variables natively.
479
473
 
480
- ### Preview adapter (v0 / Next reverse proxy) — required for Preview UI
474
+ ### Preview adapter (v0 / Next reverse proxy) — required when Preview is Next-hardwired
481
475
 
482
- v0 Preview is hardwired to **root** Next (`app/`, `next.config.mjs`, `next dev`). An empty `app/page.tsx` (or returning `null`) produces **“Your v0 generation will show here”** and never shows the Control Panel.
476
+ Detect Preview / v0 (Next already present, or Preview blank while TomorrowOS logs “listening”). Then:
483
477
 
484
- **Required layout (cms-starter-v0):**
478
+ 1. Keep a **minimal** Next app **isolated when possible** (e.g. under `preview/`) so Production Framework detection does not latch onto root `next.config.*`.
479
+ 2. TomorrowOS listens on **`127.0.0.1:TOMORROWOS_INTERNAL_PORT`** (default **3001**).
480
+ 3. Add a **catch-all** Next route (or middleware) that proxies HTTP to TomorrowOS:
485
481
 
486
- ```
487
- app/
488
- layout.tsx ← minimal shell
489
- [[...slug]]/route.ts ← proxy ALL methods to http://127.0.0.1:3001
490
- next.config.mjs ← Preview only (no empty page.tsx)
491
- cms-app.ts / server.ts ← TomorrowOS on TOMORROWOS_INTERNAL_PORT=3001
492
- api/index.ts ← Production Function only (Publish unchanged)
482
+ ```ts
483
+ // Example: preview app proxy handler
484
+ // Forward method, headers, and body to http://127.0.0.1:3001/<path>
485
+ // Return the upstream status, headers, and body to the browser.
493
486
  ```
494
487
 
495
- **Agent must:**
488
+ Preferred UX: proxy **`/`** itself (rewrites) so the Preview URL shows the Control Panel at `/`, not only under `/api/...`.
496
489
 
497
- 1. Put the Next proxy at **project root** `app/` (v0 does not reliably use `preview/` alone).
498
- 2. **Do not** add `app/page.tsx` that returns null / placeholder copy.
499
- 3. Implement `app/[[...slug]]/route.ts` that forwards method, headers, and body to `TOMORROWOS_INTERNAL_URL` (default `http://127.0.0.1:3001`) and returns upstream status/body. On connection failure, return a short HTML 502 explaining `npm run dev`.
500
- 4. Set `"dev": "npm run dev:preview"` so the default v0/dev command starts **both** TomorrowOS (:3001) and Next (:3000).
501
- 5. Keep Production on Fluid Function (`api/index.ts`, `framework: null`, no-op `build`). **Do not** make `next start` / `next build` the Publish path.
502
- 6. Document: **Preview = Next proxy + TomorrowOS :3001; Publish = api/index.ts Fluid (unchanged).**
490
+ 4. Start **both** processes for Preview (`dev:preview` pattern above).
491
+ 5. Document in the final summary: **Preview = Next shell + proxy; Publish = Node only.**
503
492
 
504
493
  **Forbidden:**
505
494
 
506
- - Empty v0 placeholder page as the Preview UX
507
- - Making Next the Production CMS
508
- - Deleting `api/index.ts` / `cms-app.ts` to “fix” Preview
509
- - Claiming Preview complete while the iframe still says “Your v0 generation will show here”
510
- - Claiming setup complete if Production entrypoint is still `next start` or `GET /` is `404: NOT_FOUND`
495
+ - Making Next the Production CMS or the only server after Publish.
496
+ - Deleting TomorrowOS `server.ts` because Preview needed Next.
497
+ - Claiming setup complete if Production entrypoint is still `next start`.
498
+ - Claiming setup complete if Production `GET /` is `404: NOT_FOUND`.
499
+ - Creating root `api/index.*` + `routes` catch-all that exports a raw `http.Server` “to fix 404”.
511
500
 
512
501
  ### `vercel.json` (Production — explicit Node, not Next)
513
502
 
@@ -565,8 +554,7 @@ If Vercel’s UI and this file disagree on bundling, **prefer whatever keeps a s
565
554
 
566
555
  ## Question 1 — Database (always)
567
556
 
568
- > **Do not** ask screen counts. Present all three options; recommend **Supabase** for Vercel fleets.
569
- > **After the user picks 1 / 2 / 3:** finish Step B/C/D (collect URL or confirm SQLite + set Env Vars) **before** asking Question 2.
557
+ > **Do not** ask screen counts. Present all three options; recommend **Supabase** for Vercel fleets.
570
558
 
571
559
  ### Step A — Ask storage choice
572
560
 
@@ -584,8 +572,6 @@ If Vercel’s UI and this file disagree on bundling, **prefer whatever keeps a s
584
572
 
585
573
  ### Step B — If **1 / Supabase**
586
574
 
587
- **Immediately** (same turn as acknowledging their choice — do not ask Q2 yet) ask for the connection string:
588
-
589
575
  **Ask exactly (connection string only):**
590
576
 
591
577
  > Paste your Supabase Postgres connection string. I will store it as **`SUPABASE_URL`**.
@@ -598,20 +584,17 @@ If Vercel’s UI and this file disagree on bundling, **prefer whatever keeps a s
598
584
 
599
585
  **You must then (automatic — do NOT ask the user to type these):**
600
586
 
601
- 1. Set Vercel Env Vars **now** (open Env UI if available):
587
+ 1. Set Vercel Env Vars:
602
588
  - `SUPABASE_URL=<user pooler string>`
603
589
  - `TOMORROWOS_STORE=supabase` ← **agent sets this**
604
590
  - `DATABASE_SSL=true` ← **agent sets this** (unless user already said SSL is off)
605
- 2. Wire store in `cms-app.ts` / `server.ts` as in **Runtime & Vercel deploy rules**.
591
+ 2. Wire `server.ts` as in **Runtime & Vercel deploy rules**.
606
592
  3. Optional committed `.env.example` with **placeholders only**.
607
593
  4. **Do not** commit the real connection string.
608
594
  5. **Do not** open a second form asking for `TOMORROWOS_STORE` or `DATABASE_SSL`.
609
- 6. **Only after Env Vars are saved** → ask **Question 2**.
610
595
 
611
596
  ### Step C — If **2 / Neon**
612
597
 
613
- **Immediately** ask for the connection string (do not ask Q2 yet):
614
-
615
598
  **Ask exactly (connection string only):**
616
599
 
617
600
  > Paste your **Neon pooled** Postgres connection string. I will store it as **`DATABASE_URL`**.
@@ -622,15 +605,14 @@ If Vercel’s UI and this file disagree on bundling, **prefer whatever keeps a s
622
605
 
623
606
  **You must then (automatic — do NOT ask the user to type these):**
624
607
 
625
- 1. Set Vercel Env Vars **now**:
608
+ 1. Set Vercel Env Vars:
626
609
  - `DATABASE_URL=<user pooled string>`
627
610
  - `TOMORROWOS_STORE=postgres` ← **agent sets this**
628
611
  - `DATABASE_SSL=true` ← **agent sets this**
629
- 2. Wire `createTomorrowOSStore` for Postgres.
612
+ 2. Wire `server.ts` — `createTomorrowOSStore` uses `PostgresStore` for `postgres` / `supabase` drivers.
630
613
  3. **Do not** commit the real connection string.
631
- 4. **Do not** ask the user to fill `TOMORROWOS_STORE` or `DATABASE_SSL`.
614
+ 4. **Do not** ask the user to fill `TOMORROWOS_STORE` or `DATABASE_SSL` in chat or in an Env popup. Those are derived from choosing Neon.
632
615
  5. If you see `ENETUNREACH` on `:5432`, switch to Neon’s **pooled** URL.
633
- 6. **Only after Env Vars are saved** → ask **Question 2**.
634
616
 
635
617
  ### Step D — If **3 / SQLite**
636
618
 
@@ -643,9 +625,8 @@ Only proceed after the user explicitly confirms.
643
625
  **You must then (automatic):**
644
626
 
645
627
  1. Set `TOMORROWOS_STORE=sqlite` yourself (do not ask the user to type it).
646
- 2. Keep `sqlitePath` in `cms-app.ts` for `data/tomorrowos.db`.
628
+ 2. Keep `sqlitePath` in `server.ts` for `data/tomorrowos.db`.
647
629
  3. Warn again in the final summary that they should move to Supabase or Neon for real devices.
648
- 4. **Then** ask **Question 2**.
649
630
 
650
631
  **Later in Question 3:** set `cms.hostingTarget` to **`"vercel"`** (all Q1 branches).
651
632
 
@@ -654,26 +635,25 @@ Only proceed after the user explicitly confirms.
654
635
  ## Question 2 — Media storage
655
636
 
656
637
  > **This question is only about media files (images/videos).**
657
- > It is **not** about OpenAI, AI pairing, or LLM keys. If you are about to ask for an `sk-` key, **stop** — you are off-protocol.
658
- > **Copy the three options below verbatim.** Do **not** substitute Supabase Storage, “disable uploads”, or make Vercel Blob the recommended option.
638
+ > It is **not** about OpenAI, AI pairing, or LLM keys. If you are about to ask for an `sk-` key, **stop** — you are off-protocol.
659
639
 
660
640
  ### Step A — Ask storage choice
661
641
 
662
- **Ask exactly (wording must match):**
642
+ **Ask exactly:**
663
643
 
664
644
  > How should playlist media (images/videos) be stored?
665
645
  >
666
- > **1. Cloudinary (recommended)** — durable public HTTPS URLs (`https://res.cloudinary.com/...`). Works out of the box with `@tomorrowos/sdk` auto-detection. **Prefer this on Vercel.**
646
+ > **1. Cloudinary (recommended)** — durable public HTTPS URLs (`https://res.cloudinary.com/...`). Works out of the box with `@tomorrowos/sdk` auto-detection.
667
647
  >
668
- > **2. Vercel Blob** — Vercel-native object storage; durable `https://*.public.blob.vercel-storage.com/...` URLs. Use when you want media on the same Vercel project without a Cloudinary account.
648
+ > **2. Vercel Blob** — Vercel-native object storage; durable `https://*.public.blob.vercel-storage.com/...` URLs. Good when you want media on the same Vercel project without a Cloudinary account.
669
649
  >
670
- > **3. Local disk only** — `cms-panel/uploads` (OK for quick local tests; **not** for production Vercel fleets — files are ephemeral).
650
+ > **3. Local disk only** — `public/uploads` (OK for quick local tests; **not** for production Vercel fleets — files are ephemeral).
671
651
  >
672
652
  > Reply with **1**, **2**, or **3** (or “Cloudinary” / “Vercel Blob” / “local”).
673
653
 
674
- ### Step B — If **1 / Cloudinary** (configure **immediately** — one Env popup)
654
+ ### Step B — If **1 / Cloudinary** (same Question 2 **one Env popup**)
675
655
 
676
- > **Critical UX rule:** As soon as the user chooses Cloudinary, **immediately** open the **Vercel Environment Variables configuration popup / form** with **all Cloudinary fields in one UI**. Do **not** ask Q3 first. Do **not** ask for cloud name, then key, then secret in separate chat messages.
656
+ > **Critical UX rule:** Immediately open the **Vercel Environment Variables configuration popup / form** with **all Cloudinary fields in one UI**. Do **not** ask for cloud name, then key, then secret in separate chat messages.
677
657
 
678
658
  **Preferred:** Vercel Env Vars UI (or Agent secrets form) with these fields together:
679
659
 
@@ -699,13 +679,10 @@ Only proceed after the user explicitly confirms.
699
679
  2. **Do not** invent placeholders.
700
680
  3. **Do not** proceed to Question 3 until all three required vars exist.
701
681
  4. **Do not** run a formal Cloudinary upload test unless the user asks.
702
- 5. **Only after Cloudinary Env Vars are saved** → ask **Question 3**.
703
682
 
704
683
  The SDK auto-detects these env vars.
705
684
 
706
- ### Step C — If **2 / Vercel Blob** (configure **immediately**)
707
-
708
- **Immediately** (do not ask Q3 yet):
685
+ ### Step C — If **2 / Vercel Blob** (same Question 2)
709
686
 
710
687
  **Ask exactly:**
711
688
 
@@ -733,51 +710,46 @@ import { put } from "@vercel/blob";
733
710
 
734
711
  4. Warn: until the project’s Blob bridge is wired, Control Panel **Media** status may show local/ephemeral — verify uploads return `https://*.blob.vercel-storage.com/...` before calling setup complete.
735
712
  5. Do **not** proceed to Question 3 until `BLOB_READ_WRITE_TOKEN` exists (or Blob is linked and token is confirmed in the Vercel dashboard).
736
- 6. **Only then** → ask **Question 3**.
737
713
 
738
714
  ### Step D — If **3 / local uploads**
739
715
 
740
- 1. Ensure `cms-panel/uploads` exists (or `public/uploads` on Replit-style starters).
741
- 2. Warn clearly: files may vanish on redeploy; players need stable HTTPS URLs — **Cloudinary is strongly preferred** on Vercel.
742
- 3. **Then** ask **Question 3**.
716
+ 1. `mkdir -p public/uploads`
717
+ 2. Warn clearly: files may vanish on redeploy; players need stable HTTPS URLs — Cloudinary or Vercel Blob is strongly preferred for Vercel.
743
718
 
744
719
  ---
745
720
 
746
721
  ## Question 3 — Brand (`brand.json` only)
747
722
 
748
- > Updates **only** `brand.json`. Does **not** change Vercel project settings, Env Vars, or store wiring beyond what Q1–Q2 already required.
723
+ > Updates **only** `brand.json`. Does **not** change Vercel project settings, Env Vars, or `server.ts` store wiring beyond what Q1–Q2 already required.
749
724
  >
750
725
  > **IRON RULE — website URL ≠ build a website / login / auth CMS.**
751
- > 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, **background**).
726
+ > 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).
752
727
  > **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**.
753
- > Default TomorrowOS Control Panel has **no login**. Keep it that way.
754
- > **Never ask only for a display name.** Always offer Option A (URL) **or** the full Option B field list below.
728
+ > Default TomorrowOS Control Panel has **no login**. Keep it that way.
755
729
 
756
730
  ### Step A — Ask branding input
757
731
 
758
732
  **Ask exactly:**
759
733
 
760
- > Let’s brand your TomorrowOS experience. Choose **one**:
734
+ > Let’s brand your TomorrowOS experience. You can answer in either way:
761
735
  >
762
- > **Option A — Website URL (recommended if you have a site)**
763
- > Paste **one public website URL**. I will use it **only** to fill **`brand.json`**: product name, tagline, primary / secondary / **background** / text colours, fonts, and logo.
764
- > I will **not** rebuild that website or add login — unless you explicitly ask for login/auth later.
736
+ > **Option A — Website URL only (brand reference)**
737
+ > Paste **one public website URL**. I will use it **only** to infer colours, fonts, name, tagline, and logo for **`brand.json`**.
738
+ > I will **not** rebuild that website, add a login page, or change Control Panel features — unless you explicitly ask for login/auth later.
765
739
  >
766
- > **Option B — Manual brand fields**
767
- > Send as many of these as you can (name + at least one colour + logo is ideal):
768
- > 1. **Product / venue name** (required)
740
+ > **Option B — Manual fields**
741
+ > Provide:
742
+ > 1. **Product / venue name**
769
743
  > 2. **Tagline** (optional)
770
744
  > 3. **Primary colour** (hex, e.g. `#FF8A3D`)
771
- > 4. **Background colour** (hex — page / panel background)
745
+ > 4. **Background colour** (hex, optional default `#FAFAF9`)
772
746
  > 5. **Text colour** (hex, optional — default `#0A0908`)
773
747
  > 6. **Secondary / accent colour** (hex, optional)
774
- > 7. **Logo** — upload SVG/PNG into the project, or a public image URL I can fetch into `./assets/`
775
- >
776
- > Reply with a URL **or** the manual fields. Do not send only a name with no colours/logo unless you want defaults (I will confirm defaults before writing).
748
+ > 7. **Logo** — upload SVG/PNG into the project, or a URL I can fetch into `./assets/`
777
749
 
778
750
  ### Step B — If the user gives **only a website URL** (Option A)
779
751
 
780
- **You must (immediately — do not defer brand write to “end of setup”):**
752
+ **You must:**
781
753
 
782
754
  1. **Fetch and inspect** the page (HTTP GET the URL; follow one redirect if needed). Do not invent colours.
783
755
  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).
@@ -792,8 +764,7 @@ import { put } from "@vercel/blob";
792
764
  - **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.
793
765
  4. **Contrast check:** if background is dark, set `activationScreen.theme` to `"dark"` and add `logoPathOnDark` when a light logo variant exists.
794
766
  5. **Infer `cms.useCase`** from page content when obvious (restaurant / retail / corporate / etc.); otherwise `"other"`.
795
- 6. **Show a one-line summary** to the user (name + primary + background hex + logo path) while writing the file — do not ask a second branding question unless fetch failed.
796
- 7. **Then** run the final deploy checklist (seed layout already done / Publish).
767
+ 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.
797
768
  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.
798
769
 
799
770
  **Explicitly forbidden when the user only gave a URL (no login request):**
@@ -838,31 +809,27 @@ Validate mentally against `brand.schema.json`. Minimum:
838
809
 
839
810
  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://`).
840
811
 
841
- **If Option B (manual):** map every field the user provided into `brand.json` **immediately** (`name`, `primaryColor`, `backgroundColor`, `textColor`, `secondaryColor`, `logoPath`, `tagline`). If they only sent a name, ask once for missing **background**, **primary colour**, and **logo** — do not proceed to deploy with name-only branding without confirming defaults.
842
-
843
812
  ---
844
813
 
845
- ## After Q1–Q3 configuredfinal deploy checklist
846
-
847
- > Env Vars for Q1/Q2 and `brand.json` for Q3 should **already be done** during each question. This section is **scaffold + Publish verify only** — do not re-ask for Cloudinary/DB secrets here.
814
+ ## After all answersexecution checklist
848
815
 
849
- ### A. Seed the project (do this early if the repo is empty — before or during Q1)
816
+ ### A. Seed the project
850
817
 
851
818
  ```bash
852
- npx @tomorrowos/sdk@latest init . --hosting v0
819
+ npx @tomorrowos/sdk@latest init .
853
820
  # If not empty and user confirms starter overwrite:
854
- # npx @tomorrowos/sdk@latest init . --hosting v0 --force
821
+ # npx @tomorrowos/sdk@latest init . --force
855
822
  ```
856
823
 
857
824
  Then (**Scheme A — mandatory for Publish**):
858
825
 
859
- 1. Ensure **`cms-app.ts`** + **`api/index.ts`** (`export default server`) + **`cms-panel/`**.
860
- 2. If Preview needs Next: keep Next under **`preview/`** only.
861
- 3. Write `vercel.json` per **Scheme A3** (`fluid`, rewrites, `maxDuration: 300` — **no** `startCommand`).
862
- 4. **Force Project Settings** per **Scheme A4** (Framework = Other, Output Directory empty, Fluid on).
863
- 5. Do **not** add a second TomorrowOS in `api/ws.ts`.
826
+ 1. Keep **`server.ts`** at **project root** with top-level `listen()` + `export default server`.
827
+ 2. If Preview needs Next: **move** `next.config.*`, `app/`, Next `tsconfig` / `postcss` / `next-env.d.ts` into **`preview/`** — remove them from root so Production is not mistaken for Next/static.
828
+ 3. Write `vercel.json` per **Scheme A3** (`fluid`, `framework: null`, no-op `buildCommand` — **no** `startCommand`).
829
+ 4. **Force Project Settings** per **Scheme A4** (Framework = Other, Output Directory empty, Start = `npm run start`, Fluid on). Patch dashboard via API if it still says Next.js.
830
+ 5. Do **not** add root `api/` catch-all for raw `http.Server`.
864
831
  6. Do **not** add `.replit*`.
865
- 7. If Q2 = Vercel Blob: install `@vercel/blob` and wire the upload bridge (should already be started in Q2).
832
+ 7. If Q2 = Vercel Blob: install `@vercel/blob` and wire the upload bridge.
866
833
 
867
834
  ### B. Environment Variables (Vercel dashboard + local)
868
835
 
@@ -945,7 +912,7 @@ Tell the user:
945
912
  | Separate `api/ws.ts` with a second `new TomorrowOS` | Two isolates — pairing memory split | **One** Function only (`api/index.ts` → shared `cms-app.ts`) |
946
913
  | `vercel inspect` shows Output = `public if it exists` | Framework=Other static default | Force Output Directory **empty**; use `cms-panel/`; Path B redeploy |
947
914
  | Build runs `next build` / missing routes-manifest | Next auto-detected from root `next.config` / `app/` | Move Next to `preview/`; Framework null/Other; `build` = no-op |
948
- | Preview blank / “Your v0 generation will show here” | Empty `app/page.tsx` or Next not proxying / TomorrowOS not on :3001 | Root `app/[[...slug]]/route.ts` proxy; `"dev": "npm run dev:preview"`; no null page |
915
+ | Preview blank but logs show TomorrowOS listening | v0 Next intercepts public port | Add Next reverse proxy internal TomorrowOS port |
949
916
  | Preview works, Publish broken / Next-only | Production still on Next | Switch Production to Fluid Function `api/index.ts` |
950
917
  | `ENETUNREACH` / IPv6 / `:5432` | Direct Postgres URL | Use **pooled** URL (Supabase `:6543` or Neon pooler) |
951
918
  | Control Panel OK, devices never stay paired after restart | SQLite / ephemeral disk | Use Supabase or Neon |
@@ -954,9 +921,7 @@ Tell the user:
954
921
  | WebSocket fails on Publish | Fluid off / static-only / Next as Production / no Function WS | Enable Fluid; Scheme A; test 101 upgrade |
955
922
  | Devices fail only on Preview | Next proxy cannot upgrade `wss` | Expected — pair on Publish URL |
956
923
  | Agent asked user to fill `TOMORROWOS_STORE` / `DATABASE_SSL` | Off-protocol | Agent must auto-set those after Neon/Supabase choice |
957
- | Agent offered Supabase Storage / “disable media” for Q2 | Invented options | Re-ask Q2 verbatim: Cloudinary Blob local |
958
- | Agent asked only brand display name for Q3 | Shortcut | Re-ask Q3 Option A (URL) or full Option B (colours + background + logo) |
959
- | Agent batched all Q&A then configured Env at the end | Off-protocol UX | Stop; configure the current question’s Env before asking the next |
924
+ | Agent asked Cloudinary key/secret in three chat turns | Off-protocol | Use **one Env popup** with all Cloudinary fields |
960
925
  | Agent asked for OpenAI / `sk-` key during setup | Hallucinated “AI pairing” requirement | **Refuse.** TomorrowOS does not need LLM keys. Return to Q2 media choices only |
961
926
  | 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` |
962
927
  | Secrets in git | Mistake | Rotate keys; move to Vercel Env Vars |
@@ -972,11 +937,10 @@ Tell the user:
972
937
  | Deploy config | `.replit`, artifact `kind=web` | `vercel.json` + Fluid; **Framework = Other**; **no** `.replit` |
973
938
  | `hostingTarget` | `"here"` | `"vercel"` |
974
939
  | Database | Supabase (primary) | Supabase **or Neon** (pooled); SQLite demo only |
975
- | Media | Cloudinary or Replit Object Storage | **Cloudinary (recommended)** or Vercel Blob (not Supabase Storage) |
940
+ | Media | Cloudinary or Replit Object Storage | Cloudinary **or Vercel Blob** |
976
941
  | Preview | Replit Preview / Autoscale Node | **Next shell + proxy** when v0 hardwires Next |
977
- | Publish | Long-lived Node | **Fluid Function + WebSockets** (not Next); gate on `/status` JSON + WS 101 |
978
- | Brand from URL | Not in Replit protocol | **Option A** — full **`brand.json`** (name/colours/background/logo); never invent login |
979
- | Configure timing | Per Replit protocol | **After each question immediately** (never batch Q1–Q3 then configure) |
942
+ | Publish | Long-lived Node | **Node `TomorrowOS.listen` only** (not Next); gate on no `404 NOT_FOUND` |
943
+ | Brand from URL | Not in Replit protocol | **Option A** — infer **`brand.json` only**; never invent login |
980
944
 
981
945
  ---
982
946