@tomorrowos/sdk 0.9.67 → 0.9.69

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.
@@ -1,6 +1,6 @@
1
1
  # Vercel / v0 - Verbatim question bank
2
2
 
3
- > **Protocol:** `vercel-questions/1.5` (pairs with `VERCEL_SETUP.md` 1.13+ / `@tomorrowos/sdk` 0.9.67+)
3
+ > **Protocol:** `vercel-questions/1.5` (pairs with `VERCEL_SETUP.md` 1.14+ / `@tomorrowos/sdk` 0.9.69+)
4
4
  > **For Agents:** When asking the user a setup question, **copy-paste the block below for that step EXACTLY**.
5
5
  > Do **not** paraphrase, reorder, rename options, or invent alternatives (no S3, no Supabase Storage, no "Blob recommended", no "disable media").
6
6
  > After each answer, configure per `VERCEL_SETUP.md`, then paste the **next** block.
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`).
7
7
  >
8
- > Protocol id: `vercel-setup/1.13` - pairs with `@tomorrowos/sdk` **0.9.67+** (`templates/cms-starter-v0`, Fluid Functions WebSockets).
8
+ > Protocol id: `vercel-setup/1.14` - pairs with `@tomorrowos/sdk` **0.9.69+** (`templates/cms-starter-v0`, Fluid Functions WebSockets).
9
9
  >
10
10
  > **Questions source of truth:** `VERCEL_QUESTIONS.md` - Agents must **open that file and paste each Q block verbatim**. Do not paraphrase.
11
11
  > **Q1/Q2 = choice questions. Q3 = form only:** open **all seven optional inputs at once**, then **HARD STOP and WAIT** for submit or "skip". Never auto-skip because fields are optional. Never ask a branding choice first. Never show only Name + Primary colour. Logo = **Upload** above **Logo URL**.
@@ -465,7 +465,7 @@ Adapt script names / process runners as needed. **Invariant:**
465
465
 
466
466
  Use the latest published `@tomorrowos/sdk` when scaffolding (do not invent versions).
467
467
 
468
- Add `@vercel/blob` only when the user chooses **Vercel Blob** in Question 2.
468
+ Add nothing extra for Blob when using `@tomorrowos/sdk@0.9.69+` (`@vercel/blob` is bundled). Only ensure `BLOB_READ_WRITE_TOKEN` + `TOMORROWOS_MEDIA=vercel-blob` when the user chooses **Vercel Blob** in Question 2.
469
469
 
470
470
  For Preview shell only, add as needed: `next`, `react`, `react-dom`, and a process runner (`concurrently` or equivalent). These must **not** become the Production CMS.
471
471
 
@@ -721,13 +721,13 @@ If you catch yourself about to offer Blob-as-recommended, Supabase Storage, loca
721
721
 
722
722
  The SDK auto-detects these env vars.
723
723
 
724
- ### Step C ??If **2 / Vercel Blob** (same Question 2)
724
+ ### Step C - If **2 / Vercel Blob** (same Question 2)
725
725
 
726
726
  **Ask exactly:**
727
727
 
728
728
  > I will enable **Vercel Blob** for media uploads.
729
729
  >
730
- > 1. In the Vercel project: **Storage ??Create ??Blob** (or link an existing Blob store to this project).
730
+ > 1. In the Vercel project: **Storage ? Create ? Blob** (or link an existing Blob store to this project).
731
731
  > 2. Confirm **`BLOB_READ_WRITE_TOKEN`** is available (Vercel usually injects it when Blob is linked).
732
732
  >
733
733
  > Paste the token only if it is not already set in your project Env Vars. Do you already have Blob linked on this Vercel project? (yes / no)
@@ -735,21 +735,13 @@ The SDK auto-detects these env vars.
735
735
  **You must then:**
736
736
 
737
737
  1. Set Vercel Env Var: `BLOB_READ_WRITE_TOKEN=<token>` (if not auto-injected). Prefer the Env popup when available.
738
- 2. `npm install @vercel/blob` (add to `dependencies`).
739
- 3. Wire uploads so media returns **absolute HTTPS Blob URLs** stored in `uploaded_assets` (players need stable public URLs).
740
- - 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).
741
- - Minimum pattern:
742
-
743
- ```ts
744
- import { put } from "@vercel/blob";
745
-
746
- // On upload: const blob = await put(filename, body, { access: "public", token: process.env.BLOB_READ_WRITE_TOKEN });
747
- // Store blob.url in uploaded_assets ??same shape as Cloudinary secure URLs.
748
- ```
749
-
750
- 4. Warn: until the project?s Blob bridge is wired, Control Panel **Media** status may show local/ephemeral ??verify uploads return `https://*.blob.vercel-storage.com/...` before calling setup complete.
751
- 5. Do **not** proceed to Question 3 until `BLOB_READ_WRITE_TOKEN` exists (or Blob is linked and token is confirmed in the Vercel dashboard).
738
+ 2. Set Vercel Env Var yourself (agent-owned): `TOMORROWOS_MEDIA=vercel-blob`. **Do not** ask the user to type this.
739
+ 3. Ensure `@tomorrowos/sdk@0.9.69+` is installed (`@vercel/blob` is a transitive dependency ? no project-level Blob bridge / custom `put()` middleware).
740
+ 4. The SDK **natively** uploads via Vercel Blob when the token is present (same HTTP routes as local: `/media/upload` and chunked complete). Returned asset URLs are absolute `https://*.public.blob.vercel-storage.com/...`.
741
+ 5. Confirm Control Panel **Media Server** shows provider **Blob** (not Local). Do **not** invent status copy about "Blob bridge" or paste the URL pattern as a status detail.
742
+ 6. Do **not** proceed to Question 3 until `BLOB_READ_WRITE_TOKEN` exists (or Blob is linked and token is confirmed in the Vercel dashboard).
752
743
 
744
+ **Forbidden for Q2=Blob:** scaffolding a custom "Blob bridge", writing status text like "uploads persist to durable https://*.public.blob.vercel-storage.com/ URLs via the Blob bridge", or leaving uploads on local `cms-panel/uploads`.
753
745
  ---
754
746
 
755
747
  ## Question 3 - Brand form (`brand.json` only; **not a choice question**; **must wait**)
@@ -874,7 +866,7 @@ Then (**Scheme A ??mandatory for Publish**):
874
866
  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.
875
867
  5. Do **not** add root `api/` catch-all for raw `http.Server`.
876
868
  6. Do **not** add `.replit*`.
877
- 7. If Q2 = Vercel Blob: install `@vercel/blob` and wire the upload bridge.
869
+ 7. If Q2 = Vercel Blob: set `BLOB_READ_WRITE_TOKEN` + `TOMORROWOS_MEDIA=vercel-blob` (SDK-native; no custom bridge).
878
870
 
879
871
  ### B. Environment Variables (Vercel dashboard + local)
880
872
 
@@ -886,6 +878,7 @@ Then (**Scheme A ??mandatory for Publish**):
886
878
  | `DATABASE_SSL` | Yes for Postgres | **Agent auto-sets only** ??never a blank field for the user (`true`) |
887
879
  | `CLOUDINARY_*` | If Q2 = Cloudinary | User via **one Env popup** (all fields) |
888
880
  | `BLOB_READ_WRITE_TOKEN` | If Q2 = Vercel Blob | User / Blob link |
881
+ | `TOMORROWOS_MEDIA` | If Q2 = Vercel Blob | **Agent auto-sets** `vercel-blob` |
889
882
  | `TOMORROWOS_INTERNAL_PORT` | Preview only | Agent (e.g. `3001`) |
890
883
  | `PORT` | Optional | Vercel injects for Production |
891
884
 
@@ -1020,7 +1013,9 @@ Protocol complete when:
1020
1013
 
1021
1014
  ## Protocol version
1022
1015
 
1023
- `vercel-setup/1.13` - pairs with `@tomorrowos/sdk` 0.9.67+ and `VERCEL_QUESTIONS.md` 1.5.
1016
+ `vercel-setup/1.14` - pairs with `@tomorrowos/sdk` 0.9.69+ and `VERCEL_QUESTIONS.md` 1.5.
1017
+
1018
+ **Changelog 1.14:** Vercel Blob is **SDK-native** (`BLOB_READ_WRITE_TOKEN` + `TOMORROWOS_MEDIA=vercel-blob`). No project Blob bridge. Status providers use display names (Neon, Supabase, Blob, Cloudinary, Replit Object Storage). Forbidden marketing status copy about blob URL patterns.
1024
1019
 
1025
1020
  **Changelog 1.13:** Q3 must **HARD STOP and WAIT** for user submit or "skip". Optional fields do **not** authorize auto-continuing with defaults. Clarifies "do not re-ask" = after the user already replied, not "skip waiting".
1026
1021
 
@@ -1 +1 @@
1
- {"version":3,"file":"server-status.d.ts","sourceRoot":"","sources":["../src/server-status.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAExD,MAAM,MAAM,cAAc,GAAG,IAAI,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC;AAEjE,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,cAAc,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,SAAS,CAAC;IAClB,OAAO,EAAE,IAAI,GAAG,UAAU,GAAG,SAAS,CAAC;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,2CAA2C;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,eAAe,EAAE,CAAC;IAC9B,QAAQ,EAAE,aAAa,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,eAAe,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAqUD,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,kBAAkB,CAAC,CA6B7B"}
1
+ {"version":3,"file":"server-status.d.ts","sourceRoot":"","sources":["../src/server-status.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAExD,MAAM,MAAM,cAAc,GAAG,IAAI,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC;AAEjE,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,cAAc,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,SAAS,CAAC;IAClB,OAAO,EAAE,IAAI,GAAG,UAAU,GAAG,SAAS,CAAC;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,2CAA2C;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,eAAe,EAAE,CAAC;IAC9B,QAAQ,EAAE,aAAa,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,eAAe,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AA4ZD,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,kBAAkB,CAAC,CA6B7B"}
@@ -2,6 +2,7 @@ import fs from "fs/promises";
2
2
  import path from "path";
3
3
  import { getReplitObjectStorageClient, isReplitHost, resolveReplitObjectStorageMode } from "./replit-object-storage.js";
4
4
  import { pingCloudinary, resolveCloudinaryConfig } from "./cloudinary-storage.js";
5
+ import { isVercelBlobConfigured, pingVercelBlob, resolveVercelBlobMode, resolveVercelBlobToken } from "./vercel-blob-storage.js";
5
6
  import { getSdkPackageVersion } from "./sdk-version.js";
6
7
  /** Replit Preview/dev often cannot resolve Supabase hosts — not a config error. */
7
8
  function isReplitPreviewDnsFailure(message) {
@@ -13,33 +14,76 @@ function isReplitPreviewDnsFailure(message) {
13
14
  m.includes("cannot resolve") ||
14
15
  m.includes("name or service not known"));
15
16
  }
17
+ function isNeonDatabaseUrl(url) {
18
+ const u = String(url || "").toLowerCase();
19
+ return (u.includes("neon.tech") ||
20
+ u.includes("neon.db") ||
21
+ /\.neon\./.test(u));
22
+ }
23
+ /**
24
+ * Display name shown next to "Database" in the Control Panel status card.
25
+ * Prefer specific products (Neon / Supabase / Replit Postgres) over generic PostgreSQL.
26
+ */
16
27
  function resolveDatabaseProvider(env) {
17
28
  const driver = String(env.TOMORROWOS_STORE || "").trim().toLowerCase();
18
- const hasUrl = !!(env.SUPABASE_URL || env.DATABASE_URL);
19
- if (driver === "supabase" || (!driver && env.SUPABASE_URL)) {
29
+ const supabaseUrl = String(env.SUPABASE_URL || "").trim();
30
+ const databaseUrl = String(env.DATABASE_URL || "").trim();
31
+ const hasUrl = !!(supabaseUrl || databaseUrl);
32
+ const probeUrl = supabaseUrl || databaseUrl;
33
+ if (driver === "supabase" || (!driver && supabaseUrl)) {
20
34
  return {
21
- provider: "supabase",
35
+ provider: "Supabase",
22
36
  label: "Database",
23
- configured: !!env.SUPABASE_URL || !!env.DATABASE_URL
37
+ configured: hasUrl,
38
+ kind: "supabase"
24
39
  };
25
40
  }
26
- if (driver === "postgres" || (!driver && hasUrl && !env.SUPABASE_URL)) {
41
+ if (driver === "postgres" || (!driver && hasUrl && !supabaseUrl)) {
42
+ if (isNeonDatabaseUrl(probeUrl)) {
43
+ return {
44
+ provider: "Neon",
45
+ label: "Database",
46
+ configured: hasUrl,
47
+ kind: "postgres"
48
+ };
49
+ }
50
+ if (isReplitHost(env)) {
51
+ return {
52
+ provider: "Replit Postgres",
53
+ label: "Database",
54
+ configured: hasUrl,
55
+ kind: "postgres"
56
+ };
57
+ }
27
58
  return {
28
- provider: "postgres",
59
+ provider: "PostgreSQL",
29
60
  label: "Database",
30
- configured: hasUrl
61
+ configured: hasUrl,
62
+ kind: "postgres"
31
63
  };
32
64
  }
33
65
  if (driver === "memory") {
34
- return { provider: "memory", label: "Database", configured: true };
66
+ return {
67
+ provider: "Memory",
68
+ label: "Database",
69
+ configured: true,
70
+ kind: "memory"
71
+ };
35
72
  }
36
73
  if (driver === "sqlite" || !driver) {
37
- return { provider: "sqlite", label: "Database", configured: true };
74
+ return {
75
+ provider: "SQLite",
76
+ label: "Database",
77
+ configured: true,
78
+ kind: "sqlite"
79
+ };
38
80
  }
81
+ const pretty = driver.charAt(0).toUpperCase() + driver.slice(1).toLowerCase();
39
82
  return {
40
- provider: driver || "unknown",
83
+ provider: pretty || "Unknown",
41
84
  label: "Database",
42
- configured: hasUrl || driver === "sqlite" || driver === "memory"
85
+ configured: hasUrl || driver === "sqlite" || driver === "memory",
86
+ kind: "unknown"
43
87
  };
44
88
  }
45
89
  async function withTimeout(promise, timeoutMs, label) {
@@ -59,7 +103,7 @@ async function withTimeout(promise, timeoutMs, label) {
59
103
  }
60
104
  async function probeDatabase(store, env, timeoutMs) {
61
105
  const meta = resolveDatabaseProvider(env);
62
- const wantsRemote = meta.provider === "supabase" || meta.provider === "postgres";
106
+ const wantsRemote = meta.kind === "supabase" || meta.kind === "postgres";
63
107
  if (wantsRemote && !meta.configured) {
64
108
  return {
65
109
  id: "database",
@@ -71,7 +115,7 @@ async function probeDatabase(store, env, timeoutMs) {
71
115
  }
72
116
  try {
73
117
  await withTimeout(store.listPairedDevices(), timeoutMs, meta.label);
74
- if (meta.provider === "memory") {
118
+ if (meta.kind === "memory") {
75
119
  return {
76
120
  id: "database",
77
121
  label: meta.label,
@@ -80,7 +124,7 @@ async function probeDatabase(store, env, timeoutMs) {
80
124
  detail: "In-memory only — data is lost on restart."
81
125
  };
82
126
  }
83
- if (meta.provider === "sqlite" && isReplitHost(env)) {
127
+ if (meta.kind === "sqlite" && isReplitHost(env)) {
84
128
  return {
85
129
  id: "database",
86
130
  label: meta.label,
@@ -94,7 +138,7 @@ async function probeDatabase(store, env, timeoutMs) {
94
138
  label: meta.label,
95
139
  provider: meta.provider,
96
140
  state: "ok",
97
- detail: `${meta.label} reachable`
141
+ detail: `${meta.provider} reachable`
98
142
  };
99
143
  }
100
144
  catch (err) {
@@ -127,7 +171,7 @@ async function probeMedia(staticRoot, env, timeoutMs) {
127
171
  return {
128
172
  id: "media",
129
173
  label: "Media Server",
130
- provider: "cloudinary",
174
+ provider: "Cloudinary",
131
175
  state: "ok",
132
176
  detail: `Cloudinary (${cloudinary.cloudName}) reachable`
133
177
  };
@@ -137,7 +181,7 @@ async function probeMedia(staticRoot, env, timeoutMs) {
137
181
  return {
138
182
  id: "media",
139
183
  label: "Media Server",
140
- provider: "cloudinary",
184
+ provider: "Cloudinary",
141
185
  state: "error",
142
186
  detail: message
143
187
  };
@@ -150,11 +194,47 @@ async function probeMedia(staticRoot, env, timeoutMs) {
150
194
  return {
151
195
  id: "media",
152
196
  label: "Media Server",
153
- provider: "cloudinary",
197
+ provider: "Cloudinary",
154
198
  state: "error",
155
199
  detail: "Cloudinary config incomplete — set CLOUDINARY_CLOUD_NAME, CLOUDINARY_API_KEY, and CLOUDINARY_API_SECRET."
156
200
  };
157
201
  }
202
+ const blobMode = resolveVercelBlobMode(env);
203
+ if (blobMode !== "off") {
204
+ const token = resolveVercelBlobToken(env);
205
+ if (!token) {
206
+ return {
207
+ id: "media",
208
+ label: "Media Server",
209
+ provider: "Blob",
210
+ state: "error",
211
+ detail: "Vercel Blob required but BLOB_READ_WRITE_TOKEN is not set. Link a Blob store to this Vercel project."
212
+ };
213
+ }
214
+ try {
215
+ await withTimeout(pingVercelBlob(token), timeoutMs, "Blob");
216
+ return {
217
+ id: "media",
218
+ label: "Media Server",
219
+ provider: "Blob",
220
+ state: "ok",
221
+ detail: "Blob reachable"
222
+ };
223
+ }
224
+ catch (err) {
225
+ const message = err instanceof Error ? err.message : String(err);
226
+ if (blobMode === "required" || isVercelBlobConfigured(env)) {
227
+ return {
228
+ id: "media",
229
+ label: "Media Server",
230
+ provider: "Blob",
231
+ state: "error",
232
+ detail: message
233
+ };
234
+ }
235
+ // preferred but ping failed: fall through
236
+ }
237
+ }
158
238
  const rosMode = resolveReplitObjectStorageMode(env);
159
239
  if (rosMode !== "off") {
160
240
  try {
@@ -164,16 +244,16 @@ async function probeMedia(staticRoot, env, timeoutMs) {
164
244
  return {
165
245
  id: "media",
166
246
  label: "Media Server",
167
- provider: "replit-object-storage",
247
+ provider: "Replit Object Storage",
168
248
  state: "ok",
169
- detail: "Replit Object Storage (App Storage) reachable — uploads persist across Republish"
249
+ detail: "Replit Object Storage reachable"
170
250
  };
171
251
  }
172
252
  if (rosMode === "required") {
173
253
  return {
174
254
  id: "media",
175
255
  label: "Media Server",
176
- provider: "replit-object-storage",
256
+ provider: "Replit Object Storage",
177
257
  state: "error",
178
258
  detail: "TOMORROWOS_MEDIA requires Replit Object Storage but no client is available. Link an App Storage bucket and install @replit/object-storage."
179
259
  };
@@ -185,7 +265,7 @@ async function probeMedia(staticRoot, env, timeoutMs) {
185
265
  return {
186
266
  id: "media",
187
267
  label: "Media Server",
188
- provider: "replit-object-storage",
268
+ provider: "Replit Object Storage",
189
269
  state: "error",
190
270
  detail: message
191
271
  };
@@ -193,9 +273,10 @@ async function probeMedia(staticRoot, env, timeoutMs) {
193
273
  // preferred: fall through to local disk probe
194
274
  }
195
275
  }
196
- const provider = isReplitHost(env) ? "replit-object-storage" : "local";
197
- const providerLabel = provider === "replit-object-storage"
198
- ? "Local uploads (Object Storage not active — files may not survive Republish)"
276
+ const onReplit = isReplitHost(env);
277
+ const provider = onReplit ? "Replit Object Storage" : "Local";
278
+ const providerLabel = onReplit
279
+ ? "Local uploads (Object Storage not active)"
199
280
  : "Local uploads";
200
281
  if (!staticRoot) {
201
282
  return {
@@ -203,7 +284,7 @@ async function probeMedia(staticRoot, env, timeoutMs) {
203
284
  label: "Media Server",
204
285
  provider,
205
286
  state: "missing",
206
- detail: "No Cloudinary and staticRoot is unset — media uploads are unavailable."
287
+ detail: "No durable media backend and staticRoot is unset — uploads unavailable."
207
288
  };
208
289
  }
209
290
  const uploadsDir = path.join(path.resolve(staticRoot), "uploads");
@@ -215,7 +296,7 @@ async function probeMedia(staticRoot, env, timeoutMs) {
215
296
  label: "Media Server",
216
297
  provider,
217
298
  state: "warn",
218
- detail: `${providerLabel} writable at ${uploadsDir}. Set TOMORROWOS_MEDIA=replit-object-storage and link App Storage for durable media, or use Cloudinary.`
299
+ detail: `${providerLabel} at ${uploadsDir}. Prefer Cloudinary, Blob, or Replit Object Storage for production.`
219
300
  };
220
301
  }
221
302
  catch (err) {
@@ -239,19 +320,22 @@ function buildBlockers(connectors) {
239
320
  message: c.detail,
240
321
  fixHint: c.state === "missing"
241
322
  ? "Paste your Supabase Postgres connection string as Secret SUPABASE_URL, set TOMORROWOS_STORE=supabase, then restart the CMS."
242
- : "Check SUPABASE_URL / network / SSL (DATABASE_SSL=true). After Publish, confirm the live URL can reach Supabase."
323
+ : "Check SUPABASE_URL / DATABASE_URL / network / SSL (DATABASE_SSL=true). After Publish, confirm the live URL can reach the database."
243
324
  });
244
325
  }
245
326
  if (c.id === "media" && c.state === "error") {
327
+ const provider = String(c.provider || "");
246
328
  blockers.push({
247
329
  connectorId: c.id,
248
330
  title: "Media storage not ready",
249
331
  message: c.detail,
250
- fixHint: c.provider === "cloudinary"
332
+ fixHint: provider === "Cloudinary"
251
333
  ? "Fix Cloudinary Secrets (CLOUDINARY_CLOUD_NAME, CLOUDINARY_API_KEY, CLOUDINARY_API_SECRET) and restart."
252
- : c.provider === "replit-object-storage"
253
- ? "Link a Replit App Storage bucket, set TOMORROWOS_MEDIA=replit-object-storage, ensure @replit/object-storage is installed, then restart."
254
- : "Enable Object Storage for public/uploads, or configure Cloudinary for durable media URLs."
334
+ : provider === "Blob"
335
+ ? "Link a Vercel Blob store, set BLOB_READ_WRITE_TOKEN (and TOMORROWOS_MEDIA=vercel-blob), then redeploy."
336
+ : provider === "Replit Object Storage"
337
+ ? "Link a Replit App Storage bucket, set TOMORROWOS_MEDIA=replit-object-storage, ensure @replit/object-storage is installed, then restart."
338
+ : "Configure Cloudinary, Vercel Blob, or Replit Object Storage for durable media URLs."
255
339
  });
256
340
  }
257
341
  if (c.id === "media" && c.state === "missing") {
@@ -259,7 +343,7 @@ function buildBlockers(connectors) {
259
343
  connectorId: c.id,
260
344
  title: "Media storage not configured",
261
345
  message: c.detail,
262
- fixHint: "Set Cloudinary Secrets, or ensure the CMS listens with staticRoot pointing at public/ (uploads folder)."
346
+ fixHint: "Set Cloudinary, Vercel Blob (BLOB_READ_WRITE_TOKEN), or ensure the CMS listens with staticRoot pointing at public/ / cms-panel/."
263
347
  });
264
348
  }
265
349
  }
@@ -280,7 +364,7 @@ export async function buildServerStatus(options) {
280
364
  const server = {
281
365
  id: "server",
282
366
  label: "Server",
283
- provider: "tomorrowos",
367
+ provider: "TomorrowOS",
284
368
  state: "ok",
285
369
  detail: "CMS process is running"
286
370
  };
@@ -1 +1 @@
1
- {"version":3,"file":"tomorrowos.d.ts","sourceRoot":"","sources":["../src/tomorrowos.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tomorrowos.d.ts","sourceRoot":"","sources":["../src/tomorrowos.ts"],"names":[],"mappings":"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"}
@@ -10,6 +10,7 @@ import { PlaylistCatalog } from "./playlist-catalog.js";
10
10
  import { MemoryStore } from "./store/memory-store.js";
11
11
  import { resolveBrandLogoPath, syncProjectAssetsToStaticRoot } from "./brand-assets.js";
12
12
  import { createSignedUploadParams, deleteCloudinaryAsset, fetchCloudinaryResource, resolveCloudinaryConfig, uploadBufferToCloudinary } from "./cloudinary-storage.js";
13
+ import { isVercelBlobConfigured, resolveVercelBlobMode, resolveVercelBlobToken, uploadBufferToVercelBlob } from "./vercel-blob-storage.js";
13
14
  import { probeVideoDurationMs } from "./media-probe.js";
14
15
  import { assembleChunkedUpload, cleanupChunkedUpload, cleanupStaleChunkedUploads, initChunkedUpload, MEDIA_UPLOAD_CHUNK_BYTES, storeChunkedUploadPart } from "./chunked-upload.js";
15
16
  import { buildBrightSignZipWithCmsEndpoint, resolveRequestCmsOrigin } from "./player-zip.js";
@@ -1342,6 +1343,44 @@ export class TomorrowOS extends EventEmitter {
1342
1343
  await this.store.setUploadedAsset(asset);
1343
1344
  return { asset, deduplicated: false };
1344
1345
  }
1346
+ const blobMode = resolveVercelBlobMode();
1347
+ const blobToken = resolveVercelBlobToken();
1348
+ if (blobMode !== "off") {
1349
+ if (!blobToken) {
1350
+ if (blobMode === "required") {
1351
+ throw new Error("TOMORROWOS_MEDIA=vercel-blob requires BLOB_READ_WRITE_TOKEN. Link a Blob store to this Vercel project.");
1352
+ }
1353
+ }
1354
+ else {
1355
+ try {
1356
+ const storedName = buildContentAddressedNameFromHash(sha256, safeName);
1357
+ const uploaded = await uploadBufferToVercelBlob(body, {
1358
+ pathname: `uploads/${storedName}`,
1359
+ token: blobToken,
1360
+ contentType: mimeType
1361
+ });
1362
+ const asset = {
1363
+ id: existing?.id ?? randomUUID(),
1364
+ sha256,
1365
+ storageKey: uploaded.pathname || storedName,
1366
+ url: uploaded.url,
1367
+ originalFilename: safeName,
1368
+ mimeType,
1369
+ resourceType,
1370
+ bytes: body.length,
1371
+ createdAt: existing?.createdAt ?? now,
1372
+ updatedAt: now
1373
+ };
1374
+ await this.store.setUploadedAsset(asset);
1375
+ return { asset, deduplicated: false };
1376
+ }
1377
+ catch (err) {
1378
+ if (blobMode === "required")
1379
+ throw err;
1380
+ console.warn("[TomorrowOS] Vercel Blob upload failed; falling back:", err);
1381
+ }
1382
+ }
1383
+ }
1345
1384
  return this.persistRelativeUpload(body, sha256, safeName, mimeType, existing, resourceType);
1346
1385
  }
1347
1386
  getAssetIdsFromItems(items) {
@@ -1665,7 +1704,8 @@ export class TomorrowOS extends EventEmitter {
1665
1704
  status: "success",
1666
1705
  chunked: Boolean(this.staticRoot),
1667
1706
  chunkSize: MEDIA_UPLOAD_CHUNK_BYTES,
1668
- cloudinary: Boolean(resolveCloudinaryConfig())
1707
+ cloudinary: Boolean(resolveCloudinaryConfig()),
1708
+ vercelBlob: isVercelBlobConfigured()
1669
1709
  });
1670
1710
  return;
1671
1711
  }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Vercel Blob media storage via `@vercel/blob`.
3
+ * Auto-detects `BLOB_READ_WRITE_TOKEN`; `TOMORROWOS_MEDIA=vercel-blob|blob` makes it required.
4
+ */
5
+ export type VercelBlobMode = "off" | "preferred" | "required";
6
+ export interface VercelBlobUploadResult {
7
+ url: string;
8
+ pathname: string;
9
+ contentType?: string;
10
+ }
11
+ export declare function isVercelHost(env?: NodeJS.ProcessEnv): boolean;
12
+ /**
13
+ * - `TOMORROWOS_MEDIA=vercel-blob|blob` → required
14
+ * - `BLOB_READ_WRITE_TOKEN` present (and media not forced elsewhere) → preferred
15
+ * - else → off
16
+ */
17
+ export declare function resolveVercelBlobMode(env?: NodeJS.ProcessEnv): VercelBlobMode;
18
+ export declare function resolveVercelBlobToken(env?: NodeJS.ProcessEnv): string | null;
19
+ export declare function isVercelBlobConfigured(env?: NodeJS.ProcessEnv): boolean;
20
+ export declare function uploadBufferToVercelBlob(body: Buffer, options: {
21
+ pathname: string;
22
+ token: string;
23
+ contentType?: string;
24
+ addRandomSuffix?: boolean;
25
+ }): Promise<VercelBlobUploadResult>;
26
+ /** Lightweight token / reachability check for CMS status UI. */
27
+ export declare function pingVercelBlob(token: string): Promise<void>;
28
+ //# sourceMappingURL=vercel-blob-storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vercel-blob-storage.d.ts","sourceRoot":"","sources":["../src/vercel-blob-storage.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,WAAW,GAAG,UAAU,CAAC;AAE9D,MAAM,WAAW,sBAAsB;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAOD,wBAAgB,YAAY,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAE1E;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,cAAc,CAqBhB;AAED,wBAAgB,sBAAsB,CACpC,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,MAAM,GAAG,IAAI,CAEf;AAED,wBAAgB,sBAAsB,CACpC,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,OAAO,CAIT;AAUD,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE;IACP,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,GACA,OAAO,CAAC,sBAAsB,CAAC,CAejC;AAED,gEAAgE;AAChE,wBAAsB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGjE"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Vercel Blob media storage via `@vercel/blob`.
3
+ * Auto-detects `BLOB_READ_WRITE_TOKEN`; `TOMORROWOS_MEDIA=vercel-blob|blob` makes it required.
4
+ */
5
+ function cleanOptional(value) {
6
+ const trimmed = String(value || "").trim();
7
+ return trimmed || undefined;
8
+ }
9
+ export function isVercelHost(env = process.env) {
10
+ return !!(env.VERCEL || env.VERCEL_ENV || env.VERCEL_URL);
11
+ }
12
+ /**
13
+ * - `TOMORROWOS_MEDIA=vercel-blob|blob` → required
14
+ * - `BLOB_READ_WRITE_TOKEN` present (and media not forced elsewhere) → preferred
15
+ * - else → off
16
+ */
17
+ export function resolveVercelBlobMode(env = process.env) {
18
+ const media = cleanOptional(env.TOMORROWOS_MEDIA)?.toLowerCase();
19
+ if (media === "local" ||
20
+ media === "disk" ||
21
+ media === "filesystem" ||
22
+ media === "cloudinary" ||
23
+ media === "replit" ||
24
+ media === "replit-object-storage" ||
25
+ media === "object-storage" ||
26
+ media === "app-storage") {
27
+ return "off";
28
+ }
29
+ if (media === "vercel-blob" || media === "blob") {
30
+ return "required";
31
+ }
32
+ if (cleanOptional(env.BLOB_READ_WRITE_TOKEN)) {
33
+ return "preferred";
34
+ }
35
+ return "off";
36
+ }
37
+ export function resolveVercelBlobToken(env = process.env) {
38
+ return cleanOptional(env.BLOB_READ_WRITE_TOKEN) || null;
39
+ }
40
+ export function isVercelBlobConfigured(env = process.env) {
41
+ const mode = resolveVercelBlobMode(env);
42
+ if (mode === "off")
43
+ return false;
44
+ return !!resolveVercelBlobToken(env);
45
+ }
46
+ async function loadBlobApi() {
47
+ const mod = await import("@vercel/blob");
48
+ return { put: mod.put, list: mod.list };
49
+ }
50
+ export async function uploadBufferToVercelBlob(body, options) {
51
+ const { put } = await loadBlobApi();
52
+ const pathname = String(options.pathname || "upload").replace(/^\/+/, "");
53
+ const result = await put(pathname, body, {
54
+ access: "public",
55
+ token: options.token,
56
+ contentType: options.contentType,
57
+ addRandomSuffix: options.addRandomSuffix ?? false,
58
+ multipart: body.length > 4 * 1024 * 1024
59
+ });
60
+ return {
61
+ url: result.url,
62
+ pathname: result.pathname,
63
+ contentType: result.contentType
64
+ };
65
+ }
66
+ /** Lightweight token / reachability check for CMS status UI. */
67
+ export async function pingVercelBlob(token) {
68
+ const { list } = await loadBlobApi();
69
+ await list({ limit: 1, token });
70
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tomorrowos/sdk",
3
- "version": "0.9.67",
3
+ "version": "0.9.69",
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",
@@ -36,6 +36,7 @@
36
36
  },
37
37
  "dependencies": {
38
38
  "@replit/object-storage": "^1.0.0",
39
+ "@vercel/blob": "^2.6.1",
39
40
  "better-sqlite3": "^12.11.1",
40
41
  "cloudinary": "^2.10.0",
41
42
  "fflate": "^0.8.3",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "my-cms",
3
- "version": "0.9.67",
3
+ "version": "0.9.69",
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.67",
16
+ "@tomorrowos/sdk": "^0.9.69",
17
17
  "dotenv": "^17.2.3",
18
18
  "tsx": "^4.19.0"
19
19
  },