@tomorrowos/sdk 0.9.29 → 0.9.31

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,7 +5,8 @@
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.7` — pairs with `@tomorrowos/sdk` packages that ship `templates/cms-starter` and this file.
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.”
9
10
 
10
11
  ## User trigger (copy-paste)
11
12
 
@@ -16,11 +17,14 @@ Follow @tomorrowos/sdk VERCEL_SETUP.md and set up my TomorrowOS CMS.
16
17
  Ask only the questions listed in VERCEL_SETUP.md, in order. Do not use LLM_PROMPT.md or REPLIT_SETUP.md.
17
18
  Do not ask for OpenAI / Anthropic / any LLM API keys — TomorrowOS CMS setup does not require them.
18
19
  If any step contradicts Vercel, adapt to settings Vercel can accept (document the adaptation).
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), package.json `start` must run TomorrowOS Node, GET /status on the production URL must return JSON (not HTML), and GET / must return the Control Panel (not 404 NOT_FOUND).
23
- Do not put startCommand or processes in vercel.json (v0 schema rejects them). Do not move server.ts into api/.
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.
24
28
  ```
25
29
 
26
30
  Alternate triggers:
@@ -74,14 +78,14 @@ almost always means **Vercel still thinks this is a Next.js / static deploy**, n
74
78
  2. **Install Command** = `npm install` / `pnpm install` / `yarn` (normal install).
75
79
  3. **Build Command** = a **no-op that exits 0** (e.g. `node -e "process.exit(0)"` or `npm run build` where `build` is that no-op).
76
80
  **Never** run `next build` for Production.
77
- 4. **Start / Dev Command for Production** = `npm run start` / `pnpm start` **`tsx server.ts`** (or `.mts`) that calls `TomorrowOS.listen` and **`export default server`**.
81
+ 4. **Production entry** = Fluid **Vercel Function** `api/index.ts` that **`export default server`** from shared `cms-app.ts` (see [WebSockets docs](https://vercel.com/docs/functions/websockets)). Local `npm start` may still use root `server.ts`.
78
82
  5. **Output Directory** = **empty / unset**. Do **not** set `outputDirectory: "."` as a fake static site.
79
- 6. **Do not** create a root `api/index.ts` that exports a raw `http.Server` and catch-all `routes` to “fix” 404 Vercel Fluid does **not** treat that as a supported handler for this CMS. That pattern causes more 404s.
83
+ 6. **Do** use a single `api/index.ts` that exports the TomorrowOS `http.Server`. **Do not** create a second TomorrowOS in `api/ws.ts`, and **do not** use bare `(req, res)` handlers that drop upgrades.
80
84
  7. If Preview needs Next, keep Next files **isolated** so Production auto-detect cannot latch onto them:
81
85
  - Prefer `preview/` (or similar) for `next.config.*`, `app/`, `pages/` used only by v0 Preview, **or**
82
- - Ensure Production Framework Preset is forced to **Other** and Start Command never runs `next`.
86
+ - Ensure Production Framework Preset is forced to **Other** and never runs `next` as the CMS.
83
87
 
84
- **Setup is NOT complete** while Production `GET /` returns `404: NOT_FOUND`. Fix Framework Preset + Start Command and redeploy.
88
+ **Setup is NOT complete** while Production `GET /` returns `404: NOT_FOUND` or WebSocket upgrade returns 200 HTML.
85
89
 
86
90
  ### Publish static trap — `public/` served without Node (read this)
87
91
 
@@ -92,11 +96,11 @@ A common **worse** failure mode than `404: NOT_FOUND`:
92
96
  - The panel shows **CMS unreachable** and console:
93
97
  `Unexpected token 'T', "The page c"... is not valid JSON`
94
98
 
95
- **Root cause:** Vercel deployed `public/` as a **static site** (Output Directory = `public` or Framework=Other default *“public if it exists”*). **No Node process** is running `tsx server.ts`, so API routes never hit `TomorrowOS`.
99
+ **Root cause:** Vercel deployed `public/` as a **static site** (Output Directory = `public` or Framework=Other default *“public if it exists”*). **No Function** is running TomorrowOS.
96
100
 
97
- **Fix (Scheme A — see dedicated section below):** force Vercel to run the **root `server.ts` Node entry** with Fluid. `public/` must be served **at runtime** via `staticRoot` in `listen()`, **not** as the build output directory.
101
+ **Fix (Scheme A):** use `cms-panel/` (not `public/`), Fluid Function `api/index.ts` + rewrites, Output Directory empty. Static UI is served **at runtime** via `staticRoot` in `listen()`.
98
102
 
99
- **Publish gate (mandatory):** after deploy, `GET /status` must return **JSON** starting with `{`, not HTML starting with `<` or `The page`.
103
+ **Publish gate (mandatory):** after deploy, `GET /status` must return **JSON** starting with `{`, not HTML starting with `<` or `The page`. **And** WebSocket upgrade on `/` or `/api` must return **101**.
100
104
 
101
105
  ```bash
102
106
  curl -s https://YOUR-PROJECT.vercel.app/status | head -c 80
@@ -104,7 +108,7 @@ curl -s https://YOUR-PROJECT.vercel.app/status | head -c 80
104
108
  # Failure: The page could not be found OR <!DOCTYPE html>
105
109
  ```
106
110
 
107
- **Setup is NOT complete** while `/status` returns HTML or the panel shows the JSON parse error.
111
+ **Setup is NOT complete** while `/status` returns HTML, the panel shows the JSON parse error, or devices cannot open `wss://`.
108
112
 
109
113
  ### Preview (v0 / Vercel Agent sandbox) — Next.js shell allowed
110
114
 
@@ -127,15 +131,25 @@ v0 Preview is often a **Next.js runtime** hardwired to `next dev`. A raw `tsx se
127
131
 
128
132
  ## Questionnaire scope (STRICT)
129
133
 
130
- Ask **only** these questions, in **this exact order**:
134
+ Ask **only** these questions, in **this exact order**. After each answer, **configure that step immediately**, then proceed:
131
135
 
132
- | Step | Section | When |
133
- |------|---------|------|
134
- | 1 | **Question 1** — Database (Supabase → Neon → SQLite) | Always |
135
- | 2 | **Question 2** — Media storage (Cloudinary → Vercel Blob → local) | Always (after Q1) |
136
- | 3 | **Question 3** — Brand / TomorrowOS look (`brand.json` only) | Always last |
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 |
137
141
 
138
- **That is the complete list.** Do **not** ask how many screens / devices.
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
153
 
140
154
  **Forbidden extra questions (never ask, even if the user skips a step):**
141
155
 
@@ -159,187 +173,198 @@ Then ask **Question 1**.
159
173
  ## Hard rules
160
174
 
161
175
  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.
162
- 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.
163
- 3. **Do not invent** Cloudinary credentials, database URLs, Vercel Blob tokens, brand colours, **or LLM API keys**.
164
- 4. **Postgres env naming:** prefer **`SUPABASE_URL`** when using Supabase; prefer **`DATABASE_URL`** when using Neon. `createTomorrowOSStore` accepts `SUPABASE_URL` then `DATABASE_URL`.
165
- 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).
166
- 6. **Prefer `npx @tomorrowos/sdk init --hosting v0`** (template `cms-starter-v0`: root `server.ts`, `cms-panel/`, `vercel.json`). For Replit use default `init` (`cms-starter`). Do not rebuild pairing / WebSocket / playlist APIs.
167
- 7. **Never commit secrets.** Use Vercel Environment Variables (+ optional non-secret flags in committed `.env.example`).
168
- 8. **Skip Replit-only files:** do **not** create `.replit`, `.replit-artifact`, or Replit `deploymentTarget`.
169
- 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`.
170
- 10. **Enable Fluid compute** for Production WebSockets. Confirm Project settings after scaffold.
171
- 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.
172
- 12. **Preview:** if the environment is v0 / Next-hardwired, install the Next reverse-proxy shell (see **Preview adapter**). Do not leave Preview broken.
173
- 13. After Q&A: **configure → install → run/deploy → minimal verify** (Control Panel at `/`). Do not run a long test suite.
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.
174
188
  14. **No inventing CMS login.** A branding URL never means “build auth.” Only add login if the user **explicitly** requests it.
175
189
  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.
176
190
  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.
177
- 17. **Publish gate:** do not mark setup complete while Production returns `404: NOT_FOUND` **or** `/status` returns HTML (static `public/` trap).
178
- 18. **Scheme A is doc-driven:** achieve Production Node Publish by **project layout + `vercel.json` + dashboard settings** during setup **no SDK code changes** required (see **Scheme A** section).
191
+ 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
+ 18. **Scheme A is doc-driven:** Production = Fluid Function + `export default server` per Vercel WebSockets docsReplit starter stays unchanged.
179
193
 
180
- **Question order:** Q1 → Q2 → Q3 → execution checklist.
194
+ **Question order:** Q1 → **configure Q1** → Q2 → **configure Q2** → Q3 → **write brand.json** → deploy checklist.
181
195
 
182
196
  ---
183
197
 
184
- ## Scheme A — Standard Node Publish (mandatory for Production)
198
+ ## Scheme A — Vercel Functions + WebSockets (mandatory for Production)
185
199
 
186
- > **Goal:** Preview may use Next + proxy; **Publish** runs **only** root `server.ts` + `TomorrowOS.listen` on Vercel Fluid.
187
- > **Scope:** This section is implemented entirely during setup (move files, write `vercel.json`, patch dashboard). **Do not** change `@tomorrowos/sdk` source.
200
+ > **Goal:** Preview may use Next + proxy; **Publish** runs TomorrowOS as a **Vercel Function** (`api/index.ts`) on **Fluid**, with WebSocket support per [Vercel WebSockets docs](https://vercel.com/docs/functions/websockets).
201
+ > **Scope:** Layout + `vercel.json` + dashboard. Prefer `npx @tomorrowos/sdk init --hosting v0` (`cms-starter-v0`).
202
+ > **Do not** break Replit/Railway: those keep using default `init` → `cms-starter` (root `server.ts` + `server.listen`, no `api/`).
188
203
 
189
204
  ### Why Scheme A exists
190
205
 
191
206
  | Layer | Preview (v0) | Publish (Production) |
192
207
  |-------|----------------|----------------------|
193
- | Host | Next.js shell + HTTP proxy | **Node `server.ts`** only |
194
- | `public/` | Proxied from TomorrowOS | Served by **`staticRoot`** inside `listen()` — **not** as Vercel Output Directory |
195
- | WebSocket | Optional / may fail in Preview | Required on same origin (`wss://`) |
196
- | Next files | Allowed under `preview/` | **Must not** live at project root |
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 |
212
+
213
+ **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)).
197
214
 
198
215
  ### A1 — Project layout (Agent must enforce)
199
216
 
200
- **At project root (Production):**
217
+ **Production (Vercel / v0 starter):**
201
218
 
202
219
  ```
203
- server.ts ← REQUIRED name at root (or server.mts)
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
204
227
  vercel.json
205
- package.json
228
+ cms-panel/
206
229
  brand.json
207
- public/ CMS static UI; NOT the Vercel output directory
208
- ```
209
-
210
- **Preview only (when v0 needs Next) — move ALL of these under `preview/`:**
211
-
212
- ```
213
- preview/
214
- next.config.mjs
215
- app/ ← Next proxy routes
216
- postcss.config.mjs
217
- next-env.d.ts
218
- tsconfig.json ← Next/tsconfig if separate from server
230
+ package.json "dev": "npm run dev:preview"
219
231
  ```
220
232
 
221
- **Remove from project root after move:** `next.config.mjs`, `app/`, root Next `tsconfig` plugin config, `postcss.config.mjs`, `next-env.d.ts`.
222
-
223
233
  **Do NOT:**
224
234
 
225
- - ❌ Leave `next.config.mjs` + `app/` at root while claiming Production is Node-only
226
- - ❌ Create root `api/index.ts` exporting `http.Server` + `routes` catch-all
227
- - ❌ Set Output Directory to `public` or `.`
235
+ - ❌ Add `app/page.tsx` that returns null (v0 shows “Your v0 generation will show here”)
236
+ - ❌ 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`
228
240
 
229
- **Update `package.json` scripts when Preview uses `preview/`:**
241
+ **`package.json` scripts:**
230
242
 
231
243
  ```json
232
244
  {
233
245
  "scripts": {
234
- "dev": "tsx watch server.ts",
246
+ "dev": "npm run dev:preview",
235
247
  "dev:preview": "concurrently -k \"npm:dev:tomorrowos\" \"npm:dev:next\"",
236
248
  "dev:tomorrowos": "cross-env TOMORROWOS_INTERNAL_PORT=3001 tsx watch server.ts",
237
- "dev:next": "cd preview && next dev -p 3000",
249
+ "dev:next": "next dev -p 3000",
238
250
  "start": "tsx server.ts",
239
251
  "build": "node -e \"process.exit(0)\""
240
252
  }
241
253
  }
242
254
  ```
255
+ - **Local / Railway-style:** `npm start` → `tsx server.ts` → SDK calls `server.listen` (no `VERCEL` env).
256
+ - **Vercel Production:** Function loads `api/index.ts`; SDK skips `listen` when `process.env.VERCEL` is set and Vercel owns the socket.
243
257
 
244
- ### A2 — `server.ts` entry (Vercel zero-config Node detection)
245
-
246
- Vercel detects a **root** `server.ts` / `server.mts` and routes traffic to it when:
258
+ ### A2 — Function entry (matches Vercel WebSockets docs)
247
259
 
248
- 1. The file lives at **project root** (not only under `api/`)
249
- 2. **`listen()` runs at module load** (top-level, not inside an unexported function)
250
- 3. The file **`export default server`** (the `http.Server` from `tomorrowos.listen`)
251
-
252
- Use the pattern in **Runtime & Vercel deploy rules** below. On Publish, **do not** set `TOMORROWOS_INTERNAL_PORT` — let Vercel inject `PORT`.
260
+ ```ts
261
+ // api/index.ts
262
+ export { server as default } from "../cms-app.js";
263
+ ```
253
264
 
254
- ### A3 — `vercel.json` (Production)
265
+ ```ts
266
+ // cms-app.ts (shared)
267
+ const server = tomorrowos.listen({
268
+ port: Number(process.env.PORT) || 3000,
269
+ host: "0.0.0.0",
270
+ staticRoot: join(__dirname, "cms-panel"),
271
+ });
272
+ export { server, tomorrowos };
273
+ ```
255
274
 
256
- **v0 / `.v0/vercel.deploy.json` schema:** only a **minimal** object is valid. These keys are **rejected** — never add them:
275
+ SDK behaviour (0.9.30+):
257
276
 
258
- - `startCommand`
259
- - `processes`
260
- - `routes` / `rewrites` to fake API routing
261
- - ❌ `outputDirectory: "public"` or `"."`
277
+ - Accepts WebSocket upgrades on `/`, `/api`, `/api/ws` (players may use either)
278
+ - When `VERCEL` is set: **`autoListen` defaults to false** — do not bind a port yourself
279
+ - Pattern aligns with docs: create `http.Server`, attach `ws`, **`export default server`**
262
280
 
263
- Vercel discovers the start command from **`package.json` → `"start": "tsx server.ts"`**, not from `vercel.json`.
281
+ ### A3 `vercel.json` (Production)
264
282
 
265
283
  ```json
266
284
  {
267
285
  "fluid": true,
268
286
  "framework": null,
269
287
  "installCommand": "npm install",
270
- "buildCommand": "npm run build"
288
+ "buildCommand": "npm run build",
289
+ "functions": {
290
+ "api/index.ts": {
291
+ "maxDuration": 300,
292
+ "memory": 1024,
293
+ "includeFiles": "{cms-panel/**,brand.json,assets/**}"
294
+ }
295
+ },
296
+ "rewrites": [
297
+ { "source": "/((?!api/).*)", "destination": "/api" }
298
+ ]
271
299
  }
272
300
  ```
273
301
 
274
- Use `pnpm` / `yarn` equivalents if the project uses them.
302
+ **`maxDuration` plan limits:** Hobby / many default plans allow **1–300** seconds. **800** requires a higher Vercel plan. Prefer **`300`** unless the account already allows more — otherwise Publish fails with *“maxDuration must be between 1 and 300… upgrade your plan”*.
275
303
 
276
- **Critical:**
304
+ **Why rewrites:** Public URLs stay `https://app.vercel.app/` and `/status` while the Function mount is `/api`. Device `wss://app.vercel.app/` is rewritten to the same Function so one isolate handles HTTP + WS.
305
+
306
+ **v0 schema:** never add `startCommand` or `processes`.
277
307
 
278
- - **Do not** add `startCommand` to `vercel.json` (invalid in v0; redundant on full Vercel — use `package.json` `start`)
279
- - **Do not** set `outputDirectory` to `public` or `.`
280
- - `"buildCommand"` must run the no-op `npm run build` — **never** `next build` for Production
281
- - Set **Output Directory empty** in the **Vercel dashboard** (Scheme A4) — that is where static-vs-Node is decided, not via invalid `vercel.json` keys
308
+ **Critical:**
282
309
 
283
- ### A4 Vercel Project Settings (dashboard or API — mandatory)
310
+ - `"fluid": true` is required for WebSockets
311
+ - **Do not** set `outputDirectory` to `cms-panel` / `public`
312
+ - `"buildCommand"` = no-op — **never** `next build` for Production
313
+ - Dashboard Output Directory must stay **empty**
284
314
 
285
- `vercel.json` alone is **not enough** if the dashboard still shows Framework = **Next.js** or Output = `public`.
315
+ ### A4 Vercel Project Settings (dashboard mandatory)
286
316
 
287
317
  | Setting | Required value |
288
318
  |---------|----------------|
289
319
  | Framework Preset | **Other** / null — **not Next.js** |
290
- | Install Command | `npm install` (or pnpm/yarn) |
291
- | Build Command | `npm run build` (no-op exit 0) |
292
- | **Output Directory** | **empty / null** — **NOT `public`** |
293
- | **Start Command** | `npm run start` → `tsx server.ts` |
320
+ | Install Command | `npm install` |
321
+ | Build Command | `npm run build` (no-op) |
322
+ | **Output Directory** | **empty** |
294
323
  | Fluid compute | **On** |
324
+ | WebSockets | Account must allow Functions WebSockets (see docs “Permissions Required”) |
295
325
 
296
- If Framework Preset still shows **Next.js** after `vercel.json`: **patch via Vercel API / CLI / dashboard**, then redeploy.
326
+ ### A5 Publish acceptance (all must pass)
297
327
 
298
- ### A5 Publish acceptance (all must pass before “setup complete”)
328
+ 1. `curl -s https://PROD/status` JSON (`{"status":...}`)
329
+ 2. `curl -s https://PROD/` → Control Panel HTML
330
+ 3. **WebSocket upgrade returns 101**, not 200 HTML:
299
331
 
300
- 1. Deploy logs show Node starting / `[TomorrowOS] listening on ...` (not only “Uploading static files”)
301
- 2. `curl -s https://PROD/status` → JSON (`{"status":...}`)
302
- 3. `curl -s https://PROD/` Control Panel HTML
303
- 4. Control Panel **does not** show `Unexpected token 'T'` / CMS unreachable
304
- 5. Framework Preset = Other; Fluid on
305
-
306
- If (2) fails but (3) passes → **static `public/` trap**. Re-apply A3 + A4; confirm dashboard Start Command + empty Output Directory; redeploy. **Do not** add `api/` catch-all. **Do not** move `server.ts` into `api/`.
307
-
308
- ### A6 — What Scheme A does **not** require
309
-
310
- - ❌ No changes to `@tomorrowos/sdk` package code
311
- - ❌ No `(req, res)` serverless handler refactor
312
- - ❌ No `next build` / `next start` on Production
313
- - ❌ No root `api/` directory (`api/index.ts`, `api/[...].ts`, `api/[[...slug]].ts`)
314
- - ❌ No `rewrites` so only `/api/status` works while `/status` stays 404
315
- - ❌ No renaming `start` → `dev` with `tsx watch` for Production
316
- - ❌ No `@vercel/node` handler that calls non-existent `tomorrowos.getStatus()`
332
+ ```bash
333
+ curl.exe -i --http1.1 ^
334
+ -H "Connection: Upgrade" -H "Upgrade: websocket" ^
335
+ -H "Sec-WebSocket-Version: 13" ^
336
+ -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" ^
337
+ "https://PROD/"
338
+ # Also try: https://PROD/api
339
+ ```
317
340
 
318
- ### A7 If v0 Publish still serves static `public/` (Path B)
341
+ 4. Control Panel does **not** show CMS unreachable / `Unexpected token 'T'`
342
+ 5. Fluid on; Framework = Other
319
343
 
320
- When **v0 Publish** keeps returning the static trap (`/` HTML OK, `/status` HTML, `Unexpected token 'T'`) **after** Scheme A is correct, the v0 deploy pipeline may not be starting the Fluid Node server.
344
+ If (1)+(2) pass but (3) fails Function is serving HTTP only. Re-check Fluid, `api/index.ts` default export, rewrites, and WebSockets permission. **Do not** invent a second `api/ws.ts` TomorrowOS instance.
321
345
 
322
- **Path Bdeploy outside v0 Publish (recommended):**
346
+ ### A6What Scheme A does **not** require
323
347
 
324
- 1. Keep Scheme A layout (root `server.ts`, Next only under `preview/`, minimal `vercel.json`).
325
- 2. Connect the repo to **Vercel Git** or run **`vercel --prod`** from the project root (full Vercel CLI / dashboard deploy — not the v0 “Publish” button).
326
- 3. In **Vercel Dashboard Project Settings → General**: Framework = **Other**, Output Directory = **empty**, Build = `npm run build`, **override** Install/Start if needed so Production runs `npm run start`.
327
- 4. Redeploy; verify `curl https://PROD/status` JSON.
348
+ - No Next.js as Production CMS
349
+ - No replacing SDK transport with hand-rolled pairing
350
+ - No SQLite as production store on Vercel
351
+ - No changes to Replit `cms-starter` layout for Vercel-only fixes
328
352
 
329
- **Path Cif Vercel cannot run TomorrowOS Node + WebSocket reliably:**
353
+ ### A7If v0 Publish still fails (Path B / C)
330
354
 
331
- Host the CMS on **Railway**, **Fly.io**, or **Replit Publish** (long-lived Node). Use v0/Vercel only for Preview/demo if needed.
355
+ **Path B:** Deploy with `vercel --prod` or Git integration (full Vercel), same Scheme A layout.
332
356
 
333
- ### A8 Undo a broken Agent “api/ migration”
357
+ **Path C:** Host CMS on **Railway / Fly.io / Replit** (long-lived Node). Use Vercel only for Preview if needed.
334
358
 
335
- If an Agent moved `server.ts` `api/index.ts` / `api/[...].ts` and only `/api/status` returns JSON:
359
+ ### A8 Migrate an existing broken Vercel project
336
360
 
337
- 1. **Restore** `server.ts` at **project root** (from git or `templates/cms-starter/server.ts`).
338
- 2. **Delete** root `api/index.ts`, `api/[...].ts`, `api/[[...slug]].ts` (Preview proxy may stay under `preview/app/` only).
339
- 3. Revert `package.json` `start` to `tsx server.ts` (not `tsx watch` for Production).
340
- 4. Apply minimal `vercel.json` (A3) no `startCommand`, no `rewrites`.
341
- 5. Dashboard: Framework Other, Output empty, Fluid on.
342
- 6. Redeploy via Path B if v0 Publish still fails.
361
+ 1. Scaffold/compare with `cms-starter-v0` (`init --hosting v0`) or copy:
362
+ - `cms-app.ts`, `api/index.ts`, `server.ts`, `vercel.json`
363
+ - rename `public/` `cms-panel/` if still present
364
+ 2. Ensure **one** Function exports the **same** `server` from `cms-app.ts`
365
+ 3. Fluid on Redeploy
366
+ 4. Verify A5 (especially WebSocket 101)
367
+ 5. On TVs: enter `https://YOUR.vercel.app/` (players also try `/api` automatically on vercel.app hosts)
343
368
 
344
369
  ---
345
370
 
@@ -347,18 +372,19 @@ If an Agent moved `server.ts` → `api/index.ts` / `api/[...].ts` and only `/api
347
372
 
348
373
  ### Production runtime (Publish)
349
374
 
350
- - Serve with **`tsx server.ts`** (or `server.mts` / compiled Node that still calls `TomorrowOS.listen` with `staticRoot`).
351
- - Keep **`@tomorrowos/sdk`** and put **`tsx` in `dependencies`** (not only `devDependencies`) if production runs via `tsx`.
375
+ - Serve TomorrowOS via **Vercel Function** `api/index.ts` `export default server` (Fluid + WebSockets).
376
+ - 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.
352
378
  - Node **20+**.
353
379
  - **Do not** replace the SDK with a hand-rolled server that drops WebSocket upgrades.
354
- - **Forbidden as the Production Deploy entrypoint:** `next start`, static export only, root `api/` fake catch-all for raw `http.Server`, or any command that does not start `TomorrowOS.listen(...)`.
380
+ - **Forbidden as the Production CMS:** `next start`, static export only, or a bare `(req,res)` handler without `http.Server` + `ws`.
355
381
 
356
382
  ### Expected `package.json` scripts
357
383
 
358
384
  ```json
359
385
  {
360
386
  "scripts": {
361
- "dev": "tsx watch server.ts",
387
+ "dev": "npm run dev:preview",
362
388
  "dev:preview": "concurrently -k \"npm:dev:tomorrowos\" \"npm:dev:next\"",
363
389
  "dev:tomorrowos": "cross-env PORT=3001 TOMORROWOS_INTERNAL_PORT=3001 tsx watch server.ts",
364
390
  "dev:next": "next dev -p 3000",
@@ -370,9 +396,10 @@ If an Agent moved `server.ts` → `api/index.ts` / `api/[...].ts` and only `/api
370
396
 
371
397
  Adapt script names / process runners as needed. **Invariant:**
372
398
 
373
- - **`npm start` / Production** = TomorrowOS Node only.
399
+ - **Local / v0 Preview `npm run dev`** = TomorrowOS `:3001` + Next `:3000` proxy.
400
+ - **Vercel Production** = Function `api/index.ts` (SDK `autoListen=false` when `VERCEL` is set).
374
401
  - **`build`** = no-op exit 0 (never `next build` for Production).
375
- - **Preview / v0** = Next on public port + TomorrowOS on internal port + proxy.
402
+ - **Do not** ship an empty `app/page.tsx` (v0 placeholder).
376
403
 
377
404
  ### Minimal dependencies (Production)
378
405
 
@@ -450,32 +477,37 @@ export default server;
450
477
 
451
478
  Load env with `dotenv` for local/`vercel dev`. On production Deploy, Vercel injects Environment Variables natively.
452
479
 
453
- ### Preview adapter (v0 / Next reverse proxy) — required when Preview is Next-hardwired
480
+ ### Preview adapter (v0 / Next reverse proxy) — required for Preview UI
454
481
 
455
- Detect Preview / v0 (Next already present, or Preview blank while TomorrowOS logs “listening”). Then:
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.
456
483
 
457
- 1. Keep a **minimal** Next app **isolated when possible** (e.g. under `preview/`) so Production Framework detection does not latch onto root `next.config.*`.
458
- 2. TomorrowOS listens on **`127.0.0.1:TOMORROWOS_INTERNAL_PORT`** (default **3001**).
459
- 3. Add a **catch-all** Next route (or middleware) that proxies HTTP to TomorrowOS:
484
+ **Required layout (cms-starter-v0):**
460
485
 
461
- ```ts
462
- // Example: preview app proxy handler
463
- // Forward method, headers, and body to http://127.0.0.1:3001/<path>
464
- // Return the upstream status, headers, and body to the browser.
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)
465
493
  ```
466
494
 
467
- Preferred UX: proxy **`/`** itself (rewrites) so the Preview URL shows the Control Panel at `/`, not only under `/api/...`.
495
+ **Agent must:**
468
496
 
469
- 4. Start **both** processes for Preview (`dev:preview` pattern above).
470
- 5. Document in the final summary: **Preview = Next shell + proxy; Publish = Node only.**
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).**
471
503
 
472
504
  **Forbidden:**
473
505
 
474
- - Making Next the Production CMS or the only server after Publish.
475
- - Deleting TomorrowOS `server.ts` because Preview needed Next.
476
- - Claiming setup complete if Production entrypoint is still `next start`.
477
- - Claiming setup complete if Production `GET /` is `404: NOT_FOUND`.
478
- - Creating root `api/index.*` + `routes` catch-all that exports a raw `http.Server` “to fix 404”.
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`
479
511
 
480
512
  ### `vercel.json` (Production — explicit Node, not Next)
481
513
 
@@ -533,7 +565,8 @@ If Vercel’s UI and this file disagree on bundling, **prefer whatever keeps a s
533
565
 
534
566
  ## Question 1 — Database (always)
535
567
 
536
- > **Do not** ask screen counts. Present all three options; recommend **Supabase** for Vercel fleets.
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.
537
570
 
538
571
  ### Step A — Ask storage choice
539
572
 
@@ -551,6 +584,8 @@ If Vercel’s UI and this file disagree on bundling, **prefer whatever keeps a s
551
584
 
552
585
  ### Step B — If **1 / Supabase**
553
586
 
587
+ **Immediately** (same turn as acknowledging their choice — do not ask Q2 yet) ask for the connection string:
588
+
554
589
  **Ask exactly (connection string only):**
555
590
 
556
591
  > Paste your Supabase Postgres connection string. I will store it as **`SUPABASE_URL`**.
@@ -563,17 +598,20 @@ If Vercel’s UI and this file disagree on bundling, **prefer whatever keeps a s
563
598
 
564
599
  **You must then (automatic — do NOT ask the user to type these):**
565
600
 
566
- 1. Set Vercel Env Vars:
601
+ 1. Set Vercel Env Vars **now** (open Env UI if available):
567
602
  - `SUPABASE_URL=<user pooler string>`
568
603
  - `TOMORROWOS_STORE=supabase` ← **agent sets this**
569
604
  - `DATABASE_SSL=true` ← **agent sets this** (unless user already said SSL is off)
570
- 2. Wire `server.ts` as in **Runtime & Vercel deploy rules**.
605
+ 2. Wire store in `cms-app.ts` / `server.ts` as in **Runtime & Vercel deploy rules**.
571
606
  3. Optional committed `.env.example` with **placeholders only**.
572
607
  4. **Do not** commit the real connection string.
573
608
  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**.
574
610
 
575
611
  ### Step C — If **2 / Neon**
576
612
 
613
+ **Immediately** ask for the connection string (do not ask Q2 yet):
614
+
577
615
  **Ask exactly (connection string only):**
578
616
 
579
617
  > Paste your **Neon pooled** Postgres connection string. I will store it as **`DATABASE_URL`**.
@@ -584,14 +622,15 @@ If Vercel’s UI and this file disagree on bundling, **prefer whatever keeps a s
584
622
 
585
623
  **You must then (automatic — do NOT ask the user to type these):**
586
624
 
587
- 1. Set Vercel Env Vars:
625
+ 1. Set Vercel Env Vars **now**:
588
626
  - `DATABASE_URL=<user pooled string>`
589
627
  - `TOMORROWOS_STORE=postgres` ← **agent sets this**
590
628
  - `DATABASE_SSL=true` ← **agent sets this**
591
- 2. Wire `server.ts` — `createTomorrowOSStore` uses `PostgresStore` for `postgres` / `supabase` drivers.
629
+ 2. Wire `createTomorrowOSStore` for Postgres.
592
630
  3. **Do not** commit the real connection string.
593
- 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.
631
+ 4. **Do not** ask the user to fill `TOMORROWOS_STORE` or `DATABASE_SSL`.
594
632
  5. If you see `ENETUNREACH` on `:5432`, switch to Neon’s **pooled** URL.
633
+ 6. **Only after Env Vars are saved** → ask **Question 2**.
595
634
 
596
635
  ### Step D — If **3 / SQLite**
597
636
 
@@ -604,8 +643,9 @@ Only proceed after the user explicitly confirms.
604
643
  **You must then (automatic):**
605
644
 
606
645
  1. Set `TOMORROWOS_STORE=sqlite` yourself (do not ask the user to type it).
607
- 2. Keep `sqlitePath` in `server.ts` for `data/tomorrowos.db`.
646
+ 2. Keep `sqlitePath` in `cms-app.ts` for `data/tomorrowos.db`.
608
647
  3. Warn again in the final summary that they should move to Supabase or Neon for real devices.
648
+ 4. **Then** ask **Question 2**.
609
649
 
610
650
  **Later in Question 3:** set `cms.hostingTarget` to **`"vercel"`** (all Q1 branches).
611
651
 
@@ -614,25 +654,26 @@ Only proceed after the user explicitly confirms.
614
654
  ## Question 2 — Media storage
615
655
 
616
656
  > **This question is only about media files (images/videos).**
617
- > 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.
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.
618
659
 
619
660
  ### Step A — Ask storage choice
620
661
 
621
- **Ask exactly:**
662
+ **Ask exactly (wording must match):**
622
663
 
623
664
  > How should playlist media (images/videos) be stored?
624
665
  >
625
- > **1. Cloudinary (recommended)** — durable public HTTPS URLs (`https://res.cloudinary.com/...`). Works out of the box with `@tomorrowos/sdk` auto-detection.
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.**
626
667
  >
627
- > **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.
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.
628
669
  >
629
- > **3. Local disk only** — `public/uploads` (OK for quick local tests; **not** for production Vercel fleets — files are ephemeral).
670
+ > **3. Local disk only** — `cms-panel/uploads` (OK for quick local tests; **not** for production Vercel fleets — files are ephemeral).
630
671
  >
631
672
  > Reply with **1**, **2**, or **3** (or “Cloudinary” / “Vercel Blob” / “local”).
632
673
 
633
- ### Step B — If **1 / Cloudinary** (same Question 2 **one Env popup**)
674
+ ### Step B — If **1 / Cloudinary** (configure **immediately** — one Env popup)
634
675
 
635
- > **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.
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.
636
677
 
637
678
  **Preferred:** Vercel Env Vars UI (or Agent secrets form) with these fields together:
638
679
 
@@ -658,10 +699,13 @@ Only proceed after the user explicitly confirms.
658
699
  2. **Do not** invent placeholders.
659
700
  3. **Do not** proceed to Question 3 until all three required vars exist.
660
701
  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**.
661
703
 
662
704
  The SDK auto-detects these env vars.
663
705
 
664
- ### Step C — If **2 / Vercel Blob** (same Question 2)
706
+ ### Step C — If **2 / Vercel Blob** (configure **immediately**)
707
+
708
+ **Immediately** (do not ask Q3 yet):
665
709
 
666
710
  **Ask exactly:**
667
711
 
@@ -689,46 +733,51 @@ import { put } from "@vercel/blob";
689
733
 
690
734
  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.
691
735
  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**.
692
737
 
693
738
  ### Step D — If **3 / local uploads**
694
739
 
695
- 1. `mkdir -p public/uploads`
696
- 2. Warn clearly: files may vanish on redeploy; players need stable HTTPS URLs — Cloudinary or Vercel Blob is strongly preferred for Vercel.
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**.
697
743
 
698
744
  ---
699
745
 
700
746
  ## Question 3 — Brand (`brand.json` only)
701
747
 
702
- > Updates **only** `brand.json`. Does **not** change Vercel project settings, Env Vars, or `server.ts` store wiring beyond what Q1–Q2 already required.
748
+ > Updates **only** `brand.json`. Does **not** change Vercel project settings, Env Vars, or store wiring beyond what Q1–Q2 already required.
703
749
  >
704
750
  > **IRON RULE — website URL ≠ build a website / login / auth CMS.**
705
- > 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).
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**).
706
752
  > **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**.
707
- > Default TomorrowOS Control Panel has **no login**. Keep it that way.
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.
708
755
 
709
756
  ### Step A — Ask branding input
710
757
 
711
758
  **Ask exactly:**
712
759
 
713
- > Let’s brand your TomorrowOS experience. You can answer in either way:
760
+ > Let’s brand your TomorrowOS experience. Choose **one**:
714
761
  >
715
- > **Option A — Website URL only (brand reference)**
716
- > Paste **one public website URL**. I will use it **only** to infer colours, fonts, name, tagline, and logo for **`brand.json`**.
717
- > I will **not** rebuild that website, add a login page, or change Control Panel features — unless you explicitly ask for login/auth later.
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.
718
765
  >
719
- > **Option B — Manual fields**
720
- > Provide:
721
- > 1. **Product / venue name**
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)
722
769
  > 2. **Tagline** (optional)
723
770
  > 3. **Primary colour** (hex, e.g. `#FF8A3D`)
724
- > 4. **Background colour** (hex, optional default `#FAFAF9`)
771
+ > 4. **Background colour** (hex — page / panel background)
725
772
  > 5. **Text colour** (hex, optional — default `#0A0908`)
726
773
  > 6. **Secondary / accent colour** (hex, optional)
727
- > 7. **Logo** — upload SVG/PNG into the project, or a URL I can fetch into `./assets/`
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).
728
777
 
729
778
  ### Step B — If the user gives **only a website URL** (Option A)
730
779
 
731
- **You must:**
780
+ **You must (immediately — do not defer brand write to “end of setup”):**
732
781
 
733
782
  1. **Fetch and inspect** the page (HTTP GET the URL; follow one redirect if needed). Do not invent colours.
734
783
  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).
@@ -743,7 +792,8 @@ import { put } from "@vercel/blob";
743
792
  - **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.
744
793
  4. **Contrast check:** if background is dark, set `activationScreen.theme` to `"dark"` and add `logoPathOnDark` when a light logo variant exists.
745
794
  5. **Infer `cms.useCase`** from page content when obvious (restaurant / retail / corporate / etc.); otherwise `"other"`.
746
- 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.
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).
747
797
  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.
748
798
 
749
799
  **Explicitly forbidden when the user only gave a URL (no login request):**
@@ -788,27 +838,31 @@ Validate mentally against `brand.schema.json`. Minimum:
788
838
 
789
839
  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://`).
790
840
 
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
+
791
843
  ---
792
844
 
793
- ## After all answersexecution checklist
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.
794
848
 
795
- ### A. Seed the project
849
+ ### A. Seed the project (do this early if the repo is empty — before or during Q1)
796
850
 
797
851
  ```bash
798
- npx @tomorrowos/sdk@latest init .
852
+ npx @tomorrowos/sdk@latest init . --hosting v0
799
853
  # If not empty and user confirms starter overwrite:
800
- # npx @tomorrowos/sdk@latest init . --force
854
+ # npx @tomorrowos/sdk@latest init . --hosting v0 --force
801
855
  ```
802
856
 
803
857
  Then (**Scheme A — mandatory for Publish**):
804
858
 
805
- 1. Keep **`server.ts`** at **project root** with top-level `listen()` + `export default server`.
806
- 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.
807
- 3. Write `vercel.json` per **Scheme A3** (`fluid`, `framework: null`, no-op `buildCommand` — **no** `startCommand`).
808
- 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.
809
- 5. Do **not** add root `api/` catch-all for raw `http.Server`.
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`.
810
864
  6. Do **not** add `.replit*`.
811
- 7. If Q2 = Vercel Blob: install `@vercel/blob` and wire the upload bridge.
865
+ 7. If Q2 = Vercel Blob: install `@vercel/blob` and wire the upload bridge (should already be started in Q2).
812
866
 
813
867
  ### B. Environment Variables (Vercel dashboard + local)
814
868
 
@@ -883,23 +937,26 @@ Tell the user:
883
937
 
884
938
  | Symptom | Likely cause | Fix |
885
939
  |---------|--------------|-----|
886
- | Publish `404: NOT_FOUND` | Framework Preset still **Next.js** / no Node start | Scheme A: Framework = **Other**, Start = `npm run start`, Output empty, Fluid on; redeploy |
887
- | Panel loads but **CMS unreachable** / `Unexpected token 'T', "The page c"...` | **`public/` deployed as static site** — no Node running | Scheme A: Output empty in dashboard, `start` in package.json; undo any `api/` migration; try **Path B** deploy |
888
- | Only `/api/status` returns JSON, `/status` is 404 | Wrong **api/ serverless** migration + broken rewrites | **A8 undo:** restore root `server.ts`, delete root `api/*`; never accept this as “fixed” |
889
- | `vercel.json` error: invalid `startCommand` | v0 schema rejects it | Remove from `vercel.json`; use `package.json` `start` + dashboard Start Command |
890
- | Agent moved server to `api/`, added `@vercel/node` handler | Off-protocol serverless refactor | **A8 undo** TomorrowOS needs one root `listen()` server, not per-request handlers |
891
- | `vercel inspect` shows Output = `public if it exists` | Framework=Other static default | Force Output Directory **empty** in dashboard; root `server.ts`; Path B redeploy |
940
+ | Publish `404: NOT_FOUND` | Framework Preset still **Next.js** / no Function | Scheme A: Framework = **Other**, Fluid on, `api/index.ts` + rewrites; redeploy |
941
+ | Panel loads but **CMS unreachable** / `Unexpected token 'T', "The page c"...` | **`public/` deployed as static site** — no Function | Rename to `cms-panel/`; Output empty; Scheme A Function entry; **Path B** if needed |
942
+ | Only `/api/status` returns JSON, `/status` is 404 | Missing **rewrites** to `/api` | Add Scheme A3 rewrite `/(.*) /api` (exclude existing `/api/`); redeploy |
943
+ | Control Panel OK, TV **could not connect** | WebSocket upgrade not reaching Function (200 HTML) | Fluid on; `export default server`; verify 101 on `/` and `/api`; check WebSockets permission |
944
+ | `vercel.json` error: invalid `startCommand` | v0 schema rejects it | Remove from `vercel.json`; Function entry does not need startCommand |
945
+ | 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
+ | `vercel inspect` shows Output = `public if it exists` | Framework=Other static default | Force Output Directory **empty**; use `cms-panel/`; Path B redeploy |
892
947
  | 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 |
893
- | Preview blank but logs show TomorrowOS listening | v0 Next intercepts public port | Add Next reverse proxy internal TomorrowOS port |
894
- | Preview works, Publish broken / Next-only | Production still on Next | Switch Production entry to `tsx server.ts` / `export default server` |
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 |
949
+ | Preview works, Publish broken / Next-only | Production still on Next | Switch Production to Fluid Function `api/index.ts` |
895
950
  | `ENETUNREACH` / IPv6 / `:5432` | Direct Postgres URL | Use **pooled** URL (Supabase `:6543` or Neon pooler) |
896
951
  | Control Panel OK, devices never stay paired after restart | SQLite / ephemeral disk | Use Supabase or Neon |
897
- | Uploads break / broken thumbs in prod | Local `public/uploads` | Cloudinary or Vercel Blob |
952
+ | Uploads break / broken thumbs in prod | Local uploads only | Cloudinary or Vercel Blob |
898
953
  | Blob uploads 401 / missing token | Blob not linked | Vercel Storage → Blob + `BLOB_READ_WRITE_TOKEN` |
899
- | WebSocket fails on Publish | Fluid off / static-only / Next as Production | Enable Fluid; sole Node `listen()` for Publish |
954
+ | WebSocket fails on Publish | Fluid off / static-only / Next as Production / no Function WS | Enable Fluid; Scheme A; test 101 upgrade |
900
955
  | Devices fail only on Preview | Next proxy cannot upgrade `wss` | Expected — pair on Publish URL |
901
956
  | Agent asked user to fill `TOMORROWOS_STORE` / `DATABASE_SSL` | Off-protocol | Agent must auto-set those after Neon/Supabase choice |
902
- | Agent asked Cloudinary key/secret in three chat turns | Off-protocol | Use **one Env popup** with all Cloudinary fields |
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 |
903
960
  | 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 |
904
961
  | 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` |
905
962
  | Secrets in git | Mistake | Rotate keys; move to Vercel Env Vars |
@@ -915,10 +972,11 @@ Tell the user:
915
972
  | Deploy config | `.replit`, artifact `kind=web` | `vercel.json` + Fluid; **Framework = Other**; **no** `.replit` |
916
973
  | `hostingTarget` | `"here"` | `"vercel"` |
917
974
  | Database | Supabase (primary) | Supabase **or Neon** (pooled); SQLite demo only |
918
- | Media | Cloudinary or Replit Object Storage | Cloudinary **or Vercel Blob** |
975
+ | Media | Cloudinary or Replit Object Storage | **Cloudinary (recommended)** or Vercel Blob (not Supabase Storage) |
919
976
  | Preview | Replit Preview / Autoscale Node | **Next shell + proxy** when v0 hardwires Next |
920
- | Publish | Long-lived Node | **Node `TomorrowOS.listen` only** (not Next); gate on no `404 NOT_FOUND` |
921
- | Brand from URL | Not in Replit protocol | **Option A** — infer **`brand.json` only**; never invent login |
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) |
922
980
 
923
981
  ---
924
982