@tomorrowos/sdk 0.9.35 → 0.9.41

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/REPLIT_SETUP.md CHANGED
@@ -12,8 +12,11 @@ When a human pastes a prompt like this, **read this file and start Question 1 im
12
12
  ```text
13
13
  Follow @tomorrowos/sdk REPLIT_SETUP.md and set up my TomorrowOS CMS.
14
14
  Ask only the questions listed in REPLIT_SETUP.md, in order. Do not use LLM_PROMPT.md.
15
- Artifact kind must be **web** (never **api**). Question 3 only updates **brand.json**.
16
- After all setup questions are answered (including Cloudinary Secrets if you chose Cloudinary), configure and start — minimal verification only (Web app type + Preview at /). **Never test Supabase connection** — save `SUPABASE_URL` and move on. No Supabase or WebSocket tests.
15
+ Artifact kind must be **web** (never **api**).
16
+ Question 1 database options in this order: 1) Supabase (Recommended) 2) Built-in Replit PostgreSQL 3) SQLite.
17
+ If Supabase: collect SUPABASE_URL once only — never ask for the connection string twice.
18
+ Question 3 branding fields are all optional; missing values keep starter brand.json defaults.
19
+ After all setup questions are answered (including Cloudinary Secrets if you chose Cloudinary), configure and start — minimal verification only (Web app type + Preview at /). **Never test database connection** in Preview — configure and move on. No DB or WebSocket tests.
17
20
  Do not skip steps.
18
21
  ```
19
22
 
@@ -58,20 +61,28 @@ Replit Agent must ask **only** the questions defined in **this file**, in **this
58
61
 
59
62
  | Step | Section | When |
60
63
  |------|---------|------|
61
- | 1 | **Question 1** — Supabase connection string | Always |
64
+ | 1 | **Question 1** — Database choice (Supabase Replit Postgres → SQLite) | Always |
62
65
  | 2 | **Question 2** — Media storage (+ Cloudinary Secrets if chosen) | Always (after Q1) |
63
- | 3 | **Question 3** — Brand / TomorrowOS app look | Always last, before execution |
66
+ | 3 | **Question 3** — Brand / TomorrowOS app look (**all fields optional**) | Always last, before execution |
64
67
 
65
68
  **That is the complete list.** There are no other setup questions. **Do not** ask how many screens / devices. There is **no** “Question 2b” — Cloudinary credentials are collected **inside Question 2**, immediately after the user chooses Cloudinary.
66
69
 
70
+ **Critical — Question 1 option order (exact):** Present database choices in **this order only**:
71
+ 1. **Supabase Postgres (Recommended)**
72
+ 2. **Built-in Replit PostgreSQL**
73
+ 3. **SQLite (local file — fine for testing)**
74
+
75
+ **Critical — Supabase path collected once only:** If the user picks Supabase, collect `SUPABASE_URL` in **one** step (Secrets UI **or** chat paste — not both). After it is saved, **never** ask for the connection string / `SUPABASE_URL` again (not in Q2, Q3, execution, Publish, or “confirm Secrets”).
76
+
67
77
  **Critical:** Question 2 is **not complete** when the user says “Cloudinary” or “yes”. You **must** immediately show the Cloudinary Secrets input (same step — do **not** label a new question) and collect `CLOUDINARY_CLOUD_NAME`, `CLOUDINARY_API_KEY`, and `CLOUDINARY_API_SECRET` **before** Question 3. Do **not** skip to branding with placeholder or invented credentials.
68
78
 
79
+ **Critical — Question 3:** Every branding field is **optional**. Empty / skip / “use defaults” → keep values from the starter **`brand.json`**. Do **not** re-ask for missing name or colours.
69
80
 
70
- If the user volunteers extra info early (e.g. brand name before Q3), **record it** and still ask the current question’s required fields you do not yet have. Do not skip ahead to execution until Q1–Q3 are complete (Question 2 includes Cloudinary Secrets when applicable).
81
+ If the user volunteers extra info early (e.g. brand name before Q3), **record it** and still ask the current question’s required fields you do not yet have. Do not skip ahead to execution until Q1–Q3 are complete (Question 2 includes Cloudinary Secrets when applicable). For Q3, “complete” includes the user skipping or leaving fields blank.
71
82
 
72
83
  If the user says “just set it up, don’t ask questions,” respond:
73
84
 
74
- > I need a few quick answers from REPLIT_SETUP.md (Supabase connection string, media storage — plus Cloudinary credentials if you choose Cloudinary — and branding). It takes about a minute and ensures the CMS deploys correctly on Replit.
85
+ > I need a few quick answers from REPLIT_SETUP.md (database choice — Supabase recommended, then Replit Postgres, then SQLite — media storage — plus Cloudinary credentials if you choose Cloudinary — and optional branding). It takes about a minute and ensures the CMS deploys correctly on Replit.
75
86
 
76
87
  Then ask **Question 1** — do not switch to `LLM_PROMPT.md`.
77
88
 
@@ -82,12 +93,15 @@ Then ask **Question 1** — do not switch to `LLM_PROMPT.md`.
82
93
  1. **Ask only REPLIT_SETUP.md questions 1–3** (see **Questionnaire scope**). Never use `LLM_PROMPT.md` on Replit.
83
94
  2. **One question at a time.** Wait for the user’s answer before asking the next question (unless they already answered several in one message). **Exception:** If the user chooses Cloudinary in Question 2, **stay on Question 2** and immediately show the Secrets form — do **not** open a separate “2b” step or announce a new question number.
84
95
  3. **Do not invent** Cloudinary credentials, Supabase URLs, or brand colours. **Do not ask** screen / device count.
85
- 4. **Do not use Replit’s reserved `DATABASE_URL` Secret for Supabase.** On Replit, `DATABASE_URL` is often pre-claimed. Always use **`SUPABASE_URL`** for TomorrowOS Supabase.
96
+ 4. **Do not use Replit’s reserved `DATABASE_URL` Secret for Supabase.** On Replit, `DATABASE_URL` is often the **built-in Replit PostgreSQL** URL. For Supabase always use **`SUPABASE_URL`**. For built-in Replit Postgres, use **`DATABASE_URL`** with `TOMORROWOS_STORE=postgres`.
97
+ 4a. **If Supabase is chosen: never collect `SUPABASE_URL` twice.** One capture after the choice is enough for the whole wizard and execution checklist.
98
+ 4b. **Question 3 fields are all optional.** Missing values → keep starter `brand.json` defaults; do not block or re-prompt.
99
+ 4c. **`TOMORROWOS_STORE` + `DATABASE_SSL` are agent-owned.** After the Q1 choice (and URL if needed), set them yourself. Never ask the user to type those names/values.
86
100
  5. **Prefer `npx @tomorrowos/sdk init`** (or the package’s `templates/cms-starter`) as the project seed. Do not rebuild pairing, WebSocket, or playlist APIs from scratch.
87
101
  6. **Never commit secrets** into git. Put credentials only in Replit **Secrets** (and optionally mirror non-secret flags in `.env` that reference env vars).
88
102
  7. After Q&A, **configure and start** — do **not** run a long test suite (see **Post-Q&A: minimal verification only**).
89
103
  8. **Follow the Runtime & Replit publish rules below exactly.** A project that cannot Deploy / Publish on Replit is incomplete.
90
- 9. **Supabase: configure only — never test.** Save `SUPABASE_URL` + `TOMORROWOS_STORE=supabase` and proceed. **Do not** ping Postgres, run `psql`, start the server solely to verify DB connectivity, or treat log errors like `ENOTFOUND` / `database connection failure` in **Replit dev / Preview** as a failed setup. Supabase often works only after **Publish** (production networking), not in the dev shell.
104
+ 9. **Database: configure only — never test in Preview.** Save store Secrets / flags and proceed. **Do not** ping Postgres, run `psql`, or treat `ENOTFOUND` / `database connection failure` in **Replit dev / Preview** as a failed setup (especially Supabase).
91
105
  10. **Artifact kind = `web` only.** **`kind = "api"` is forbidden** for the TomorrowOS CMS artifact. See **IRON RULE** above. Setup is incomplete if Publish would still see an `api` artifact.
92
106
 
93
107
  **Question order (always):**
@@ -330,17 +344,17 @@ Replace `YOUR-REPLIT-DOMAIN` with the actual published hostname (e.g. `my-cms.re
330
344
 
331
345
  After Questions 1–3, **configure → `npm install` → `npm run start` → done.**
332
346
 
333
- ### Supabase: never test in Replit dev (mandatory)
347
+ ### Database / Postgres: never test in Replit dev (mandatory)
334
348
 
335
- Setup always uses Supabase: **save credentials + wire `server.ts` + start** — **nothing else**.
349
+ After Q1, **save credentials / flags + wire `server.ts` + start** — **nothing else**.
336
350
 
337
351
  **Forbidden after Q&A (and after Question 1):**
338
352
 
339
- - ❌ “Testing Supabase connection…”
353
+ - ❌ “Testing Supabase / Postgres connection…”
340
354
  - ❌ `psql`, `pg`, Prisma `db pull`, or any one-off DB probe script
341
355
  - ❌ Restarting / debugging the server **because** logs show `database connection failure`, `getaddrinfo ENOTFOUND`, or `db.*.supabase.co can't be resolved`
342
356
  - ❌ Telling the user their Supabase URL is wrong **only** because dev Preview cannot resolve the host
343
- - ❌ Blocking setup completion on Supabase connectivity
357
+ - ❌ Blocking setup completion on database connectivity
344
358
 
345
359
  **Expected in Replit dev:** Supabase host may **not resolve** in the dev environment. That is **normal**. The Control Panel at `/` can still load. Mark setup **complete** when Web app type is correct and Preview shows the Control Panel — **even if** Supabase errors appear in logs.
346
360
 
@@ -356,7 +370,7 @@ Setup always uses Supabase: **save credentials + wire `server.ts` + start** —
356
370
 
357
371
  ### Do NOT run after setup (unless user asks or Publish fails)
358
372
 
359
- - ❌ **Supabase connection test** — **never**, including after `npm run start` (no `psql`, no DB ping, no “verifying database…”, no retry loops for `ENOTFOUND`)
373
+ - ❌ **Database connection test** — **never**, including after `npm run start` (no `psql`, no DB ping, no “verifying database…”, no retry loops for `ENOTFOUND`)
360
374
  - ❌ **Cloudinary upload test**
361
375
  - ❌ **`curl` / `fetch` on `/brand.json`** as a formal gate
362
376
  - ❌ **Browser WebSocket console snippet** as a formal gate
@@ -372,29 +386,94 @@ Then (and only then) check: API vs Web type → `.replit` ports/`deploymentTarge
372
386
 
373
387
  ---
374
388
 
375
- ## Question 1 — Supabase connection (always)
389
+ ## Question 1 — Database choice
390
+
391
+ > **This is the first setup question.** **Do not** ask how many screens / devices.
392
+
393
+ ### Step A — Ask exactly (option order is mandatory)
394
+
395
+ Present **exactly these three options, in this order** (Supabase first / recommended):
396
+
397
+ > **Database choice**
398
+ >
399
+ > 1. **Supabase Postgres (Recommended)** — I have a connection string
400
+ > 2. **Built-in Replit PostgreSQL** — no external account needed
401
+ > 3. **SQLite** (local file — fine for testing)
402
+ >
403
+ > Which do you want?
404
+
405
+ **Forbidden option orders / labels:**
406
+
407
+ - ❌ Putting Built-in Replit PostgreSQL first or marking it “recommended”
408
+ - ❌ Omitting Supabase or SQLite
409
+ - ❌ Asking only for a connection string without showing this choice first (unless `SUPABASE_URL` is already saved and the user is continuing a prior Supabase setup)
376
410
 
377
- > **This is the first setup question.** Always configure Supabase. **Do not** ask how many screens / devices.
411
+ **Agent auto-sets after the choice** (never ask the user to type these):
378
412
 
379
- **Preferred on Replit:** open an **input / Secrets** UI where the user pastes the connection string into a field for:
413
+ | Choice | Agent sets | User provides |
414
+ |--------|------------|---------------|
415
+ | 1 Supabase | `TOMORROWOS_STORE=supabase`, `DATABASE_SSL=true` | `SUPABASE_URL` (**once**) |
416
+ | 2 Replit PostgreSQL | `TOMORROWOS_STORE=postgres`, `DATABASE_SSL=true` | Use Replit’s built-in **`DATABASE_URL`** (do not ask them to invent a URI) |
417
+ | 3 SQLite | `TOMORROWOS_STORE=sqlite` | Confirmation only |
418
+
419
+ ### Step B1 — If **1 Supabase (Recommended)**
420
+
421
+ Collect `SUPABASE_URL` **exactly once** (IRON RULE).
422
+
423
+ | Allowed (pick **one**) | Forbidden |
424
+ |------------------------|-----------|
425
+ | Open the Replit **Secrets** UI for `SUPABASE_URL` **or** ask the user to paste the string in chat | Asking for the connection string again after Secrets was already filled |
426
+ | Save Secret once, then move on | Opening Secrets **and** also asking them to paste the same URI in chat |
427
+ | Reuse the saved Secret in execution | Asking again in Q2 / Q3 / “confirm your Supabase Secret” / Publish / checklist |
428
+
429
+ If `SUPABASE_URL` is **already** present in Replit Secrets when this branch starts, **do not** ask for it again — confirm you will use the existing Secret and proceed to Question 2 (unless the user explicitly wants to replace it).
430
+
431
+ **Preferred on Replit:** open an **input / Secrets** UI for:
380
432
 
381
433
  - `SUPABASE_URL`
382
434
 
383
- **Ask exactly (copy wording; do not substitute LLM_PROMPT questions):**
435
+ **Only if** the Secrets UI is unavailable, ask exactly in chat. **Do not** also open a second Secrets prompt for the same value:
384
436
 
385
- > Paste your Supabase Postgres connection string below. I will store it as the Replit Secret **`SUPABASE_URL`** (not `DATABASE_URL` — that name is often reserved by Replit).
437
+ > Paste your Supabase Postgres connection string below. I will store it as the Replit Secret **`SUPABASE_URL`** (not `DATABASE_URL` — that name is for built-in Replit PostgreSQL).
386
438
  >
387
439
  > Example shape: `postgresql://postgres.[PROJECT]:[PASSWORD]@aws-0-[REGION].pooler.supabase.com:6543/postgres`
388
440
  >
389
- > Also confirm whether SSL is required (usually **yes** for Supabase).
441
+ > SSL is assumed **yes** for Supabase unless you say otherwise.
390
442
 
391
443
  **You must then:**
392
444
 
393
- 1. Create / update Replit Secret: `SUPABASE_URL=<user value>`
394
- 2. Create / update Replit Secret or `.env`:
445
+ 1. Create / update Replit Secret: `SUPABASE_URL=<user value>` (**once**).
446
+ 2. Set flags yourself (not a second paste of the URI):
395
447
  - `TOMORROWOS_STORE=supabase`
396
- - `DATABASE_SSL=true` (unless the user explicitly says SSL is off)
397
- 3. Update `server.ts` so the store uses Supabase via `SUPABASE_URL`:
448
+ - `DATABASE_SSL=true`
449
+ 3. Wire `server.ts` with `createTomorrowOSStore({ databaseUrl: process.env.SUPABASE_URL || process.env.DATABASE_URL, sqlitePath: ... })`.
450
+ 4. **Do not** write the password into committed files.
451
+ 5. **Do not** test the connection — configuration only.
452
+ 6. **Do not** proceed to Question 2 until `SUPABASE_URL` is saved (or was already present and reused).
453
+ 7. **After this branch completes, never re-request `SUPABASE_URL`.**
454
+
455
+ ### Step B2 — If **2 Built-in Replit PostgreSQL**
456
+
457
+ 1. Ensure Replit’s built-in Postgres is enabled for the Repl (create/attach if the platform UI requires it).
458
+ 2. Use the platform-provided **`DATABASE_URL`** Secret — **do not** ask the user to paste a Supabase-style URI.
459
+ 3. Set yourself:
460
+ - `TOMORROWOS_STORE=postgres`
461
+ - `DATABASE_SSL=true` (unless Replit docs for this DB say otherwise)
462
+ 4. Wire `server.ts` with `databaseUrl: process.env.DATABASE_URL || process.env.SUPABASE_URL`.
463
+ 5. **Do not** set `TOMORROWOS_STORE=supabase` for this path.
464
+ 6. **Do not** ask for `SUPABASE_URL`.
465
+ 7. **Do not** test the DB connection in Preview — configuration only.
466
+ 8. Proceed to Question 2.
467
+
468
+ ### Step B3 — If **3 SQLite**
469
+
470
+ 1. Set `TOMORROWOS_STORE=sqlite` yourself.
471
+ 2. Ensure `data/tomorrowos.db` path via starter / `createTomorrowOSStore({ sqlitePath: join(__dirname, "data", "tomorrowos.db") })`.
472
+ 3. Warn briefly: SQLite is fine for testing; pairings may not survive some rebuilds as well as Postgres.
473
+ 4. **Do not** ask for any connection string.
474
+ 5. Proceed to Question 2.
475
+
476
+ ### Shared `server.ts` shape (all Q1 paths)
398
477
 
399
478
  ```ts
400
479
  import "dotenv/config";
@@ -407,7 +486,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
407
486
  const brand = JSON.parse(readFileSync(join(__dirname, "brand.json"), "utf8"));
408
487
 
409
488
  const store = createTomorrowOSStore({
410
- // Prefer SUPABASE_URL on Replit. Do NOT rely on Replit's reserved DATABASE_URL.
489
+ // Supabase: SUPABASE_URL. Replit Postgres: DATABASE_URL. SQLite: ignores URL.
411
490
  databaseUrl: process.env.SUPABASE_URL || process.env.DATABASE_URL,
412
491
  sqlitePath: join(__dirname, "data", "tomorrowos.db")
413
492
  });
@@ -421,20 +500,6 @@ tomorrowos.listen({
421
500
  });
422
501
  ```
423
502
 
424
- 4. Ensure `.env` / Secrets include:
425
-
426
- ```env
427
- TOMORROWOS_STORE=supabase
428
- SUPABASE_URL=postgresql://...
429
- DATABASE_SSL=true
430
- ```
431
-
432
- 5. **Do not** write the real password into committed files.
433
- 6. **Do not** test the Supabase connection after saving the Secret — **configuration only**. No probe, no `npm run start` just to verify DB, no fixing “connection failure” in dev. Saving `SUPABASE_URL` completes Question 1.
434
- 7. **Do not** proceed to Question 2 until `SUPABASE_URL` is saved.
435
-
436
- If the user refuses Supabase, warn that Replit fleets need a durable Postgres store, then offer SQLite only after they explicitly confirm.
437
-
438
503
  **Store in `brand.json` later (Question 3):** `cms.hostingTarget`: `"here"`. Do **not** ask for `expectedScreens`; leave a default (e.g. `5`) when writing `brand.json`.
439
504
 
440
505
  ---
@@ -499,35 +564,45 @@ The SDK auto-detects these env vars and uploads media to Cloudinary. New uploads
499
564
 
500
565
  ---
501
566
 
502
- ## Question 3 — Brand / TomorrowOS app look (`brand.json` only)
567
+ ## Question 3 — Brand / TomorrowOS app look (`brand.json` only; **all fields optional**)
503
568
 
504
569
  > **Scope:** Question 3 answers **only** update **`brand.json`** — colours, name, tagline, logo path, etc. They configure how the **TomorrowOS player app** looks and metadata flags.
505
570
  >
506
571
  > **Question 3 does NOT affect:** Replit Publish, artifact `kind`, `.replit`, `server.ts`, Secrets, Supabase, Cloudinary, ports, deployment target, or CMS server wiring. **Never** change deploy/runtime config based on branding answers.
572
+ >
573
+ > **Every field below is optional.** The user may skip the whole question, answer only some fields, or say “use defaults”. For anything missing, **keep the existing starter `brand.json` values** (from `templates/cms-starter/brand.json` / project `brand.json` after `init`). **Do not** invent a second branding prompt. **Do not** block setup because name or colours were omitted.
507
574
 
508
575
  > **This is the only branding / platform / use-case question block.** Do not ask separate LLM_PROMPT “target platform”, “use case”, or “hosting” questions before this. **Do not** ask screen count.
509
576
 
510
- **Ask exactly (one message; user may answer in one reply):**
577
+ **Ask exactly (one message; user may answer in one reply, partially, or skip):**
511
578
 
512
- > Let’s brand your TomorrowOS experience. Please provide:
579
+ > Let’s brand your TomorrowOS experience (**all optional** — reply “skip” or leave blank to keep the starter `brand.json` defaults):
513
580
  >
514
- > 1. **Product / venue name** (shown on screens and the Control Panel)
515
- > 2. **Tagline** (optional)
516
- > 3. **Primary colour** (hex, e.g. `#FF8A3D`)
517
- > 4. **Background colour** (hex, optional — default `#FAFAF9`)
518
- > 5. **Text colour** (hex, optional — default `#0A0908`)
519
- > 6. **Secondary / accent colour** (hex, optional)
520
- > 7. **Logo** — upload an SVG/PNG into the project, or give a URL I can fetch into `./assets/`
581
+ > 1. **Product / venue name** (optional default from `brand.json`, e.g. `My Venue`)
582
+ > 2. **Tagline** (optional — default e.g. `Digital signage`)
583
+ > 3. **Primary colour** (optional hex — default e.g. `#FF8A3D`)
584
+ > 4. **Background colour** (optional — default e.g. `#FAFAF9`)
585
+ > 5. **Text colour** (optional — default e.g. `#0A0908`)
586
+ > 6. **Secondary / accent colour** (optional — default e.g. `#F5F3EF`)
587
+ > 7. **Logo** (optional) — upload an SVG/PNG, or a URL to fetch into `./assets/` (otherwise keep `./assets/logo.svg`)
588
+
589
+ **Defaults rule (mandatory):**
521
590
 
591
+ 1. Start from the current project **`brand.json`** (after `init`, this matches the SDK starter).
592
+ 2. Overlay **only** fields the user actually provided.
593
+ 3. If the user gives **no** product name → keep existing `"name"`.
594
+ 4. If the user gives **no** colours → keep existing `primaryColor` / `backgroundColor` / `textColor` / `secondaryColor`.
595
+ 5. If the user says **skip** / **defaults** / **n/a** / sends an empty answer → leave `brand.json` unchanged (except set `cms.hostingTarget` to `"here"` if needed — see below).
596
+ 6. **Never** re-ask for missing branding fields. **Never** treat blank name/colour as an error.
522
597
 
523
- If the user only gives a name and primary colour, use defaults for the rest and say what you assumed.
598
+ **Then write / update `brand.json` only** at the project root (validate mentally against `brand.schema.json` in the SDK). **Do not** modify `.replit`, `server.ts`, Secrets, or deployment settings from these answers.
524
599
 
525
- **Then write / update `brand.json` only** at the project root (validate mentally against `brand.schema.json` in the SDK). **Do not** modify `.replit`, `server.ts`, Secrets, or deployment settings from these answers. Minimum example:
600
+ Example when the user provided only a name (other fields kept from starter):
526
601
 
527
602
  ```json
528
603
  {
529
- "name": "<user name>",
530
- "tagline": "<user tagline or Digital signage>",
604
+ "name": "<user name or keep starter name>",
605
+ "tagline": "Digital signage",
531
606
  "targetPlatforms": ["tizen"],
532
607
  "primaryColor": "#FF8A3D",
533
608
  "secondaryColor": "#F5F3EF",
@@ -575,14 +650,32 @@ If `init` is inappropriate (existing customised project), merge carefully:
575
650
 
576
651
  ### B. Apply store + media Secrets
577
652
 
578
- **Supabase (always from Question 1):**
653
+ Apply **only** the path chosen in Question 1 — do not re-ask.
654
+
655
+ **If Q1 = Supabase (reuse; do not ask again):**
579
656
 
580
657
  ```env
581
658
  TOMORROWOS_STORE=supabase
582
- SUPABASE_URL=...
583
659
  DATABASE_SSL=true
584
660
  ```
585
661
 
662
+ Use the **`SUPABASE_URL` already saved**. **Do not** prompt to paste it again.
663
+
664
+ **If Q1 = Built-in Replit PostgreSQL:**
665
+
666
+ ```env
667
+ TOMORROWOS_STORE=postgres
668
+ DATABASE_SSL=true
669
+ ```
670
+
671
+ Use Replit’s **`DATABASE_URL`**. **Do not** ask for `SUPABASE_URL`.
672
+
673
+ **If Q1 = SQLite:**
674
+
675
+ ```env
676
+ TOMORROWOS_STORE=sqlite
677
+ ```
678
+
586
679
  **Cloudinary (if chosen):**
587
680
 
588
681
  ```env
@@ -594,7 +687,7 @@ CLOUDINARY_API_SECRET=...
594
687
  ### C. Wire `server.ts`
595
688
 
596
689
  - Always: `host: "0.0.0.0"`, `port: Number(process.env.PORT) || 3000`, `staticRoot: public`
597
- - Supabase path: pass `databaseUrl: process.env.SUPABASE_URL || process.env.DATABASE_URL` into `createTomorrowOSStore`
690
+ - Store: `createTomorrowOSStore({ databaseUrl: process.env.SUPABASE_URL || process.env.DATABASE_URL, sqlitePath: ... })` driver comes from `TOMORROWOS_STORE`
598
691
  - Keep TomorrowOS event handlers from the starter (paired / online policy push)
599
692
 
600
693
  ### D. Runtime files for Replit publish
@@ -659,6 +752,9 @@ Do **not**:
659
752
  - Replace `@tomorrowos/sdk` with a hand-rolled server
660
753
  - Change the production start command away from `tsx server.ts` / `npm run start`
661
754
  - Point Supabase at Replit’s reserved `DATABASE_URL` when `SUPABASE_URL` should be used
755
+ - List Q1 databases with **Replit PostgreSQL first** or mark it recommended instead of Supabase
756
+ - **Ask for `SUPABASE_URL` / the Postgres connection string more than once** (Secrets UI + chat, or again during execution / Publish)
757
+ - Ask the user to type `TOMORROWOS_STORE` or `DATABASE_SSL`
662
758
  - Skip Cloudinary recommendation without stating the persistence tradeoff
663
759
  - **Skip Cloudinary Secrets** after the user chooses Cloudinary — always collect `CLOUDINARY_*` inside Question 2 before Question 3
664
760
  - **Announce “Question 2b”** or a separate follow-up question — show the Secrets form immediately instead
@@ -669,14 +765,16 @@ Do **not**:
669
765
  - Create the Repl as **API-only** or publish with **Static Deployment** only
670
766
  - Set or leave artifact **`kind = "api"`** for the TomorrowOS CMS
671
767
  - Change `.replit`, `server.ts`, Secrets, or deployment config based on **Question 3** branding answers (those go in **`brand.json` only**)
768
+ - **Require** product name, colours, or logo in Question 3 — all branding fields are optional; missing → starter `brand.json` defaults
769
+ - **Re-ask** Question 3 for blank name/colour instead of keeping defaults
672
770
  - Omit `[[ports]]` / `deploymentTarget` from `.replit` when setting up for Publish
673
- - Run **Supabase connection tests**, **WebSocket console tests**, or other post-setup smoke tests unless Preview fails or the user asks
771
+ - Run **database connection tests**, **WebSocket console tests**, or other post-setup smoke tests unless Preview fails or the user asks
674
772
  - **Debug or “fix” Supabase** because dev logs show `getaddrinfo ENOTFOUND` or `database connection failure` — save config and finish setup instead
675
- - Ask how many screens / devices (≤5 vs >5) — always start with Supabase
773
+ - Ask how many screens / devices (≤5 vs >5)
676
774
 
677
775
  ---
678
776
 
679
- ## SQLite fallback — `server.ts` reference (only if user refuses Supabase)
777
+ ## SQLite path — `server.ts` reference (Q1 option 3)
680
778
 
681
779
  ```ts
682
780
  const store = createTomorrowOSStore({
@@ -712,7 +810,11 @@ TOMORROWOS_STORE=sqlite
712
810
 
713
811
  ## Protocol version
714
812
 
715
- `replit-setup/1.8` — aligned with TomorrowOS protocol `1.0` and `@tomorrowos/sdk` store drivers `sqlite` | `supabase` | `postgres` | `memory`.
813
+ `replit-setup/1.10` — aligned with TomorrowOS protocol `1.0` and `@tomorrowos/sdk` store drivers `sqlite` | `supabase` | `postgres` | `memory`.
814
+
815
+ **Changelog 1.10:** Question 1 is a **database choice** with fixed order: **1) Supabase (Recommended) → 2) Built-in Replit PostgreSQL → 3) SQLite**. Supabase still collects `SUPABASE_URL` once only.
816
+
817
+ **Changelog 1.9:** Question 1 collects `SUPABASE_URL` **once only** (no second Secrets/chat prompt in execution). Question 3 branding fields are **all optional**; missing values keep starter `brand.json` defaults.
716
818
 
717
819
  **Changelog 1.8:** Question 1 is always Supabase connection string (input / Secret). Removed screen-count branching (≤5 SQLite vs >5 Supabase). Media storage is Question 2; branding is Question 3.
718
820
 
@@ -1,27 +1,27 @@
1
- # Vercel / v0 Verbatim question bank
1
+ # Vercel / v0 �?Verbatim question bank
2
2
 
3
- > **Protocol:** `vercel-questions/1.0` (pairs with `VERCEL_SETUP.md` 1.9+ / `@tomorrowos/sdk` 0.9.35+)
3
+ > **Protocol:** `vercel-questions/1.0` (pairs with `VERCEL_SETUP.md` 1.9+ / `@tomorrowos/sdk` 0.9.41+)
4
4
  > **For Agents:** When asking the user a setup question, **copy-paste the block below for that step EXACTLY**.
5
- > Do **not** paraphrase, reorder, rename options, or invent alternatives (no S3, no Supabase Storage, no “Blob recommended”, no “disable media”).
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.
7
7
 
8
8
  ---
9
9
 
10
- ## Q1 Database (paste this entire block)
10
+ ## Q1 �?Database (paste this entire block)
11
11
 
12
12
  Which database should TomorrowOS use on Vercel?
13
13
 
14
- **1. Supabase Postgres (recommended)** durable pairing/playlists; use the **Session pooler** URL (`*.pooler.supabase.com:6543`).
14
+ **1. Supabase Postgres (recommended)** �?durable pairing/playlists; use the **Session pooler** URL (`*.pooler.supabase.com:6543`).
15
15
 
16
- **2. Neon Postgres** serverless Postgres native to Vercel; use Neon’s **pooled** connection string from the Neon dashboard (not the direct un-pooled host for serverless).
16
+ **2. Neon Postgres** �?serverless Postgres native to Vercel; use Neon’s **pooled** connection string from the Neon dashboard (not the direct un-pooled host for serverless).
17
17
 
18
- **3. SQLite (`data/tomorrowos.db`)** — **demo / local only on Vercel.** The filesystem is ephemeral; pairings and playlists will not survive redeploys. Only choose this if you understand the limitation.
18
+ **3. SQLite (`data/tomorrowos.db`)** �?**demo / local only on Vercel.** The filesystem is ephemeral; pairings and playlists will not survive redeploys. Only choose this if you understand the limitation.
19
19
 
20
- Reply with **1**, **2**, or **3** (or “Supabase / “Neon / “SQLite”).
20
+ Reply with **1**, **2**, or **3** (or “Supabase�?/ “Neon�?/ “SQLite�?.
21
21
 
22
22
  ---
23
23
 
24
- ## After Q1 agent only (do not ask the user)
24
+ ## After Q1 �?agent only (do not ask the user)
25
25
 
26
26
  | Choice | You set silently | User only provides |
27
27
  |--------|------------------|--------------------|
@@ -33,38 +33,38 @@ Never show blank Env fields for `TOMORROWOS_STORE` or `DATABASE_SSL`.
33
33
 
34
34
  ---
35
35
 
36
- ## Q2 Media (paste this entire block)
36
+ ## Q2 �?Media (paste this entire block)
37
37
 
38
38
  How should playlist media (images/videos) be stored?
39
39
 
40
- **1. Cloudinary (recommended)** durable public HTTPS URLs (`https://res.cloudinary.com/...`). Works out of the box with `@tomorrowos/sdk` auto-detection. **Prefer this on Vercel.**
40
+ **1. Cloudinary (recommended)** �?durable public HTTPS URLs (`https://res.cloudinary.com/...`). Works out of the box with `@tomorrowos/sdk` auto-detection. **Prefer this on Vercel.**
41
41
 
42
- **2. Vercel Blob** Vercel-native object storage; durable `https://*.public.blob.vercel-storage.com/...` URLs. Use when you want media on the same Vercel project without a Cloudinary account.
42
+ **2. Vercel Blob** �?Vercel-native object storage; durable `https://*.public.blob.vercel-storage.com/...` URLs. Use when you want media on the same Vercel project without a Cloudinary account.
43
43
 
44
- **3. Local disk only** — `cms-panel/uploads` or `public/uploads` (OK for quick local tests; **not** for production Vercel fleets files are ephemeral).
44
+ **3. Local disk only** �?`cms-panel/uploads` or `public/uploads` (OK for quick local tests; **not** for production Vercel fleets �?files are ephemeral).
45
45
 
46
- Reply with **1**, **2**, or **3** (or “Cloudinary / “Vercel Blob / “local”).
46
+ Reply with **1**, **2**, or **3** (or “Cloudinary�?/ “Vercel Blob�?/ “local�?.
47
47
 
48
- **Forbidden replacements (never ask these):** Vercel Blob as recommended #1; Supabase Storage; S3 / R2 / B2 / `MEDIA_S3_*`; “no media storage / disable uploads.
48
+ **Forbidden replacements (never ask these):** Vercel Blob as recommended #1; Supabase Storage; S3 / R2 / B2 / `MEDIA_S3_*`; “no media storage�?/ disable uploads.
49
49
 
50
50
  ---
51
51
 
52
- ## Q3 Brand (paste this entire block)
52
+ ## Q3 �?Brand (paste this entire block)
53
53
 
54
54
  Let’s brand your TomorrowOS experience. Choose **one**:
55
55
 
56
- **Option A Website URL (recommended if you have a site)**
56
+ **Option A �?Website URL (recommended if you have a site)**
57
57
  Paste **one public website URL**. I will use it **only** to fill **`brand.json`**: product name, tagline, primary / secondary / **background** / text colours, fonts, and logo.
58
- I will **not** rebuild that website or add login unless you explicitly ask for login/auth later.
58
+ I will **not** rebuild that website or add login �?unless you explicitly ask for login/auth later.
59
59
 
60
- **Option B Manual brand fields**
60
+ **Option B �?Manual brand fields**
61
61
  Send as many of these as you can (name + at least one colour + logo is ideal):
62
62
  1. **Product / venue name** (required)
63
63
  2. **Tagline** (optional)
64
64
  3. **Primary colour** (hex, e.g. `#FF8A3D`)
65
- 4. **Background colour** (hex page / panel background)
66
- 5. **Text colour** (hex, optional default `#0A0908`)
65
+ 4. **Background colour** (hex �?page / panel background)
66
+ 5. **Text colour** (hex, optional �?default `#0A0908`)
67
67
  6. **Secondary / accent colour** (hex, optional)
68
- 7. **Logo** upload SVG/PNG into the project, or a public image URL I can fetch into `./assets/`
68
+ 7. **Logo** �?upload SVG/PNG into the project, or a public image URL I can fetch into `./assets/`
69
69
 
70
70
  Reply with a URL **or** the manual fields. Do not send only a name with no colours/logo unless you want defaults (I will confirm defaults before writing).