@tomorrowos/sdk 0.9.24 → 0.9.26

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 CHANGED
@@ -73,8 +73,8 @@ IF any step contradicts Vercel, adapt to settings that Vercel can accept.
73
73
 
74
74
  That wizard asks: database (**Supabase** → **Neon** → SQLite demo), media (**Cloudinary** → **Vercel Blob** → local),
75
75
  branding (manual fields or **website URL → `brand.json` only**, no login unless asked). Preview may use a thin **Next.js reverse proxy**;
76
- **Publish stays pure Node** `TomorrowOS.listen` (not Next). Cloudinary credentials are collected one-at-a-time
77
- (cloud name API key API secret). Sets `cms.hostingTarget` to `"vercel"`.
76
+ **Publish stays pure Node** `TomorrowOS.listen` (Framework Preset = Other — gate on no `404 NOT_FOUND`). Cloudinary uses **one Env popup** (all fields).
77
+ Neon/Supabase: agent auto-sets `TOMORROWOS_STORE` + `DATABASE_SSL`. Sets `cms.hostingTarget` to `"vercel"`.
78
78
 
79
79
  ### Replit upgrade (existing CMS)
80
80
 
package/VERCEL_SETUP.md CHANGED
@@ -5,7 +5,7 @@
5
5
  >
6
6
  > **On Vercel, this file is the setup questionnaire** (not `REPLIT_SETUP.md`, not `LLM_PROMPT.md`).
7
7
  >
8
- > Protocol id: `vercel-setup/1.3` — pairs with `@tomorrowos/sdk` packages that ship `templates/cms-starter` and this file.
8
+ > Protocol id: `vercel-setup/1.5` — pairs with `@tomorrowos/sdk` packages that ship `templates/cms-starter` and this file.
9
9
 
10
10
  ## User trigger (copy-paste)
11
11
 
@@ -14,10 +14,12 @@ When a human pastes a prompt like this, **read this file and start Question 1 im
14
14
  ```text
15
15
  Follow @tomorrowos/sdk VERCEL_SETUP.md and set up my TomorrowOS CMS.
16
16
  Ask only the questions listed in VERCEL_SETUP.md, in order. Do not use LLM_PROMPT.md or REPLIT_SETUP.md.
17
+ Do not ask for OpenAI / Anthropic / any LLM API keys — TomorrowOS CMS setup does not require them.
17
18
  If any step contradicts Vercel, adapt to settings Vercel can accept (document the adaptation).
18
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.
19
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.
20
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), startCommand must run TomorrowOS Node, and GET / on the production URL must return the Control Panel (not 404 NOT_FOUND).
21
23
  ```
22
24
 
23
25
  Alternate triggers:
@@ -43,6 +45,7 @@ TomorrowOS CMS is **one Node process** that serves:
43
45
  | Required | Forbidden |
44
46
  |----------|-----------|
45
47
  | Single **Node HTTP server** entry (`tsx server.ts` / exported `http.Server` from `listen()`) | **Next.js as the Production CMS** (`next start`, Next App Router as the live app) |
48
+ | **Framework Preset = Other / `null`** (dashboard **and** project settings API) | Leaving Framework Preset as **Next.js** (causes `404: NOT_FOUND`) |
46
49
  | **Fluid compute** enabled (needed for WebSockets on Vercel) | Classic short-lived serverless with no WebSocket / no Fluid |
47
50
  | Durable store = **Postgres** (Supabase or Neon, pooled URL) | **SQLite / `data/*.db` as production store** on Vercel (ephemeral filesystem) |
48
51
  | Media = **Cloudinary** or **Vercel Blob** (durable HTTPS URLs) | Relying on local `public/uploads` alone for production fleets |
@@ -51,6 +54,34 @@ TomorrowOS CMS is **one Node process** that serves:
51
54
 
52
55
  **Publish / Production Deploy** must run **`TomorrowOS.listen(...)`** directly. Do **not** put Next.js in front of Production traffic.
53
56
 
57
+ ### Publish `404: NOT_FOUND` — mandatory prevention (read before every deploy)
58
+
59
+ The production URL showing:
60
+
61
+ ```text
62
+ 404: NOT_FOUND
63
+ Code: NOT_FOUND
64
+ ```
65
+
66
+ almost always means **Vercel still thinks this is a Next.js / static deploy**, not a Node TomorrowOS server.
67
+
68
+ **Before every Production deploy, you MUST verify all of the following:**
69
+
70
+ 1. **Project → Settings → General → Framework Preset = Other** (or unset / null).
71
+ If it still says **Next.js**, patch it via Vercel API / CLI / dashboard **before** redeploying.
72
+ `"framework": null` in `vercel.json` alone is **not enough** if the dashboard still says Next.js.
73
+ 2. **Install Command** = `npm install` / `pnpm install` / `yarn` (normal install).
74
+ 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).
75
+ **Never** run `next build` for Production.
76
+ 4. **Start / Dev Command for Production** = `npm run start` / `pnpm start` → **`tsx server.ts`** (or `.mts`) that calls `TomorrowOS.listen` and **`export default server`**.
77
+ 5. **Output Directory** = **empty / unset**. Do **not** set `outputDirectory: "."` as a fake static site.
78
+ 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.
79
+ 7. If Preview needs Next, keep Next files **isolated** so Production auto-detect cannot latch onto them:
80
+ - Prefer `preview/` (or similar) for `next.config.*`, `app/`, `pages/` used only by v0 Preview, **or**
81
+ - Ensure Production Framework Preset is forced to **Other** and Start Command never runs `next`.
82
+
83
+ **Setup is NOT complete** while Production `GET /` returns `404: NOT_FOUND`. Fix Framework Preset + Start Command and redeploy.
84
+
54
85
  ### Preview (v0 / Vercel Agent sandbox) — Next.js shell allowed
55
86
 
56
87
  v0 Preview is often a **Next.js runtime** hardwired to `next dev`. A raw `tsx server.ts` may listen successfully while Preview still shows a blank page, because the sandbox intercepts the public port with Next before requests reach TomorrowOS.
@@ -82,6 +113,17 @@ Ask **only** these questions, in **this exact order**:
82
113
 
83
114
  **That is the complete list.** Do **not** ask how many screens / devices.
84
115
 
116
+ **Forbidden extra questions (never ask, even if the user skips a step):**
117
+
118
+ - ❌ OpenAI / Anthropic / Gemini / any LLM API key (`sk-…`, etc.)
119
+ - ❌ “AI pairing”, “AI feature logic”, or inventing auth that needs an LLM key
120
+ - ❌ Asking the user to type `TOMORROWOS_STORE` or `DATABASE_SSL` after they already chose Neon/Supabase — **you set those automatically**
121
+ - ❌ Any secret not listed in Questions 1–3 (DB URL, media credentials, brand)
122
+
123
+ TomorrowOS pairing and Control Panel logic live in `@tomorrowos/sdk`. They do **not** call OpenAI.
124
+
125
+ If the user **skips** a Question 2 option, do **not** invent a substitute question. Re-state Question 2 choices, or proceed with a documented default (e.g. local uploads with a warning) — never pivot to LLM keys.
126
+
85
127
  If the user says “just set it up, don’t ask questions,” respond:
86
128
 
87
129
  > I need a few quick answers from VERCEL_SETUP.md (database — Supabase or Neon preferred — media storage, and branding). It takes about a minute and ensures the CMS works on Vercel.
@@ -92,10 +134,10 @@ Then ask **Question 1**.
92
134
 
93
135
  ## Hard rules
94
136
 
95
- 1. **Ask only VERCEL_SETUP.md questions 1–3.** Never use `LLM_PROMPT.md` or `REPLIT_SETUP.md` as the questionnaire.
96
- 2. **One question / one credential at a time.** For Cloudinary: ask **cloud name**, then **API key alone**, then **API secret alone** never one message that mixes key + secret.
97
- 3. **Do not invent** Cloudinary credentials, database URLs, Vercel Blob tokens, or brand colours.
98
- 4. **Postgres env naming:** prefer **`SUPABASE_URL`** when using Supabase; prefer **`DATABASE_URL`** (or `NEON_DATABASE_URL`) when using Neon. `createTomorrowOSStore` accepts `SUPABASE_URL` then `DATABASE_URL`.
137
+ 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.
138
+ 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.
139
+ 3. **Do not invent** Cloudinary credentials, database URLs, Vercel Blob tokens, brand colours, **or LLM API keys**.
140
+ 4. **Postgres env naming:** prefer **`SUPABASE_URL`** when using Supabase; prefer **`DATABASE_URL`** when using Neon. `createTomorrowOSStore` accepts `SUPABASE_URL` then `DATABASE_URL`.
99
141
  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).
100
142
  6. **Prefer `npx @tomorrowos/sdk init`** (or `templates/cms-starter`) as the seed. Do not rebuild pairing / WebSocket / playlist APIs.
101
143
  7. **Never commit secrets.** Use Vercel Environment Variables (+ optional non-secret flags in committed `.env.example`).
@@ -106,6 +148,9 @@ Then ask **Question 1**.
106
148
  12. **Preview:** if the environment is v0 / Next-hardwired, install the Next reverse-proxy shell (see **Preview adapter**). Do not leave Preview broken.
107
149
  13. After Q&A: **configure → install → run/deploy → minimal verify** (Control Panel at `/`). Do not run a long test suite.
108
150
  14. **No inventing CMS login.** A branding URL never means “build auth.” Only add login if the user **explicitly** requests it.
151
+ 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.
152
+ 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.
153
+ 17. **Publish gate:** do not mark setup complete while Production returns `404: NOT_FOUND`. Fix Framework Preset + Start Command first (see **Publish 404** section).
109
154
 
110
155
  **Question order:** Q1 → Q2 → Q3 → execution checklist.
111
156
 
@@ -115,11 +160,11 @@ Then ask **Question 1**.
115
160
 
116
161
  ### Production runtime (Publish)
117
162
 
118
- - Serve with **`tsx server.ts`** (or compiled Node that still calls `TomorrowOS.listen` with `staticRoot`).
163
+ - Serve with **`tsx server.ts`** (or `server.mts` / compiled Node that still calls `TomorrowOS.listen` with `staticRoot`).
119
164
  - Keep **`@tomorrowos/sdk`** and put **`tsx` in `dependencies`** (not only `devDependencies`) if production runs via `tsx`.
120
165
  - Node **20+**.
121
166
  - **Do not** replace the SDK with a hand-rolled server that drops WebSocket upgrades.
122
- - **Forbidden as the Production Deploy entrypoint:** `next start`, static export only, or any command that does not start `TomorrowOS.listen(...)`.
167
+ - **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(...)`.
123
168
 
124
169
  ### Expected `package.json` scripts
125
170
 
@@ -131,7 +176,7 @@ Then ask **Question 1**.
131
176
  "dev:tomorrowos": "cross-env PORT=3001 TOMORROWOS_INTERNAL_PORT=3001 tsx watch server.ts",
132
177
  "dev:next": "next dev -p 3000",
133
178
  "start": "tsx server.ts",
134
- "build": "echo \"Production is Node — no Next production build required\""
179
+ "build": "node -e \"process.exit(0)\""
135
180
  }
136
181
  }
137
182
  ```
@@ -139,6 +184,7 @@ Then ask **Question 1**.
139
184
  Adapt script names / process runners as needed. **Invariant:**
140
185
 
141
186
  - **`npm start` / Production** = TomorrowOS Node only.
187
+ - **`build`** = no-op exit 0 (never `next build` for Production).
142
188
  - **Preview / v0** = Next on public port + TomorrowOS on internal port + proxy.
143
189
 
144
190
  ### Minimal dependencies (Production)
@@ -160,7 +206,8 @@ Adapt script names / process runners as needed. **Invariant:**
160
206
  },
161
207
  "scripts": {
162
208
  "dev": "tsx watch server.ts",
163
- "start": "tsx server.ts"
209
+ "start": "tsx server.ts",
210
+ "build": "node -e \"process.exit(0)\""
164
211
  }
165
212
  }
166
213
  ```
@@ -173,6 +220,8 @@ For Preview shell only, add as needed: `next`, `react`, `react-dom`, and a proce
173
220
 
174
221
  ### `server.ts` pattern (Vercel-adapted — Production)
175
222
 
223
+ Keep the server at **project root** (e.g. `server.ts` / `server.mts`). Do **not** move Production into `api/` solely to satisfy Vercel routing myths.
224
+
176
225
  ```ts
177
226
  import "dotenv/config";
178
227
  import { readFileSync } from "fs";
@@ -218,12 +267,12 @@ Load env with `dotenv` for local/`vercel dev`. On production Deploy, Vercel inje
218
267
 
219
268
  Detect Preview / v0 (Next already present, or Preview blank while TomorrowOS logs “listening”). Then:
220
269
 
221
- 1. Keep a **minimal** Next app (App Router or Pages whichever the sandbox expects).
270
+ 1. Keep a **minimal** Next app **isolated when possible** (e.g. under `preview/`) so Production Framework detection does not latch onto root `next.config.*`.
222
271
  2. TomorrowOS listens on **`127.0.0.1:TOMORROWOS_INTERNAL_PORT`** (default **3001**).
223
272
  3. Add a **catch-all** Next route (or middleware) that proxies HTTP to TomorrowOS:
224
273
 
225
274
  ```ts
226
- // Example: app/api/tomorrowos-proxy/[...path]/route.ts or a rewrite + route handler
275
+ // Example: preview app proxy handler
227
276
  // Forward method, headers, and body to http://127.0.0.1:3001/<path>
228
277
  // Return the upstream status, headers, and body to the browser.
229
278
  ```
@@ -238,24 +287,41 @@ Preferred UX: proxy **`/`** itself (rewrites) so the Preview URL shows the Contr
238
287
  - Making Next the Production CMS or the only server after Publish.
239
288
  - Deleting TomorrowOS `server.ts` because Preview needed Next.
240
289
  - Claiming setup complete if Production entrypoint is still `next start`.
290
+ - Claiming setup complete if Production `GET /` is `404: NOT_FOUND`.
291
+ - Creating root `api/index.*` + `routes` catch-all that exports a raw `http.Server` “to fix 404”.
241
292
 
242
- ### `vercel.json` (recommended minimum Production)
293
+ ### `vercel.json` (Productionexplicit Node, not Next)
243
294
 
244
- Adapt to the project’s current Vercel Node-server / Fluid conventions. At minimum ensure:
295
+ Write `vercel.json` so Production cannot be mistaken for Next. Minimum:
245
296
 
246
297
  ```json
247
298
  {
248
- "fluid": true
299
+ "fluid": true,
300
+ "framework": null,
301
+ "installCommand": "npm install",
302
+ "buildCommand": "npm run build",
303
+ "outputDirectory": null
249
304
  }
250
305
  ```
251
306
 
252
- Also:
307
+ Use `pnpm` / `yarn` variants of install/build if that is the project’s package manager.
308
+
309
+ **Also force Project Settings (dashboard or Vercel API) — this is mandatory:**
310
+
311
+ | Setting | Required value |
312
+ |---------|----------------|
313
+ | Framework Preset | **Other** / null — **not Next.js** |
314
+ | Install Command | `npm install` (or pnpm/yarn) |
315
+ | Build Command | `npm run build` (no-op exit 0) |
316
+ | Output Directory | **empty** |
317
+ | Start Command | `npm run start` → `tsx server.ts` (or `.mts`) |
318
+ | Fluid compute | **On** |
319
+
320
+ If `next` / `next.config.*` exist for Preview and the dashboard still auto-selects Next.js:
253
321
 
254
- 1. Project **Fluid compute** enabled (required for WebSockets on Publish).
255
- 2. Framework Preset for **Production:** **Other** (or Node) — **not** Next.js as the live CMS.
256
- 3. Install / Start commands for **Production** must end in running the CMS Node server (e.g. `npm run start` → `tsx server.ts`).
257
- 4. Do **not** enable “Output Directory” static-only hosting for this CMS.
258
- 5. If the project also contains a Next Preview shell, configure Vercel so **Production Deploy ignores Next as the app** (Node `server.ts` / `export default server` remains the capture path). Document any dashboard setting you change.
322
+ 1. Patch Framework Preset to **Other** via API/CLI/dashboard.
323
+ 2. Redeploy.
324
+ 3. Hit Production `GET /` must be Control Panel HTML, not `404: NOT_FOUND`.
259
325
 
260
326
  If Vercel’s UI and this file disagree on bundling, **prefer whatever keeps a single long-lived Node HTTP server with WebSocket upgrade on `/` for Production**. Document the adaptation in the final summary.
261
327
 
@@ -270,6 +336,7 @@ If Vercel’s UI and this file disagree on bundling, **prefer whatever keeps a s
270
336
 
271
337
  - ❌ `.replit`, `.replit-artifact`
272
338
  - ❌ Next.js as the **Production** CMS (App Router replacing TomorrowOS)
339
+ - ❌ Root `api/` catch-all exporting raw `http.Server` to “fix” Publish 404
273
340
  - ❌ Separate “API project” without the Control Panel
274
341
  - ❌ Committing database passwords, Cloudinary secrets, or Blob tokens
275
342
 
@@ -295,52 +362,47 @@ If Vercel’s UI and this file disagree on bundling, **prefer whatever keeps a s
295
362
 
296
363
  ### Step B — If **1 / Supabase**
297
364
 
298
- **Ask exactly:**
365
+ **Ask exactly (connection string only):**
299
366
 
300
- > Paste your Supabase Postgres connection string. I will store it as the Vercel Environment Variable **`SUPABASE_URL`**.
367
+ > Paste your Supabase Postgres connection string. I will store it as **`SUPABASE_URL`**.
301
368
  >
302
369
  > In Supabase: **Project Settings → Database → Connection string → Connection pooling** (Session mode).
303
370
  > Preferred shape:
304
371
  > `postgresql://postgres.[PROJECT]:[PASSWORD]@aws-0-[REGION].pooler.supabase.com:6543/postgres`
305
372
  >
306
- > **Do not** use the direct host `db.*.supabase.co:5432` for Vercel — sandbox/serverless often fails with `ENETUNREACH` (commonly on IPv6).
307
- >
308
- > Confirm SSL is required (usually **yes** for Supabase).
373
+ > **Do not** use the direct host `db.*.supabase.co:5432` for Vercel.
309
374
 
310
- **You must then:**
375
+ **You must then (automatic — do NOT ask the user to type these):**
311
376
 
312
- 1. Set Vercel Env Vars (Production + Preview + Development as appropriate):
313
- - `SUPABASE_URL=<pooler string>`
314
- - `TOMORROWOS_STORE=supabase`
315
- - `DATABASE_SSL=true` (unless user explicitly disables SSL)
377
+ 1. Set Vercel Env Vars:
378
+ - `SUPABASE_URL=<user pooler string>`
379
+ - `TOMORROWOS_STORE=supabase` ← **agent sets this**
380
+ - `DATABASE_SSL=true` ← **agent sets this** (unless user already said SSL is off)
316
381
  2. Wire `server.ts` as in **Runtime & Vercel deploy rules**.
317
- 3. Optional committed `.env.example` with **placeholders only** (no real password).
382
+ 3. Optional committed `.env.example` with **placeholders only**.
318
383
  4. **Do not** commit the real connection string.
319
- 5. **Configuration over probing:** if local/`vercel dev` still shows `ENETUNREACH` / IPv6 errors, verify the string is the **pooler** (`pooler.supabase.com` + `:6543`). Treat remaining sandbox DNS issues as non-fatal when Production deploy Env Vars are correct — but **do** fix an obvious direct `:5432` URL before finishing.
384
+ 5. **Do not** open a second form asking for `TOMORROWOS_STORE` or `DATABASE_SSL`.
320
385
 
321
386
  ### Step C — If **2 / Neon**
322
387
 
323
- **Ask exactly:**
388
+ **Ask exactly (connection string only):**
324
389
 
325
- > Paste your **Neon pooled** Postgres connection string. I will store it as **`DATABASE_URL`** (or `NEON_DATABASE_URL` if you prefer — wire whichever you set in `server.ts`).
390
+ > Paste your **Neon pooled** Postgres connection string. I will store it as **`DATABASE_URL`**.
326
391
  >
327
- > In Neon: **Dashboard → Connection details → Pooled connection** (enable pooling).
392
+ > In Neon: **Dashboard → Connection details → Pooled connection**.
328
393
  > Typical shape:
329
- > `postgresql://[user]:[password]@[endpoint].pooler.[region].aws.neon.tech/[dbname]?sslmode=require`
330
- >
331
- > **Do not** use the direct (non-pooled) connection string for Vercel serverless unless Neon docs explicitly say otherwise for your plan.
332
- >
333
- > Confirm SSL (`sslmode=require`) is on (usually **yes**).
394
+ > `postgresql://[user]:[password]@[endpoint]-pooler.[region].aws.neon.tech/[dbname]?sslmode=require`
334
395
 
335
- **You must then:**
396
+ **You must then (automatic — do NOT ask the user to type these):**
336
397
 
337
398
  1. Set Vercel Env Vars:
338
- - `DATABASE_URL=<pooled Neon string>` (or `NEON_DATABASE_URL` + read it in `server.ts`)
339
- - `TOMORROWOS_STORE=postgres`
340
- - `DATABASE_SSL=true` (unless user explicitly disables SSL)
399
+ - `DATABASE_URL=<user pooled string>`
400
+ - `TOMORROWOS_STORE=postgres` ← **agent sets this**
401
+ - `DATABASE_SSL=true` **agent sets this**
341
402
  2. Wire `server.ts` — `createTomorrowOSStore` uses `PostgresStore` for `postgres` / `supabase` drivers.
342
403
  3. **Do not** commit the real connection string.
343
- 4. Same pooler guidance as Supabase: if you see `ENETUNREACH` on `:5432`, switch to Neon’s **pooled** URL.
404
+ 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.
405
+ 5. If you see `ENETUNREACH` on `:5432`, switch to Neon’s **pooled** URL.
344
406
 
345
407
  ### Step D — If **3 / SQLite**
346
408
 
@@ -350,9 +412,9 @@ If Vercel’s UI and this file disagree on bundling, **prefer whatever keeps a s
350
412
 
351
413
  Only proceed after the user explicitly confirms.
352
414
 
353
- **You must then:**
415
+ **You must then (automatic):**
354
416
 
355
- 1. Set `TOMORROWOS_STORE=sqlite` (or omit `DATABASE_URL` / `SUPABASE_URL` so the factory defaults to SQLite).
417
+ 1. Set `TOMORROWOS_STORE=sqlite` yourself (do not ask the user to type it).
356
418
  2. Keep `sqlitePath` in `server.ts` for `data/tomorrowos.db`.
357
419
  3. Warn again in the final summary that they should move to Supabase or Neon for real devices.
358
420
 
@@ -362,6 +424,9 @@ Only proceed after the user explicitly confirms.
362
424
 
363
425
  ## Question 2 — Media storage
364
426
 
427
+ > **This question is only about media files (images/videos).**
428
+ > 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.
429
+
365
430
  ### Step A — Ask storage choice
366
431
 
367
432
  **Ask exactly:**
@@ -376,46 +441,33 @@ Only proceed after the user explicitly confirms.
376
441
  >
377
442
  > Reply with **1**, **2**, or **3** (or “Cloudinary” / “Vercel Blob” / “local”).
378
443
 
379
- ### Step B — If **1 / Cloudinary** (same Question 2 — sequential prompts)
380
-
381
- > **Critical UX rule:** Collect Cloudinary credentials in **separate messages**. Never ask for API key and API secret in the same message or the same form field group.
382
-
383
- #### B1 — Cloud name (ask alone)
384
-
385
- **Ask exactly:**
386
-
387
- > Paste your Cloudinary **Cloud name** only (`CLOUDINARY_CLOUD_NAME`).
388
- > Find it at [cloudinary.com/console](https://cloudinary.com/console) → Dashboard.
389
-
390
- Save as Vercel Env Var `CLOUDINARY_CLOUD_NAME`. Do **not** ask for key or secret yet.
444
+ ### Step B — If **1 / Cloudinary** (same Question 2 — **one Env popup**)
391
445
 
392
- #### B2 API key (ask alone after B1 is saved)
446
+ > **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.
393
447
 
394
- **Ask exactly:**
395
-
396
- > Paste your Cloudinary **API Key** only (`CLOUDINARY_API_KEY`).
397
- > Do not paste the API Secret in this step.
398
-
399
- Save as Vercel Env Var `CLOUDINARY_API_KEY`. Do **not** ask for the secret in this message.
400
-
401
- #### B3 — API secret (ask alone — after B2 is saved)
402
-
403
- **Ask exactly:**
448
+ **Preferred:** Vercel Env Vars UI (or Agent secrets form) with these fields together:
404
449
 
405
- > Paste your Cloudinary **API Secret** only (`CLOUDINARY_API_SECRET`).
406
- > This is the last Cloudinary credential.
450
+ | Field | Env name | Required |
451
+ |-------|----------|----------|
452
+ | Cloud name | `CLOUDINARY_CLOUD_NAME` | Yes |
453
+ | API Key | `CLOUDINARY_API_KEY` | Yes |
454
+ | API Secret | `CLOUDINARY_API_SECRET` | Yes |
455
+ | Folder (optional) | `CLOUDINARY_FOLDER` | No (e.g. `tomorrowos`) |
407
456
 
408
- Save as Vercel Env Var `CLOUDINARY_API_SECRET`.
457
+ **If a multi-field popup is unavailable, ask once in a single message** (still not three turns):
409
458
 
410
- Optional fourth message (only if useful):
411
-
412
- > Optional: Cloudinary folder name (`CLOUDINARY_FOLDER`, e.g. `tomorrowos`). Press Enter to skip.
459
+ > Paste Cloudinary credentials from [cloudinary.com/console](https://cloudinary.com/console) (I will store them as Vercel Env Vars):
460
+ >
461
+ > 1. `CLOUDINARY_CLOUD_NAME`
462
+ > 2. `CLOUDINARY_API_KEY`
463
+ > 3. `CLOUDINARY_API_SECRET`
464
+ > 4. optional `CLOUDINARY_FOLDER`
413
465
 
414
466
  **You must then:**
415
467
 
416
- 1. Confirm all three required Env Vars exist: `CLOUDINARY_CLOUD_NAME`, `CLOUDINARY_API_KEY`, `CLOUDINARY_API_SECRET`.
468
+ 1. Save all three required Env Vars for Production + Preview + Development as appropriate.
417
469
  2. **Do not** invent placeholders.
418
- 3. **Do not** proceed to Question 3 until all three exist.
470
+ 3. **Do not** proceed to Question 3 until all three required vars exist.
419
471
  4. **Do not** run a formal Cloudinary upload test unless the user asks.
420
472
 
421
473
  The SDK auto-detects these env vars.
@@ -433,7 +485,7 @@ The SDK auto-detects these env vars.
433
485
 
434
486
  **You must then:**
435
487
 
436
- 1. Set Vercel Env Var: `BLOB_READ_WRITE_TOKEN=<token>` (if not auto-injected).
488
+ 1. Set Vercel Env Var: `BLOB_READ_WRITE_TOKEN=<token>` (if not auto-injected). Prefer the Env popup when available.
437
489
  2. `npm install @vercel/blob` (add to `dependencies`).
438
490
  3. Wire uploads so media returns **absolute HTTPS Blob URLs** stored in `uploaded_assets` (players need stable public URLs).
439
491
  - The SDK **natively auto-detects Cloudinary** today. For Vercel Blob, add a thin upload bridge in the CMS project (e.g. custom route or middleware that calls `put()` from `@vercel/blob` and persists the returned `url` the same way Cloudinary URLs are stored).
@@ -561,26 +613,26 @@ npx @tomorrowos/sdk@latest init .
561
613
 
562
614
  Then:
563
615
 
564
- 1. Keep TomorrowOS `server.ts` as the **Production** app (**export `listen()` HTTP server**).
565
- 2. If the environment is **v0 / Next Preview**, add the **Preview adapter** (Next shell + reverse proxy + internal TomorrowOS port). Do **not** make Next the Production CMS.
566
- 3. Add/adjust `vercel.json` with **`fluid: true`** (or enable Fluid in the dashboard) for Publish.
567
- 4. Ensure Production start command is **`npm run start` → `tsx server.ts`** (or equivalent Node), **not** `next start`.
568
- 5. Do **not** add `.replit*`.
569
- 6. If Q2 = Vercel Blob: install `@vercel/blob` and wire the upload bridge.
616
+ 1. Keep TomorrowOS `server.ts` / `server.mts` at **root** as the **Production** app (**export `listen()` HTTP server**).
617
+ 2. If the environment is **v0 / Next Preview**, add the **Preview adapter** (Next shell + reverse proxy + internal TomorrowOS port). Isolate Next configs when possible. Do **not** make Next the Production CMS.
618
+ 3. Write `vercel.json` with **`fluid: true`**, **`framework: null`**, no-op **`buildCommand`**, and **empty outputDirectory**.
619
+ 4. **Force Project Settings:** Framework Preset = **Other**, Start Command = `npm run start` (Node), Fluid on.
620
+ 5. Ensure Production start command is **`tsx server.ts`** (or `.mts`), **not** `next start`.
621
+ 6. Do **not** add root `api/` catch-all for raw `http.Server`.
622
+ 7. Do **not** add `.replit*`.
623
+ 8. If Q2 = Vercel Blob: install `@vercel/blob` and wire the upload bridge.
570
624
 
571
625
  ### B. Environment Variables (Vercel dashboard + local)
572
626
 
573
- | Name | Required | Notes |
574
- |------|----------|--------|
575
- | `SUPABASE_URL` | If Q1 = Supabase | **Session pooler** `:6543` |
576
- | `DATABASE_URL` | If Q1 = Neon | **Pooled** Neon string |
577
- | `TOMORROWOS_STORE` | Yes | `supabase`, `postgres`, or `sqlite` |
578
- | `DATABASE_SSL` | Yes (typical) | `true` |
579
- | `CLOUDINARY_CLOUD_NAME` | If Q2 = Cloudinary | Collected alone (B1) |
580
- | `CLOUDINARY_API_KEY` | If Q2 = Cloudinary | Collected alone after cloud name (B2) |
581
- | `CLOUDINARY_API_SECRET` | If Q2 = Cloudinary | Collected alone after API key (B3) |
582
- | `BLOB_READ_WRITE_TOKEN` | If Q2 = Vercel Blob | From Vercel Blob store |
583
- | `TOMORROWOS_INTERNAL_PORT` | Preview only | e.g. `3001` when Next owns `:3000` |
627
+ | Name | Required | Who fills it |
628
+ |------|----------|--------------|
629
+ | `SUPABASE_URL` | If Q1 = Supabase | User pastes URL |
630
+ | `DATABASE_URL` | If Q1 = Neon | User pastes URL |
631
+ | `TOMORROWOS_STORE` | Yes | **Agent auto-sets** (`supabase` / `postgres` / `sqlite`) |
632
+ | `DATABASE_SSL` | Yes for Postgres | **Agent auto-sets** `true` |
633
+ | `CLOUDINARY_*` | If Q2 = Cloudinary | User via **one Env popup** (all fields) |
634
+ | `BLOB_READ_WRITE_TOKEN` | If Q2 = Vercel Blob | User / Blob link |
635
+ | `TOMORROWOS_INTERNAL_PORT` | Preview only | Agent (e.g. `3001`) |
584
636
  | `PORT` | Optional | Vercel injects for Production |
585
637
 
586
638
  Mirror non-secrets in `.env` for local `npm run dev` if helpful; never commit secrets.
@@ -606,12 +658,15 @@ vercel --prod
606
658
  # or git push → Vercel Git integration
607
659
  ```
608
660
 
609
- Before finishing, verify Production settings:
661
+ **Publish acceptance checklist (all must pass):**
610
662
 
611
- 1. Start / entry = **Node `TomorrowOS.listen`** (`npm run start` / exported `server`)
612
- 2. **Not** `next start` as Production
663
+ 1. Framework Preset = **Other** / null (dashboard confirms not Next.js)
664
+ 2. Start Command = Node `TomorrowOS.listen` (`npm run start`)
613
665
  3. Fluid compute on
614
- 4. Env Vars present for Production environment
666
+ 4. Env Vars present for Production
667
+ 5. Production `GET /` returns Control Panel HTML — **not** `404: NOT_FOUND`
668
+
669
+ If step 5 fails: do **not** invent `api/` + `routes`. Patch Framework Preset + Start Command, redeploy, re-check `/`.
615
670
 
616
671
  Tell the user:
617
672
 
@@ -624,7 +679,7 @@ Tell the user:
624
679
  **Do:**
625
680
 
626
681
  1. Preview: Control Panel HTML at `/` (via Next proxy if applicable)
627
- 2. Publish: Control Panel HTML at `/` from **Node** TomorrowOS
682
+ 2. Publish: Control Panel HTML at `/` from **Node** TomorrowOS (block on `404: NOT_FOUND`)
628
683
  3. Server status: Media OK when Cloudinary or Blob configured
629
684
  4. Database: if ERROR shows **`ENETUNREACH` … IPv6 … `:5432`**, switch to **pooled** URL (Supabase `:6543` or Neon pooler) and redeploy — call this out explicitly to the user
630
685
  5. Fluid compute on for Production
@@ -637,6 +692,8 @@ Tell the user:
637
692
 
638
693
  | Symptom | Likely cause | Fix |
639
694
  |---------|--------------|-----|
695
+ | Publish `404: NOT_FOUND` | Framework Preset still **Next.js** / no Node start | Set Framework = **Other**, Start = `npm run start`, Fluid on; redeploy. Do **not** add root `api/` catch-all |
696
+ | Build runs `next build` / missing routes-manifest | Next auto-detected from `next.config` / deps | Force Framework null/Other; `build` = no-op; isolate Preview Next files |
640
697
  | Preview blank but logs show TomorrowOS listening | v0 Next intercepts public port | Add Next reverse proxy → internal TomorrowOS port |
641
698
  | Preview works, Publish broken / Next-only | Production still on Next | Switch Production entry to `tsx server.ts` / `export default server` |
642
699
  | `ENETUNREACH` / IPv6 / `:5432` | Direct Postgres URL | Use **pooled** URL (Supabase `:6543` or Neon pooler) |
@@ -645,11 +702,12 @@ Tell the user:
645
702
  | Blob uploads 401 / missing token | Blob not linked | Vercel Storage → Blob + `BLOB_READ_WRITE_TOKEN` |
646
703
  | WebSocket fails on Publish | Fluid off / static-only / Next as Production | Enable Fluid; sole Node `listen()` for Publish |
647
704
  | Devices fail only on Preview | Next proxy cannot upgrade `wss` | Expected — pair on Publish URL |
648
- | Agent mixed Cloudinary key+secret in one prompt | Violated Step B | Re-ask key alone, then secret alone |
705
+ | Agent asked user to fill `TOMORROWOS_STORE` / `DATABASE_SSL` | Off-protocol | Agent must auto-set those after Neon/Supabase choice |
706
+ | Agent asked Cloudinary key/secret in three chat turns | Off-protocol | Use **one Env popup** with all Cloudinary fields |
707
+ | 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 |
708
+ | 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` |
649
709
  | Secrets in git | Mistake | Rotate keys; move to Vercel Env Vars |
650
710
  | Replit files present | Copied wrong protocol | Delete `.replit*`; use this file |
651
- | Brand colours wrong from URL | SPA / blocked fetch | Manual Option B for colours + logo |
652
- | Agent built a login page after user pasted a URL | Misread brand reference as product scope | Remove login; keep starter Control Panel; apply URL only to `brand.json` |
653
711
 
654
712
  ---
655
713
 
@@ -658,12 +716,12 @@ Tell the user:
658
716
  | Topic | Replit | Vercel |
659
717
  |-------|--------|--------|
660
718
  | Secrets | Replit Secrets | Vercel Environment Variables |
661
- | Deploy config | `.replit`, artifact `kind=web` | `vercel.json` + Fluid; **no** `.replit` |
719
+ | Deploy config | `.replit`, artifact `kind=web` | `vercel.json` + Fluid; **Framework = Other**; **no** `.replit` |
662
720
  | `hostingTarget` | `"here"` | `"vercel"` |
663
721
  | Database | Supabase (primary) | Supabase **or Neon** (pooled); SQLite demo only |
664
722
  | Media | Cloudinary or Replit Object Storage | Cloudinary **or Vercel Blob** |
665
723
  | Preview | Replit Preview / Autoscale Node | **Next shell + proxy** when v0 hardwires Next |
666
- | Publish | Long-lived Node | **Node `TomorrowOS.listen` only** (not Next) |
724
+ | Publish | Long-lived Node | **Node `TomorrowOS.listen` only** (not Next); gate on no `404 NOT_FOUND` |
667
725
  | Brand from URL | Not in Replit protocol | **Option A** — infer **`brand.json` only**; never invent login |
668
726
 
669
727
  ---
@@ -673,18 +731,19 @@ Tell the user:
673
731
  After setup, report:
674
732
 
675
733
  1. **Live URL** (Production Publish)
676
- 2. **Preview note:** Next proxy shell used / not needed
677
- 3. **Env Vars set** (names only never values)
678
- 4. **Store:** Supabase pooler / Neon pooled / SQLite (with warning)
679
- 5. **Media:** Cloudinary, Vercel Blob, or local (with warning)
680
- 6. **Brand:** name + `hostingTarget: vercel` (+ note if derived from website URL)
681
- 7. **Runtime split:** Preview = ; Publish = Node TomorrowOS (confirm **not** Next)
682
- 8. **Open action:** if Database still ERROR, paste the pooler instruction again
734
+ 2. **Publish health:** Framework Preset = Other; Start = Node; `GET /` OK (not 404)
735
+ 3. **Preview note:** Next proxy shell used / not needed
736
+ 4. **Env Vars set** (names only never values); note which were auto-set (`TOMORROWOS_STORE`, `DATABASE_SSL`)
737
+ 5. **Store:** Supabase pooler / Neon pooled / SQLite (with warning)
738
+ 6. **Media:** Cloudinary, Vercel Blob, or local (with warning)
739
+ 7. **Brand:** name + `hostingTarget: vercel` (+ note if derived from website URL)
740
+ 8. **Runtime split:** Preview = ; Publish = Node TomorrowOS (confirm **not** Next)
741
+ 9. **Open action:** if Database still ERROR, paste the pooler instruction again
683
742
 
684
743
  Protocol complete when:
685
744
 
686
745
  - **Preview** shows the Control Panel at `/` (via Next proxy if required), **and**
687
- - **Publish** is configured for pure Node `TomorrowOS.listen` with Fluid, **and**
746
+ - **Publish** returns Control Panel at `/` from pure Node `TomorrowOS.listen` with Fluid (not `404: NOT_FOUND`), **and**
688
747
  - Q1–Q3 configuration is saved
689
748
 
690
749
  — even if a sandbox DB probe still flakes **after** the pooled URL is correctly set for Production.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tomorrowos/sdk",
3
- "version": "0.9.24",
3
+ "version": "0.9.26",
4
4
  "description": "TomorrowOS CMS server SDK - WebSocket transport, pairing, device commands, optional static CMS UI. Includes CLI (tomorrowos init / build) and starter templates.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "my-cms",
3
- "version": "0.9.24",
3
+ "version": "0.9.26",
4
4
  "description": "CMS server on @tomorrowos/sdk. Add your UI (React, static files, etc.) alongside this server.",
5
5
  "private": true,
6
6
  "type": "module",
@@ -13,7 +13,7 @@
13
13
  "build-player": "tomorrowos build --platform tizen"
14
14
  },
15
15
  "dependencies": {
16
- "@tomorrowos/sdk": "^0.9.24",
16
+ "@tomorrowos/sdk": "^0.9.26",
17
17
  "dotenv": "^17.2.3",
18
18
  "tsx": "^4.19.0"
19
19
  },