@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/README.md +5 -4
- package/VERCEL_SETUP.md +266 -208
- package/dist/tomorrowos.d.ts +13 -1
- package/dist/tomorrowos.d.ts.map +1 -1
- package/dist/tomorrowos.js +45 -9
- package/package.json +1 -1
- package/templates/cms-starter/package.json +2 -2
- package/templates/cms-starter-v0/README.md +10 -35
- package/templates/cms-starter-v0/api/index.ts +10 -0
- package/templates/cms-starter-v0/app/[[...slug]]/route.ts +89 -0
- package/templates/cms-starter-v0/app/layout.tsx +15 -0
- package/templates/cms-starter-v0/cms-app.ts +75 -0
- package/templates/cms-starter-v0/next-env.d.ts +4 -0
- package/templates/cms-starter-v0/next.config.mjs +15 -0
- package/templates/cms-starter-v0/package.json +11 -11
- package/templates/cms-starter-v0/server.ts +3 -72
- package/templates/cms-starter-v0/tsconfig.json +2 -1
- package/templates/cms-starter-v0/vercel.json +11 -1
- package/templates/cms-starter-v0/preview/app/layout.tsx +0 -11
- package/templates/cms-starter-v0/preview/app/page.tsx +0 -3
- package/templates/cms-starter-v0/preview/next-env.d.ts +0 -2
- package/templates/cms-starter-v0/preview/next.config.mjs +0 -18
- package/templates/cms-starter-v0/preview/tsconfig.json +0 -20
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.
|
|
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
|
-
|
|
20
|
-
Question
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
Do not
|
|
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 disk — NEVER 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. **
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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) |
|
|
135
|
-
| 2 | **Question 2** — Media
|
|
136
|
-
| 3 | **Question 3** — Brand
|
|
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
|
-
**
|
|
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. **
|
|
163
|
-
3. **
|
|
164
|
-
4. **
|
|
165
|
-
5. **
|
|
166
|
-
6. **
|
|
167
|
-
7. **
|
|
168
|
-
8. **
|
|
169
|
-
9. **
|
|
170
|
-
10. **
|
|
171
|
-
11. **
|
|
172
|
-
12. **
|
|
173
|
-
13. After
|
|
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**
|
|
178
|
-
18. **Scheme A is doc-driven:**
|
|
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 docs — Replit starter stays unchanged.
|
|
179
193
|
|
|
180
|
-
**Question order:** Q1 → Q2 → Q3 →
|
|
194
|
+
**Question order:** Q1 → **configure Q1** → Q2 → **configure Q2** → Q3 → **write brand.json** → deploy checklist.
|
|
181
195
|
|
|
182
196
|
---
|
|
183
197
|
|
|
184
|
-
## Scheme A —
|
|
198
|
+
## Scheme A — Vercel Functions + WebSockets (mandatory for Production)
|
|
185
199
|
|
|
186
|
-
> **Goal:** Preview may use Next + proxy; **Publish** runs **
|
|
187
|
-
> **Scope:**
|
|
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.
|
|
194
|
-
|
|
|
195
|
-
| WebSocket | Optional / may fail in Preview | Required
|
|
196
|
-
| Next files |
|
|
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
|
-
**
|
|
217
|
+
**Production (Vercel / v0 starter):**
|
|
201
218
|
|
|
202
219
|
```
|
|
203
|
-
|
|
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
|
-
|
|
228
|
+
cms-panel/
|
|
206
229
|
brand.json
|
|
207
|
-
|
|
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
|
-
- ❌
|
|
226
|
-
- ❌
|
|
227
|
-
- ❌
|
|
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
|
-
|
|
241
|
+
**`package.json` scripts:**
|
|
230
242
|
|
|
231
243
|
```json
|
|
232
244
|
{
|
|
233
245
|
"scripts": {
|
|
234
|
-
"dev": "
|
|
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": "
|
|
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 —
|
|
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
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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
|
-
|
|
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
|
-
|
|
275
|
+
SDK behaviour (0.9.30+):
|
|
257
276
|
|
|
258
|
-
-
|
|
259
|
-
-
|
|
260
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
**
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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`
|
|
291
|
-
| Build Command | `npm run build` (no-op
|
|
292
|
-
| **Output Directory** | **empty
|
|
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
|
-
|
|
326
|
+
### A5 — Publish acceptance (all must pass)
|
|
297
327
|
|
|
298
|
-
|
|
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
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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
|
-
|
|
341
|
+
4. Control Panel does **not** show CMS unreachable / `Unexpected token 'T'`
|
|
342
|
+
5. Fluid on; Framework = Other
|
|
319
343
|
|
|
320
|
-
|
|
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
|
-
|
|
346
|
+
### A6 — What Scheme A does **not** require
|
|
323
347
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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
|
-
|
|
353
|
+
### A7 — If v0 Publish still fails (Path B / C)
|
|
330
354
|
|
|
331
|
-
|
|
355
|
+
**Path B:** Deploy with `vercel --prod` or Git integration (full Vercel), same Scheme A layout.
|
|
332
356
|
|
|
333
|
-
|
|
357
|
+
**Path C:** Host CMS on **Railway / Fly.io / Replit** (long-lived Node). Use Vercel only for Preview if needed.
|
|
334
358
|
|
|
335
|
-
|
|
359
|
+
### A8 — Migrate an existing broken Vercel project
|
|
336
360
|
|
|
337
|
-
1.
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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
|
|
351
|
-
- Keep
|
|
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
|
|
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": "
|
|
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
|
-
-
|
|
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
|
-
- **
|
|
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
|
|
480
|
+
### Preview adapter (v0 / Next reverse proxy) — required for Preview UI
|
|
454
481
|
|
|
455
|
-
|
|
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
|
-
|
|
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
|
-
```
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
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
|
-
|
|
495
|
+
**Agent must:**
|
|
468
496
|
|
|
469
|
-
|
|
470
|
-
|
|
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
|
-
-
|
|
475
|
-
-
|
|
476
|
-
-
|
|
477
|
-
- Claiming
|
|
478
|
-
-
|
|
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 `
|
|
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
|
|
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 `
|
|
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.
|
|
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** — `
|
|
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** (
|
|
674
|
+
### Step B — If **1 / Cloudinary** (configure **immediately** — one Env popup)
|
|
634
675
|
|
|
635
|
-
> **Critical UX rule:**
|
|
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** (
|
|
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. `
|
|
696
|
-
2. Warn clearly: files may vanish on redeploy; players need stable HTTPS URLs — Cloudinary
|
|
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
|
|
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.
|
|
760
|
+
> Let’s brand your TomorrowOS experience. Choose **one**:
|
|
714
761
|
>
|
|
715
|
-
> **Option A — Website URL
|
|
716
|
-
> Paste **one public website URL**. I will use it **only** to
|
|
717
|
-
> I will **not** rebuild that website
|
|
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
|
-
>
|
|
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
|
|
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)
|
|
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
|
|
845
|
+
## After Q1–Q3 configured — final 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.
|
|
806
|
-
2. If Preview needs Next:
|
|
807
|
-
3. Write `vercel.json` per **Scheme A3** (`fluid`, `
|
|
808
|
-
4. **Force Project Settings** per **Scheme A4** (Framework = Other, Output Directory empty,
|
|
809
|
-
5. Do **not** add
|
|
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
|
|
887
|
-
| Panel loads but **CMS unreachable** / `Unexpected token 'T', "The page c"...` | **`public/` deployed as static site** — no
|
|
888
|
-
| Only `/api/status` returns JSON, `/status` is 404 |
|
|
889
|
-
|
|
|
890
|
-
|
|
|
891
|
-
|
|
|
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
|
|
894
|
-
| Preview works, Publish broken / Next-only | Production still on Next | Switch Production
|
|
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
|
|
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;
|
|
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
|
|
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 | **
|
|
921
|
-
| Brand from URL | Not in Replit protocol | **Option A** —
|
|
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
|
|