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