@tomorrowos/sdk 0.9.25 → 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.4` — 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
 
@@ -19,6 +19,7 @@ If any step contradicts Vercel, adapt to settings Vercel can accept (document th
19
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
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
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).
22
23
  ```
23
24
 
24
25
  Alternate triggers:
@@ -44,6 +45,7 @@ TomorrowOS CMS is **one Node process** that serves:
44
45
  | Required | Forbidden |
45
46
  |----------|-----------|
46
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`) |
47
49
  | **Fluid compute** enabled (needed for WebSockets on Vercel) | Classic short-lived serverless with no WebSocket / no Fluid |
48
50
  | Durable store = **Postgres** (Supabase or Neon, pooled URL) | **SQLite / `data/*.db` as production store** on Vercel (ephemeral filesystem) |
49
51
  | Media = **Cloudinary** or **Vercel Blob** (durable HTTPS URLs) | Relying on local `public/uploads` alone for production fleets |
@@ -52,6 +54,34 @@ TomorrowOS CMS is **one Node process** that serves:
52
54
 
53
55
  **Publish / Production Deploy** must run **`TomorrowOS.listen(...)`** directly. Do **not** put Next.js in front of Production traffic.
54
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
+
55
85
  ### Preview (v0 / Vercel Agent sandbox) — Next.js shell allowed
56
86
 
57
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.
@@ -87,6 +117,7 @@ Ask **only** these questions, in **this exact order**:
87
117
 
88
118
  - ❌ OpenAI / Anthropic / Gemini / any LLM API key (`sk-…`, etc.)
89
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**
90
121
  - ❌ Any secret not listed in Questions 1–3 (DB URL, media credentials, brand)
91
122
 
92
123
  TomorrowOS pairing and Control Panel logic live in `@tomorrowos/sdk`. They do **not** call OpenAI.
@@ -104,9 +135,9 @@ Then ask **Question 1**.
104
135
  ## Hard rules
105
136
 
106
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.
107
- 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.
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.
108
139
  3. **Do not invent** Cloudinary credentials, database URLs, Vercel Blob tokens, brand colours, **or LLM API keys**.
109
- 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`.
140
+ 4. **Postgres env naming:** prefer **`SUPABASE_URL`** when using Supabase; prefer **`DATABASE_URL`** when using Neon. `createTomorrowOSStore` accepts `SUPABASE_URL` then `DATABASE_URL`.
110
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).
111
142
  6. **Prefer `npx @tomorrowos/sdk init`** (or `templates/cms-starter`) as the seed. Do not rebuild pairing / WebSocket / playlist APIs.
112
143
  7. **Never commit secrets.** Use Vercel Environment Variables (+ optional non-secret flags in committed `.env.example`).
@@ -118,6 +149,8 @@ Then ask **Question 1**.
118
149
  13. After Q&A: **configure → install → run/deploy → minimal verify** (Control Panel at `/`). Do not run a long test suite.
119
150
  14. **No inventing CMS login.** A branding URL never means “build auth.” Only add login if the user **explicitly** requests it.
120
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).
121
154
 
122
155
  **Question order:** Q1 → Q2 → Q3 → execution checklist.
123
156
 
@@ -127,11 +160,11 @@ Then ask **Question 1**.
127
160
 
128
161
  ### Production runtime (Publish)
129
162
 
130
- - 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`).
131
164
  - Keep **`@tomorrowos/sdk`** and put **`tsx` in `dependencies`** (not only `devDependencies`) if production runs via `tsx`.
132
165
  - Node **20+**.
133
166
  - **Do not** replace the SDK with a hand-rolled server that drops WebSocket upgrades.
134
- - **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(...)`.
135
168
 
136
169
  ### Expected `package.json` scripts
137
170
 
@@ -143,7 +176,7 @@ Then ask **Question 1**.
143
176
  "dev:tomorrowos": "cross-env PORT=3001 TOMORROWOS_INTERNAL_PORT=3001 tsx watch server.ts",
144
177
  "dev:next": "next dev -p 3000",
145
178
  "start": "tsx server.ts",
146
- "build": "echo \"Production is Node — no Next production build required\""
179
+ "build": "node -e \"process.exit(0)\""
147
180
  }
148
181
  }
149
182
  ```
@@ -151,6 +184,7 @@ Then ask **Question 1**.
151
184
  Adapt script names / process runners as needed. **Invariant:**
152
185
 
153
186
  - **`npm start` / Production** = TomorrowOS Node only.
187
+ - **`build`** = no-op exit 0 (never `next build` for Production).
154
188
  - **Preview / v0** = Next on public port + TomorrowOS on internal port + proxy.
155
189
 
156
190
  ### Minimal dependencies (Production)
@@ -172,7 +206,8 @@ Adapt script names / process runners as needed. **Invariant:**
172
206
  },
173
207
  "scripts": {
174
208
  "dev": "tsx watch server.ts",
175
- "start": "tsx server.ts"
209
+ "start": "tsx server.ts",
210
+ "build": "node -e \"process.exit(0)\""
176
211
  }
177
212
  }
178
213
  ```
@@ -185,6 +220,8 @@ For Preview shell only, add as needed: `next`, `react`, `react-dom`, and a proce
185
220
 
186
221
  ### `server.ts` pattern (Vercel-adapted — Production)
187
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
+
188
225
  ```ts
189
226
  import "dotenv/config";
190
227
  import { readFileSync } from "fs";
@@ -230,12 +267,12 @@ Load env with `dotenv` for local/`vercel dev`. On production Deploy, Vercel inje
230
267
 
231
268
  Detect Preview / v0 (Next already present, or Preview blank while TomorrowOS logs “listening”). Then:
232
269
 
233
- 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.*`.
234
271
  2. TomorrowOS listens on **`127.0.0.1:TOMORROWOS_INTERNAL_PORT`** (default **3001**).
235
272
  3. Add a **catch-all** Next route (or middleware) that proxies HTTP to TomorrowOS:
236
273
 
237
274
  ```ts
238
- // Example: app/api/tomorrowos-proxy/[...path]/route.ts or a rewrite + route handler
275
+ // Example: preview app proxy handler
239
276
  // Forward method, headers, and body to http://127.0.0.1:3001/<path>
240
277
  // Return the upstream status, headers, and body to the browser.
241
278
  ```
@@ -250,24 +287,41 @@ Preferred UX: proxy **`/`** itself (rewrites) so the Preview URL shows the Contr
250
287
  - Making Next the Production CMS or the only server after Publish.
251
288
  - Deleting TomorrowOS `server.ts` because Preview needed Next.
252
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”.
253
292
 
254
- ### `vercel.json` (recommended minimum Production)
293
+ ### `vercel.json` (Productionexplicit Node, not Next)
255
294
 
256
- 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:
257
296
 
258
297
  ```json
259
298
  {
260
- "fluid": true
299
+ "fluid": true,
300
+ "framework": null,
301
+ "installCommand": "npm install",
302
+ "buildCommand": "npm run build",
303
+ "outputDirectory": null
261
304
  }
262
305
  ```
263
306
 
264
- Also:
307
+ Use `pnpm` / `yarn` variants of install/build if that is the project’s package manager.
265
308
 
266
- 1. Project **Fluid compute** enabled (required for WebSockets on Publish).
267
- 2. Framework Preset for **Production:** **Other** (or Node) — **not** Next.js as the live CMS.
268
- 3. Install / Start commands for **Production** must end in running the CMS Node server (e.g. `npm run start` → `tsx server.ts`).
269
- 4. Do **not** enable “Output Directory” static-only hosting for this CMS.
270
- 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.
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:
321
+
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`.
271
325
 
272
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.
273
327
 
@@ -282,6 +336,7 @@ If Vercel’s UI and this file disagree on bundling, **prefer whatever keeps a s
282
336
 
283
337
  - ❌ `.replit`, `.replit-artifact`
284
338
  - ❌ Next.js as the **Production** CMS (App Router replacing TomorrowOS)
339
+ - ❌ Root `api/` catch-all exporting raw `http.Server` to “fix” Publish 404
285
340
  - ❌ Separate “API project” without the Control Panel
286
341
  - ❌ Committing database passwords, Cloudinary secrets, or Blob tokens
287
342
 
@@ -307,52 +362,47 @@ If Vercel’s UI and this file disagree on bundling, **prefer whatever keeps a s
307
362
 
308
363
  ### Step B — If **1 / Supabase**
309
364
 
310
- **Ask exactly:**
365
+ **Ask exactly (connection string only):**
311
366
 
312
- > 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`**.
313
368
  >
314
369
  > In Supabase: **Project Settings → Database → Connection string → Connection pooling** (Session mode).
315
370
  > Preferred shape:
316
371
  > `postgresql://postgres.[PROJECT]:[PASSWORD]@aws-0-[REGION].pooler.supabase.com:6543/postgres`
317
372
  >
318
- > **Do not** use the direct host `db.*.supabase.co:5432` for Vercel — sandbox/serverless often fails with `ENETUNREACH` (commonly on IPv6).
319
- >
320
- > Confirm SSL is required (usually **yes** for Supabase).
373
+ > **Do not** use the direct host `db.*.supabase.co:5432` for Vercel.
321
374
 
322
- **You must then:**
375
+ **You must then (automatic — do NOT ask the user to type these):**
323
376
 
324
- 1. Set Vercel Env Vars (Production + Preview + Development as appropriate):
325
- - `SUPABASE_URL=<pooler string>`
326
- - `TOMORROWOS_STORE=supabase`
327
- - `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)
328
381
  2. Wire `server.ts` as in **Runtime & Vercel deploy rules**.
329
- 3. Optional committed `.env.example` with **placeholders only** (no real password).
382
+ 3. Optional committed `.env.example` with **placeholders only**.
330
383
  4. **Do not** commit the real connection string.
331
- 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`.
332
385
 
333
386
  ### Step C — If **2 / Neon**
334
387
 
335
- **Ask exactly:**
388
+ **Ask exactly (connection string only):**
336
389
 
337
- > 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`**.
338
391
  >
339
- > In Neon: **Dashboard → Connection details → Pooled connection** (enable pooling).
392
+ > In Neon: **Dashboard → Connection details → Pooled connection**.
340
393
  > Typical shape:
341
- > `postgresql://[user]:[password]@[endpoint].pooler.[region].aws.neon.tech/[dbname]?sslmode=require`
342
- >
343
- > **Do not** use the direct (non-pooled) connection string for Vercel serverless unless Neon docs explicitly say otherwise for your plan.
344
- >
345
- > Confirm SSL (`sslmode=require`) is on (usually **yes**).
394
+ > `postgresql://[user]:[password]@[endpoint]-pooler.[region].aws.neon.tech/[dbname]?sslmode=require`
346
395
 
347
- **You must then:**
396
+ **You must then (automatic — do NOT ask the user to type these):**
348
397
 
349
398
  1. Set Vercel Env Vars:
350
- - `DATABASE_URL=<pooled Neon string>` (or `NEON_DATABASE_URL` + read it in `server.ts`)
351
- - `TOMORROWOS_STORE=postgres`
352
- - `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**
353
402
  2. Wire `server.ts` — `createTomorrowOSStore` uses `PostgresStore` for `postgres` / `supabase` drivers.
354
403
  3. **Do not** commit the real connection string.
355
- 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.
356
406
 
357
407
  ### Step D — If **3 / SQLite**
358
408
 
@@ -362,9 +412,9 @@ If Vercel’s UI and this file disagree on bundling, **prefer whatever keeps a s
362
412
 
363
413
  Only proceed after the user explicitly confirms.
364
414
 
365
- **You must then:**
415
+ **You must then (automatic):**
366
416
 
367
- 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).
368
418
  2. Keep `sqlitePath` in `server.ts` for `data/tomorrowos.db`.
369
419
  3. Warn again in the final summary that they should move to Supabase or Neon for real devices.
370
420
 
@@ -391,46 +441,33 @@ Only proceed after the user explicitly confirms.
391
441
  >
392
442
  > Reply with **1**, **2**, or **3** (or “Cloudinary” / “Vercel Blob” / “local”).
393
443
 
394
- ### Step B — If **1 / Cloudinary** (same Question 2 — sequential prompts)
395
-
396
- > **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.
444
+ ### Step B — If **1 / Cloudinary** (same Question 2 — **one Env popup**)
397
445
 
398
- #### B1 Cloud name (ask alone)
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.
399
447
 
400
- **Ask exactly:**
401
-
402
- > Paste your Cloudinary **Cloud name** only (`CLOUDINARY_CLOUD_NAME`).
403
- > Find it at [cloudinary.com/console](https://cloudinary.com/console) → Dashboard.
448
+ **Preferred:** Vercel Env Vars UI (or Agent secrets form) with these fields together:
404
449
 
405
- Save as Vercel Env Var `CLOUDINARY_CLOUD_NAME`. Do **not** ask for key or secret yet.
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`) |
406
456
 
407
- #### B2 API key (ask alone after B1 is saved)
457
+ **If a multi-field popup is unavailable, ask once in a single message** (still not three turns):
408
458
 
409
- **Ask exactly:**
410
-
411
- > Paste your Cloudinary **API Key** only (`CLOUDINARY_API_KEY`).
412
- > Do not paste the API Secret in this step.
413
-
414
- Save as Vercel Env Var `CLOUDINARY_API_KEY`. Do **not** ask for the secret in this message.
415
-
416
- #### B3 — API secret (ask alone — after B2 is saved)
417
-
418
- **Ask exactly:**
419
-
420
- > Paste your Cloudinary **API Secret** only (`CLOUDINARY_API_SECRET`).
421
- > This is the last Cloudinary credential.
422
-
423
- Save as Vercel Env Var `CLOUDINARY_API_SECRET`.
424
-
425
- Optional fourth message (only if useful):
426
-
427
- > 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`
428
465
 
429
466
  **You must then:**
430
467
 
431
- 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.
432
469
  2. **Do not** invent placeholders.
433
- 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.
434
471
  4. **Do not** run a formal Cloudinary upload test unless the user asks.
435
472
 
436
473
  The SDK auto-detects these env vars.
@@ -448,7 +485,7 @@ The SDK auto-detects these env vars.
448
485
 
449
486
  **You must then:**
450
487
 
451
- 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.
452
489
  2. `npm install @vercel/blob` (add to `dependencies`).
453
490
  3. Wire uploads so media returns **absolute HTTPS Blob URLs** stored in `uploaded_assets` (players need stable public URLs).
454
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).
@@ -576,26 +613,26 @@ npx @tomorrowos/sdk@latest init .
576
613
 
577
614
  Then:
578
615
 
579
- 1. Keep TomorrowOS `server.ts` as the **Production** app (**export `listen()` HTTP server**).
580
- 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.
581
- 3. Add/adjust `vercel.json` with **`fluid: true`** (or enable Fluid in the dashboard) for Publish.
582
- 4. Ensure Production start command is **`npm run start` → `tsx server.ts`** (or equivalent Node), **not** `next start`.
583
- 5. Do **not** add `.replit*`.
584
- 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.
585
624
 
586
625
  ### B. Environment Variables (Vercel dashboard + local)
587
626
 
588
- | Name | Required | Notes |
589
- |------|----------|--------|
590
- | `SUPABASE_URL` | If Q1 = Supabase | **Session pooler** `:6543` |
591
- | `DATABASE_URL` | If Q1 = Neon | **Pooled** Neon string |
592
- | `TOMORROWOS_STORE` | Yes | `supabase`, `postgres`, or `sqlite` |
593
- | `DATABASE_SSL` | Yes (typical) | `true` |
594
- | `CLOUDINARY_CLOUD_NAME` | If Q2 = Cloudinary | Collected alone (B1) |
595
- | `CLOUDINARY_API_KEY` | If Q2 = Cloudinary | Collected alone after cloud name (B2) |
596
- | `CLOUDINARY_API_SECRET` | If Q2 = Cloudinary | Collected alone after API key (B3) |
597
- | `BLOB_READ_WRITE_TOKEN` | If Q2 = Vercel Blob | From Vercel Blob store |
598
- | `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`) |
599
636
  | `PORT` | Optional | Vercel injects for Production |
600
637
 
601
638
  Mirror non-secrets in `.env` for local `npm run dev` if helpful; never commit secrets.
@@ -621,12 +658,15 @@ vercel --prod
621
658
  # or git push → Vercel Git integration
622
659
  ```
623
660
 
624
- Before finishing, verify Production settings:
661
+ **Publish acceptance checklist (all must pass):**
625
662
 
626
- 1. Start / entry = **Node `TomorrowOS.listen`** (`npm run start` / exported `server`)
627
- 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`)
628
665
  3. Fluid compute on
629
- 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 `/`.
630
670
 
631
671
  Tell the user:
632
672
 
@@ -639,7 +679,7 @@ Tell the user:
639
679
  **Do:**
640
680
 
641
681
  1. Preview: Control Panel HTML at `/` (via Next proxy if applicable)
642
- 2. Publish: Control Panel HTML at `/` from **Node** TomorrowOS
682
+ 2. Publish: Control Panel HTML at `/` from **Node** TomorrowOS (block on `404: NOT_FOUND`)
643
683
  3. Server status: Media OK when Cloudinary or Blob configured
644
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
645
685
  5. Fluid compute on for Production
@@ -652,6 +692,8 @@ Tell the user:
652
692
 
653
693
  | Symptom | Likely cause | Fix |
654
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 |
655
697
  | Preview blank but logs show TomorrowOS listening | v0 Next intercepts public port | Add Next reverse proxy → internal TomorrowOS port |
656
698
  | Preview works, Publish broken / Next-only | Production still on Next | Switch Production entry to `tsx server.ts` / `export default server` |
657
699
  | `ENETUNREACH` / IPv6 / `:5432` | Direct Postgres URL | Use **pooled** URL (Supabase `:6543` or Neon pooler) |
@@ -660,12 +702,12 @@ Tell the user:
660
702
  | Blob uploads 401 / missing token | Blob not linked | Vercel Storage → Blob + `BLOB_READ_WRITE_TOKEN` |
661
703
  | WebSocket fails on Publish | Fluid off / static-only / Next as Production | Enable Fluid; sole Node `listen()` for Publish |
662
704
  | Devices fail only on Preview | Next proxy cannot upgrade `wss` | Expected — pair on Publish URL |
663
- | Agent mixed Cloudinary key+secret in one prompt | Violated Step B | Re-ask key alone, then secret alone |
664
- | Secrets in git | Mistake | Rotate keys; move to Vercel Env Vars |
665
- | Replit files present | Copied wrong protocol | Delete `.replit*`; use this file |
666
- | Brand colours wrong from URL | SPA / blocked fetch | Manual Option B for colours + logo |
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 |
667
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 |
668
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` |
709
+ | Secrets in git | Mistake | Rotate keys; move to Vercel Env Vars |
710
+ | Replit files present | Copied wrong protocol | Delete `.replit*`; use this file |
669
711
 
670
712
  ---
671
713
 
@@ -674,12 +716,12 @@ Tell the user:
674
716
  | Topic | Replit | Vercel |
675
717
  |-------|--------|--------|
676
718
  | Secrets | Replit Secrets | Vercel Environment Variables |
677
- | 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` |
678
720
  | `hostingTarget` | `"here"` | `"vercel"` |
679
721
  | Database | Supabase (primary) | Supabase **or Neon** (pooled); SQLite demo only |
680
722
  | Media | Cloudinary or Replit Object Storage | Cloudinary **or Vercel Blob** |
681
723
  | Preview | Replit Preview / Autoscale Node | **Next shell + proxy** when v0 hardwires Next |
682
- | 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` |
683
725
  | Brand from URL | Not in Replit protocol | **Option A** — infer **`brand.json` only**; never invent login |
684
726
 
685
727
  ---
@@ -689,18 +731,19 @@ Tell the user:
689
731
  After setup, report:
690
732
 
691
733
  1. **Live URL** (Production Publish)
692
- 2. **Preview note:** Next proxy shell used / not needed
693
- 3. **Env Vars set** (names only never values)
694
- 4. **Store:** Supabase pooler / Neon pooled / SQLite (with warning)
695
- 5. **Media:** Cloudinary, Vercel Blob, or local (with warning)
696
- 6. **Brand:** name + `hostingTarget: vercel` (+ note if derived from website URL)
697
- 7. **Runtime split:** Preview = ; Publish = Node TomorrowOS (confirm **not** Next)
698
- 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
699
742
 
700
743
  Protocol complete when:
701
744
 
702
745
  - **Preview** shows the Control Panel at `/` (via Next proxy if required), **and**
703
- - **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**
704
747
  - Q1–Q3 configuration is saved
705
748
 
706
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.25",
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.25",
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.25",
16
+ "@tomorrowos/sdk": "^0.9.26",
17
17
  "dotenv": "^17.2.3",
18
18
  "tsx": "^4.19.0"
19
19
  },