@tomorrowos/sdk 0.9.25 → 0.9.27

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` (Scheme A: root `server.ts`, Next in `preview/`, `GET /status` must be JSON). 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.6` — 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, GET /status on the production URL must return JSON (not HTML), and GET / 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,57 @@ 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
+
85
+ ### Publish static trap — `public/` served without Node (read this)
86
+
87
+ A common **worse** failure mode than `404: NOT_FOUND`:
88
+
89
+ - `GET /` loads the Control Panel HTML (from static `public/index.html`)
90
+ - `GET /status`, `/devices`, etc. return **HTML** (Vercel 404 page)
91
+ - The panel shows **CMS unreachable** and console:
92
+ `Unexpected token 'T', "The page c"... is not valid JSON`
93
+
94
+ **Root cause:** Vercel deployed `public/` as a **static site** (Output Directory = `public` or Framework=Other default *“public if it exists”*). **No Node process** is running `tsx server.ts`, so API routes never hit `TomorrowOS`.
95
+
96
+ **Fix (Scheme A — see dedicated section below):** force Vercel to run the **root `server.ts` Node entry** with Fluid. `public/` must be served **at runtime** via `staticRoot` in `listen()`, **not** as the build output directory.
97
+
98
+ **Publish gate (mandatory):** after deploy, `GET /status` must return **JSON** starting with `{`, not HTML starting with `<` or `The page`.
99
+
100
+ ```bash
101
+ curl -s https://YOUR-PROJECT.vercel.app/status | head -c 80
102
+ # Expected: {"status":"success",...}
103
+ # Failure: The page could not be found OR <!DOCTYPE html>
104
+ ```
105
+
106
+ **Setup is NOT complete** while `/status` returns HTML or the panel shows the JSON parse error.
107
+
55
108
  ### Preview (v0 / Vercel Agent sandbox) — Next.js shell allowed
56
109
 
57
110
  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 +140,7 @@ Ask **only** these questions, in **this exact order**:
87
140
 
88
141
  - ❌ OpenAI / Anthropic / Gemini / any LLM API key (`sk-…`, etc.)
89
142
  - ❌ “AI pairing”, “AI feature logic”, or inventing auth that needs an LLM key
143
+ - ❌ Asking the user to type `TOMORROWOS_STORE` or `DATABASE_SSL` after they already chose Neon/Supabase — **you set those automatically**
90
144
  - ❌ Any secret not listed in Questions 1–3 (DB URL, media credentials, brand)
91
145
 
92
146
  TomorrowOS pairing and Control Panel logic live in `@tomorrowos/sdk`. They do **not** call OpenAI.
@@ -104,9 +158,9 @@ Then ask **Question 1**.
104
158
  ## Hard rules
105
159
 
106
160
  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.
161
+ 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
162
  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`.
163
+ 4. **Postgres env naming:** prefer **`SUPABASE_URL`** when using Supabase; prefer **`DATABASE_URL`** when using Neon. `createTomorrowOSStore` accepts `SUPABASE_URL` then `DATABASE_URL`.
110
164
  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
165
  6. **Prefer `npx @tomorrowos/sdk init`** (or `templates/cms-starter`) as the seed. Do not rebuild pairing / WebSocket / playlist APIs.
112
166
  7. **Never commit secrets.** Use Vercel Environment Variables (+ optional non-secret flags in committed `.env.example`).
@@ -118,20 +172,148 @@ Then ask **Question 1**.
118
172
  13. After Q&A: **configure → install → run/deploy → minimal verify** (Control Panel at `/`). Do not run a long test suite.
119
173
  14. **No inventing CMS login.** A branding URL never means “build auth.” Only add login if the user **explicitly** requests it.
120
174
  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.
175
+ 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.
176
+ 17. **Publish gate:** do not mark setup complete while Production returns `404: NOT_FOUND` **or** `/status` returns HTML (static `public/` trap).
177
+ 18. **Scheme A is doc-driven:** achieve Production Node Publish by **project layout + `vercel.json` + dashboard settings** during setup — **no SDK code changes** required (see **Scheme A** section).
121
178
 
122
179
  **Question order:** Q1 → Q2 → Q3 → execution checklist.
123
180
 
124
181
  ---
125
182
 
183
+ ## Scheme A — Standard Node Publish (mandatory for Production)
184
+
185
+ > **Goal:** Preview may use Next + proxy; **Publish** runs **only** root `server.ts` + `TomorrowOS.listen` on Vercel Fluid.
186
+ > **Scope:** This section is implemented entirely during setup (move files, write `vercel.json`, patch dashboard). **Do not** change `@tomorrowos/sdk` source.
187
+
188
+ ### Why Scheme A exists
189
+
190
+ | Layer | Preview (v0) | Publish (Production) |
191
+ |-------|----------------|----------------------|
192
+ | Host | Next.js shell + HTTP proxy | **Node `server.ts`** only |
193
+ | `public/` | Proxied from TomorrowOS | Served by **`staticRoot`** inside `listen()` — **not** as Vercel Output Directory |
194
+ | WebSocket | Optional / may fail in Preview | Required on same origin (`wss://`) |
195
+ | Next files | Allowed under `preview/` | **Must not** live at project root |
196
+
197
+ ### A1 — Project layout (Agent must enforce)
198
+
199
+ **At project root (Production):**
200
+
201
+ ```
202
+ server.ts ← REQUIRED name at root (or server.mts)
203
+ vercel.json
204
+ package.json
205
+ brand.json
206
+ public/ ← CMS static UI; NOT the Vercel output directory
207
+ ```
208
+
209
+ **Preview only (when v0 needs Next) — move ALL of these under `preview/`:**
210
+
211
+ ```
212
+ preview/
213
+ next.config.mjs
214
+ app/ ← Next proxy routes
215
+ postcss.config.mjs
216
+ next-env.d.ts
217
+ tsconfig.json ← Next/tsconfig if separate from server
218
+ ```
219
+
220
+ **Remove from project root after move:** `next.config.mjs`, `app/`, root Next `tsconfig` plugin config, `postcss.config.mjs`, `next-env.d.ts`.
221
+
222
+ **Do NOT:**
223
+
224
+ - ❌ Leave `next.config.mjs` + `app/` at root while claiming Production is Node-only
225
+ - ❌ Create root `api/index.ts` exporting `http.Server` + `routes` catch-all
226
+ - ❌ Set Output Directory to `public` or `.`
227
+
228
+ **Update `package.json` scripts when Preview uses `preview/`:**
229
+
230
+ ```json
231
+ {
232
+ "scripts": {
233
+ "dev": "tsx watch server.ts",
234
+ "dev:preview": "concurrently -k \"npm:dev:tomorrowos\" \"npm:dev:next\"",
235
+ "dev:tomorrowos": "cross-env TOMORROWOS_INTERNAL_PORT=3001 tsx watch server.ts",
236
+ "dev:next": "cd preview && next dev -p 3000",
237
+ "start": "tsx server.ts",
238
+ "build": "node -e \"process.exit(0)\""
239
+ }
240
+ }
241
+ ```
242
+
243
+ ### A2 — `server.ts` entry (Vercel zero-config Node detection)
244
+
245
+ Vercel detects a **root** `server.ts` / `server.mts` and routes traffic to it when:
246
+
247
+ 1. The file lives at **project root** (not only under `api/`)
248
+ 2. **`listen()` runs at module load** (top-level, not inside an unexported function)
249
+ 3. The file **`export default server`** (the `http.Server` from `tomorrowos.listen`)
250
+
251
+ Use the pattern in **Runtime & Vercel deploy rules** below. On Publish, **do not** set `TOMORROWOS_INTERNAL_PORT` — let Vercel inject `PORT`.
252
+
253
+ ### A3 — `vercel.json` (Production)
254
+
255
+ ```json
256
+ {
257
+ "fluid": true,
258
+ "framework": null,
259
+ "installCommand": "npm install",
260
+ "buildCommand": "npm run build",
261
+ "startCommand": "npm run start",
262
+ "outputDirectory": null
263
+ }
264
+ ```
265
+
266
+ Use `pnpm` / `yarn` equivalents if the project uses them.
267
+
268
+ **Critical:**
269
+
270
+ - `"outputDirectory": null` — **never** `"public"`, `"."`, or `"public if exists"`
271
+ - `"startCommand"` must be explicit when the dashboard keeps defaulting to static
272
+ - `"buildCommand"` must be the no-op `npm run build` — **never** `next build` for Production
273
+
274
+ ### A4 — Vercel Project Settings (dashboard or API — mandatory)
275
+
276
+ `vercel.json` alone is **not enough** if the dashboard still shows Framework = **Next.js** or Output = `public`.
277
+
278
+ | Setting | Required value |
279
+ |---------|----------------|
280
+ | Framework Preset | **Other** / null — **not Next.js** |
281
+ | Install Command | `npm install` (or pnpm/yarn) |
282
+ | Build Command | `npm run build` (no-op exit 0) |
283
+ | **Output Directory** | **empty / null** — **NOT `public`** |
284
+ | **Start Command** | `npm run start` → `tsx server.ts` |
285
+ | Fluid compute | **On** |
286
+
287
+ If Framework Preset still shows **Next.js** after `vercel.json`: **patch via Vercel API / CLI / dashboard**, then redeploy.
288
+
289
+ ### A5 — Publish acceptance (all must pass before “setup complete”)
290
+
291
+ 1. Deploy logs show Node starting / `[TomorrowOS] listening on ...` (not only “Uploading static files”)
292
+ 2. `curl -s https://PROD/status` → JSON (`{"status":...}`)
293
+ 3. `curl -s https://PROD/` → Control Panel HTML
294
+ 4. Control Panel **does not** show `Unexpected token 'T'` / CMS unreachable
295
+ 5. Framework Preset = Other; Fluid on
296
+
297
+ If (2) fails but (3) passes → **static `public/` trap**. Re-apply A3 + A4; confirm `startCommand`; redeploy. **Do not** add `api/` catch-all.
298
+
299
+ ### A6 — What Scheme A does **not** require
300
+
301
+ - ❌ No changes to `@tomorrowos/sdk` package code
302
+ - ❌ No `(req, res)` handler refactor
303
+ - ❌ No `next build` / `next start` on Production
304
+ - ❌ No root `api/` directory for the CMS server
305
+
306
+ ---
307
+
126
308
  ## Runtime & Vercel deploy rules (mandatory)
127
309
 
128
310
  ### Production runtime (Publish)
129
311
 
130
- - Serve with **`tsx server.ts`** (or compiled Node that still calls `TomorrowOS.listen` with `staticRoot`).
312
+ - Serve with **`tsx server.ts`** (or `server.mts` / compiled Node that still calls `TomorrowOS.listen` with `staticRoot`).
131
313
  - Keep **`@tomorrowos/sdk`** and put **`tsx` in `dependencies`** (not only `devDependencies`) if production runs via `tsx`.
132
314
  - Node **20+**.
133
315
  - **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(...)`.
316
+ - **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
317
 
136
318
  ### Expected `package.json` scripts
137
319
 
@@ -143,7 +325,7 @@ Then ask **Question 1**.
143
325
  "dev:tomorrowos": "cross-env PORT=3001 TOMORROWOS_INTERNAL_PORT=3001 tsx watch server.ts",
144
326
  "dev:next": "next dev -p 3000",
145
327
  "start": "tsx server.ts",
146
- "build": "echo \"Production is Node — no Next production build required\""
328
+ "build": "node -e \"process.exit(0)\""
147
329
  }
148
330
  }
149
331
  ```
@@ -151,6 +333,7 @@ Then ask **Question 1**.
151
333
  Adapt script names / process runners as needed. **Invariant:**
152
334
 
153
335
  - **`npm start` / Production** = TomorrowOS Node only.
336
+ - **`build`** = no-op exit 0 (never `next build` for Production).
154
337
  - **Preview / v0** = Next on public port + TomorrowOS on internal port + proxy.
155
338
 
156
339
  ### Minimal dependencies (Production)
@@ -172,7 +355,8 @@ Adapt script names / process runners as needed. **Invariant:**
172
355
  },
173
356
  "scripts": {
174
357
  "dev": "tsx watch server.ts",
175
- "start": "tsx server.ts"
358
+ "start": "tsx server.ts",
359
+ "build": "node -e \"process.exit(0)\""
176
360
  }
177
361
  }
178
362
  ```
@@ -185,6 +369,8 @@ For Preview shell only, add as needed: `next`, `react`, `react-dom`, and a proce
185
369
 
186
370
  ### `server.ts` pattern (Vercel-adapted — Production)
187
371
 
372
+ Keep the server at **project root** (e.g. `server.ts` / `server.mts`). Do **not** move Production into `api/` solely to satisfy Vercel routing myths.
373
+
188
374
  ```ts
189
375
  import "dotenv/config";
190
376
  import { readFileSync } from "fs";
@@ -230,12 +416,12 @@ Load env with `dotenv` for local/`vercel dev`. On production Deploy, Vercel inje
230
416
 
231
417
  Detect Preview / v0 (Next already present, or Preview blank while TomorrowOS logs “listening”). Then:
232
418
 
233
- 1. Keep a **minimal** Next app (App Router or Pages whichever the sandbox expects).
419
+ 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
420
  2. TomorrowOS listens on **`127.0.0.1:TOMORROWOS_INTERNAL_PORT`** (default **3001**).
235
421
  3. Add a **catch-all** Next route (or middleware) that proxies HTTP to TomorrowOS:
236
422
 
237
423
  ```ts
238
- // Example: app/api/tomorrowos-proxy/[...path]/route.ts or a rewrite + route handler
424
+ // Example: preview app proxy handler
239
425
  // Forward method, headers, and body to http://127.0.0.1:3001/<path>
240
426
  // Return the upstream status, headers, and body to the browser.
241
427
  ```
@@ -250,24 +436,43 @@ Preferred UX: proxy **`/`** itself (rewrites) so the Preview URL shows the Contr
250
436
  - Making Next the Production CMS or the only server after Publish.
251
437
  - Deleting TomorrowOS `server.ts` because Preview needed Next.
252
438
  - Claiming setup complete if Production entrypoint is still `next start`.
439
+ - Claiming setup complete if Production `GET /` is `404: NOT_FOUND`.
440
+ - Creating root `api/index.*` + `routes` catch-all that exports a raw `http.Server` “to fix 404”.
253
441
 
254
- ### `vercel.json` (recommended minimum Production)
442
+ ### `vercel.json` (Productionexplicit Node, not Next)
255
443
 
256
- Adapt to the project’s current Vercel Node-server / Fluid conventions. At minimum ensure:
444
+ Write `vercel.json` so Production cannot be mistaken for Next. Minimum:
257
445
 
258
446
  ```json
259
447
  {
260
- "fluid": true
448
+ "fluid": true,
449
+ "framework": null,
450
+ "installCommand": "npm install",
451
+ "buildCommand": "npm run build",
452
+ "startCommand": "npm run start",
453
+ "outputDirectory": null
261
454
  }
262
455
  ```
263
456
 
264
- Also:
457
+ Use `pnpm` / `yarn` variants of install/build/start if that is the project’s package manager.
265
458
 
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.
459
+ **Also force Project Settings (dashboard or Vercel API) this is mandatory (see Scheme A4):**
460
+
461
+ | Setting | Required value |
462
+ |---------|----------------|
463
+ | Framework Preset | **Other** / null **not Next.js** |
464
+ | Install Command | `npm install` (or pnpm/yarn) |
465
+ | Build Command | `npm run build` (no-op exit 0) |
466
+ | Output Directory | **empty / null** — **NOT `public`** |
467
+ | Start Command | `npm run start` → `tsx server.ts` (or `.mts`) — **explicit in `vercel.json` too** |
468
+ | Fluid compute | **On** |
469
+
470
+ If `next` / `next.config.*` exist for Preview and the dashboard still auto-selects Next.js:
471
+
472
+ 1. Patch Framework Preset to **Other** via API/CLI/dashboard.
473
+ 2. Redeploy.
474
+ 3. Hit Production `GET /status` — must be **JSON**, not HTML.
475
+ 4. Hit Production `GET /` — must be Control Panel HTML, not `404: NOT_FOUND`.
271
476
 
272
477
  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
478
 
@@ -282,6 +487,7 @@ If Vercel’s UI and this file disagree on bundling, **prefer whatever keeps a s
282
487
 
283
488
  - ❌ `.replit`, `.replit-artifact`
284
489
  - ❌ Next.js as the **Production** CMS (App Router replacing TomorrowOS)
490
+ - ❌ Root `api/` catch-all exporting raw `http.Server` to “fix” Publish 404
285
491
  - ❌ Separate “API project” without the Control Panel
286
492
  - ❌ Committing database passwords, Cloudinary secrets, or Blob tokens
287
493
 
@@ -307,52 +513,47 @@ If Vercel’s UI and this file disagree on bundling, **prefer whatever keeps a s
307
513
 
308
514
  ### Step B — If **1 / Supabase**
309
515
 
310
- **Ask exactly:**
516
+ **Ask exactly (connection string only):**
311
517
 
312
- > Paste your Supabase Postgres connection string. I will store it as the Vercel Environment Variable **`SUPABASE_URL`**.
518
+ > Paste your Supabase Postgres connection string. I will store it as **`SUPABASE_URL`**.
313
519
  >
314
520
  > In Supabase: **Project Settings → Database → Connection string → Connection pooling** (Session mode).
315
521
  > Preferred shape:
316
522
  > `postgresql://postgres.[PROJECT]:[PASSWORD]@aws-0-[REGION].pooler.supabase.com:6543/postgres`
317
523
  >
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).
524
+ > **Do not** use the direct host `db.*.supabase.co:5432` for Vercel.
321
525
 
322
- **You must then:**
526
+ **You must then (automatic — do NOT ask the user to type these):**
323
527
 
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)
528
+ 1. Set Vercel Env Vars:
529
+ - `SUPABASE_URL=<user pooler string>`
530
+ - `TOMORROWOS_STORE=supabase` ← **agent sets this**
531
+ - `DATABASE_SSL=true` ← **agent sets this** (unless user already said SSL is off)
328
532
  2. Wire `server.ts` as in **Runtime & Vercel deploy rules**.
329
- 3. Optional committed `.env.example` with **placeholders only** (no real password).
533
+ 3. Optional committed `.env.example` with **placeholders only**.
330
534
  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.
535
+ 5. **Do not** open a second form asking for `TOMORROWOS_STORE` or `DATABASE_SSL`.
332
536
 
333
537
  ### Step C — If **2 / Neon**
334
538
 
335
- **Ask exactly:**
539
+ **Ask exactly (connection string only):**
336
540
 
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`).
541
+ > Paste your **Neon pooled** Postgres connection string. I will store it as **`DATABASE_URL`**.
338
542
  >
339
- > In Neon: **Dashboard → Connection details → Pooled connection** (enable pooling).
543
+ > In Neon: **Dashboard → Connection details → Pooled connection**.
340
544
  > 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**).
545
+ > `postgresql://[user]:[password]@[endpoint]-pooler.[region].aws.neon.tech/[dbname]?sslmode=require`
346
546
 
347
- **You must then:**
547
+ **You must then (automatic — do NOT ask the user to type these):**
348
548
 
349
549
  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)
550
+ - `DATABASE_URL=<user pooled string>`
551
+ - `TOMORROWOS_STORE=postgres` ← **agent sets this**
552
+ - `DATABASE_SSL=true` **agent sets this**
353
553
  2. Wire `server.ts` — `createTomorrowOSStore` uses `PostgresStore` for `postgres` / `supabase` drivers.
354
554
  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.
555
+ 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.
556
+ 5. If you see `ENETUNREACH` on `:5432`, switch to Neon’s **pooled** URL.
356
557
 
357
558
  ### Step D — If **3 / SQLite**
358
559
 
@@ -362,9 +563,9 @@ If Vercel’s UI and this file disagree on bundling, **prefer whatever keeps a s
362
563
 
363
564
  Only proceed after the user explicitly confirms.
364
565
 
365
- **You must then:**
566
+ **You must then (automatic):**
366
567
 
367
- 1. Set `TOMORROWOS_STORE=sqlite` (or omit `DATABASE_URL` / `SUPABASE_URL` so the factory defaults to SQLite).
568
+ 1. Set `TOMORROWOS_STORE=sqlite` yourself (do not ask the user to type it).
368
569
  2. Keep `sqlitePath` in `server.ts` for `data/tomorrowos.db`.
369
570
  3. Warn again in the final summary that they should move to Supabase or Neon for real devices.
370
571
 
@@ -391,46 +592,33 @@ Only proceed after the user explicitly confirms.
391
592
  >
392
593
  > Reply with **1**, **2**, or **3** (or “Cloudinary” / “Vercel Blob” / “local”).
393
594
 
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.
595
+ ### Step B — If **1 / Cloudinary** (same Question 2 — **one Env popup**)
397
596
 
398
- #### B1 Cloud name (ask alone)
597
+ > **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
598
 
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.
404
-
405
- Save as Vercel Env Var `CLOUDINARY_CLOUD_NAME`. Do **not** ask for key or secret yet.
406
-
407
- #### B2 — API key (ask alone — after B1 is saved)
408
-
409
- **Ask exactly:**
599
+ **Preferred:** Vercel Env Vars UI (or Agent secrets form) with these fields together:
410
600
 
411
- > Paste your Cloudinary **API Key** only (`CLOUDINARY_API_KEY`).
412
- > Do not paste the API Secret in this step.
601
+ | Field | Env name | Required |
602
+ |-------|----------|----------|
603
+ | Cloud name | `CLOUDINARY_CLOUD_NAME` | Yes |
604
+ | API Key | `CLOUDINARY_API_KEY` | Yes |
605
+ | API Secret | `CLOUDINARY_API_SECRET` | Yes |
606
+ | Folder (optional) | `CLOUDINARY_FOLDER` | No (e.g. `tomorrowos`) |
413
607
 
414
- Save as Vercel Env Var `CLOUDINARY_API_KEY`. Do **not** ask for the secret in this message.
608
+ **If a multi-field popup is unavailable, ask once in a single message** (still not three turns):
415
609
 
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.
610
+ > Paste Cloudinary credentials from [cloudinary.com/console](https://cloudinary.com/console) (I will store them as Vercel Env Vars):
611
+ >
612
+ > 1. `CLOUDINARY_CLOUD_NAME`
613
+ > 2. `CLOUDINARY_API_KEY`
614
+ > 3. `CLOUDINARY_API_SECRET`
615
+ > 4. optional `CLOUDINARY_FOLDER`
428
616
 
429
617
  **You must then:**
430
618
 
431
- 1. Confirm all three required Env Vars exist: `CLOUDINARY_CLOUD_NAME`, `CLOUDINARY_API_KEY`, `CLOUDINARY_API_SECRET`.
619
+ 1. Save all three required Env Vars for Production + Preview + Development as appropriate.
432
620
  2. **Do not** invent placeholders.
433
- 3. **Do not** proceed to Question 3 until all three exist.
621
+ 3. **Do not** proceed to Question 3 until all three required vars exist.
434
622
  4. **Do not** run a formal Cloudinary upload test unless the user asks.
435
623
 
436
624
  The SDK auto-detects these env vars.
@@ -448,7 +636,7 @@ The SDK auto-detects these env vars.
448
636
 
449
637
  **You must then:**
450
638
 
451
- 1. Set Vercel Env Var: `BLOB_READ_WRITE_TOKEN=<token>` (if not auto-injected).
639
+ 1. Set Vercel Env Var: `BLOB_READ_WRITE_TOKEN=<token>` (if not auto-injected). Prefer the Env popup when available.
452
640
  2. `npm install @vercel/blob` (add to `dependencies`).
453
641
  3. Wire uploads so media returns **absolute HTTPS Blob URLs** stored in `uploaded_assets` (players need stable public URLs).
454
642
  - 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).
@@ -574,28 +762,27 @@ npx @tomorrowos/sdk@latest init .
574
762
  # npx @tomorrowos/sdk@latest init . --force
575
763
  ```
576
764
 
577
- Then:
765
+ Then (**Scheme A — mandatory for Publish**):
578
766
 
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.
767
+ 1. Keep **`server.ts`** at **project root** with top-level `listen()` + `export default server`.
768
+ 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.
769
+ 3. Write `vercel.json` per **Scheme A3** (`fluid`, `framework: null`, `startCommand`, `outputDirectory: null`).
770
+ 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.
771
+ 5. Do **not** add root `api/` catch-all for raw `http.Server`.
772
+ 6. Do **not** add `.replit*`.
773
+ 7. If Q2 = Vercel Blob: install `@vercel/blob` and wire the upload bridge.
585
774
 
586
775
  ### B. Environment Variables (Vercel dashboard + local)
587
776
 
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` |
777
+ | Name | Required | Who fills it |
778
+ |------|----------|--------------|
779
+ | `SUPABASE_URL` | If Q1 = Supabase | User pastes URL |
780
+ | `DATABASE_URL` | If Q1 = Neon | User pastes URL |
781
+ | `TOMORROWOS_STORE` | Yes | **Agent auto-sets** (`supabase` / `postgres` / `sqlite`) |
782
+ | `DATABASE_SSL` | Yes for Postgres | **Agent auto-sets** `true` |
783
+ | `CLOUDINARY_*` | If Q2 = Cloudinary | User via **one Env popup** (all fields) |
784
+ | `BLOB_READ_WRITE_TOKEN` | If Q2 = Vercel Blob | User / Blob link |
785
+ | `TOMORROWOS_INTERNAL_PORT` | Preview only | Agent (e.g. `3001`) |
599
786
  | `PORT` | Optional | Vercel injects for Production |
600
787
 
601
788
  Mirror non-secrets in `.env` for local `npm run dev` if helpful; never commit secrets.
@@ -621,12 +808,17 @@ vercel --prod
621
808
  # or git push → Vercel Git integration
622
809
  ```
623
810
 
624
- Before finishing, verify Production settings:
811
+ **Publish acceptance checklist (all must pass — Scheme A5):**
812
+
813
+ 1. Framework Preset = **Other** / null (dashboard confirms — not Next.js)
814
+ 2. Output Directory = **empty** (not `public`)
815
+ 3. Start Command = Node `TomorrowOS.listen` (`npm run start` / `tsx server.ts`)
816
+ 4. Fluid compute on
817
+ 5. Env Vars present for Production
818
+ 6. Production `GET /status` returns **JSON** — **not** HTML (`Unexpected token 'T'` means this failed)
819
+ 7. Production `GET /` returns Control Panel HTML — **not** `404: NOT_FOUND`
625
820
 
626
- 1. Start / entry = **Node `TomorrowOS.listen`** (`npm run start` / exported `server`)
627
- 2. **Not** `next start` as Production
628
- 3. Fluid compute on
629
- 4. Env Vars present for Production environment
821
+ If step 6 fails but step 7 passes: **static `public/` trap** — Vercel is not running Node. Re-apply Scheme A3–A4; do **not** invent `api/` + `routes`.
630
822
 
631
823
  Tell the user:
632
824
 
@@ -638,11 +830,12 @@ Tell the user:
638
830
 
639
831
  **Do:**
640
832
 
641
- 1. Preview: Control Panel HTML at `/` (via Next proxy if applicable)
642
- 2. Publish: Control Panel HTML at `/` from **Node** TomorrowOS
643
- 3. Server status: Media OK when Cloudinary or Blob configured
644
- 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
- 5. Fluid compute on for Production
833
+ 1. Preview: Control Panel HTML at `/` (via Next proxy in `preview/` if applicable)
834
+ 2. Publish: `GET /status` JSON; `GET /` → Control Panel from **Node** TomorrowOS
835
+ 3. Publish: panel does **not** show `Unexpected token 'T'` / CMS unreachable
836
+ 4. Server status: Media OK when Cloudinary or Blob configured
837
+ 5. 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
838
+ 6. Fluid compute on for Production
646
839
 
647
840
  **Do not block** on long WebSocket/device pairing tests unless the user asks. Prefer pairing against **Publish**, not Preview.
648
841
 
@@ -652,6 +845,10 @@ Tell the user:
652
845
 
653
846
  | Symptom | Likely cause | Fix |
654
847
  |---------|--------------|-----|
848
+ | Publish `404: NOT_FOUND` | Framework Preset still **Next.js** / no Node start | Scheme A: Framework = **Other**, Start = `npm run start`, Output empty, Fluid on; redeploy |
849
+ | Panel loads but **CMS unreachable** / `Unexpected token 'T', "The page c"...` | **`public/` deployed as static site** — no Node running | Scheme A: `outputDirectory: null`, explicit `startCommand`, move Next to `preview/`; verify `GET /status` returns JSON |
850
+ | `vercel inspect` shows Output = `public if it exists` | Framework=Other static default | Force Output Directory **empty** in dashboard + `outputDirectory: null`; ensure root `server.ts` + `startCommand` |
851
+ | 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 |
655
852
  | Preview blank but logs show TomorrowOS listening | v0 Next intercepts public port | Add Next reverse proxy → internal TomorrowOS port |
656
853
  | Preview works, Publish broken / Next-only | Production still on Next | Switch Production entry to `tsx server.ts` / `export default server` |
657
854
  | `ENETUNREACH` / IPv6 / `:5432` | Direct Postgres URL | Use **pooled** URL (Supabase `:6543` or Neon pooler) |
@@ -660,12 +857,12 @@ Tell the user:
660
857
  | Blob uploads 401 / missing token | Blob not linked | Vercel Storage → Blob + `BLOB_READ_WRITE_TOKEN` |
661
858
  | WebSocket fails on Publish | Fluid off / static-only / Next as Production | Enable Fluid; sole Node `listen()` for Publish |
662
859
  | 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 |
860
+ | Agent asked user to fill `TOMORROWOS_STORE` / `DATABASE_SSL` | Off-protocol | Agent must auto-set those after Neon/Supabase choice |
861
+ | Agent asked Cloudinary key/secret in three chat turns | Off-protocol | Use **one Env popup** with all Cloudinary fields |
667
862
  | 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
863
  | 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` |
864
+ | Secrets in git | Mistake | Rotate keys; move to Vercel Env Vars |
865
+ | Replit files present | Copied wrong protocol | Delete `.replit*`; use this file |
669
866
 
670
867
  ---
671
868
 
@@ -674,12 +871,12 @@ Tell the user:
674
871
  | Topic | Replit | Vercel |
675
872
  |-------|--------|--------|
676
873
  | Secrets | Replit Secrets | Vercel Environment Variables |
677
- | Deploy config | `.replit`, artifact `kind=web` | `vercel.json` + Fluid; **no** `.replit` |
874
+ | Deploy config | `.replit`, artifact `kind=web` | `vercel.json` + Fluid; **Framework = Other**; **no** `.replit` |
678
875
  | `hostingTarget` | `"here"` | `"vercel"` |
679
876
  | Database | Supabase (primary) | Supabase **or Neon** (pooled); SQLite demo only |
680
877
  | Media | Cloudinary or Replit Object Storage | Cloudinary **or Vercel Blob** |
681
878
  | Preview | Replit Preview / Autoscale Node | **Next shell + proxy** when v0 hardwires Next |
682
- | Publish | Long-lived Node | **Node `TomorrowOS.listen` only** (not Next) |
879
+ | Publish | Long-lived Node | **Node `TomorrowOS.listen` only** (not Next); gate on no `404 NOT_FOUND` |
683
880
  | Brand from URL | Not in Replit protocol | **Option A** — infer **`brand.json` only**; never invent login |
684
881
 
685
882
  ---
@@ -689,18 +886,19 @@ Tell the user:
689
886
  After setup, report:
690
887
 
691
888
  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
889
+ 2. **Publish health:** Framework = Other; Output ≠ `public`; `GET /status` JSON; `GET /` OK
890
+ 3. **Preview note:** Next proxy shell used / not needed
891
+ 4. **Env Vars set** (names only never values); note which were auto-set (`TOMORROWOS_STORE`, `DATABASE_SSL`)
892
+ 5. **Store:** Supabase pooler / Neon pooled / SQLite (with warning)
893
+ 6. **Media:** Cloudinary, Vercel Blob, or local (with warning)
894
+ 7. **Brand:** name + `hostingTarget: vercel` (+ note if derived from website URL)
895
+ 8. **Runtime split:** Preview = ; Publish = Node TomorrowOS (confirm **not** Next)
896
+ 9. **Open action:** if Database still ERROR, paste the pooler instruction again
699
897
 
700
898
  Protocol complete when:
701
899
 
702
900
  - **Preview** shows the Control Panel at `/` (via Next proxy if required), **and**
703
- - **Publish** is configured for pure Node `TomorrowOS.listen` with Fluid, **and**
901
+ - **Publish** returns Control Panel at `/` **and** `/status` JSON from pure Node `TomorrowOS.listen` with Fluid (not static `public/`, not `404: NOT_FOUND`, not `Unexpected token 'T'`), **and**
704
902
  - Q1–Q3 configuration is saved
705
903
 
706
904
  — 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.27",
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.27",
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.27",
17
17
  "dotenv": "^17.2.3",
18
18
  "tsx": "^4.19.0"
19
19
  },