@tomorrowos/sdk 0.9.41 → 0.9.43

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
@@ -14,9 +14,10 @@ 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
15
  Artifact kind must be **web** (never **api**).
16
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
+ If Supabase: show the SUPABASE_URL Secrets input immediately after the choice (same Question 1 — once only).
18
+ If Cloudinary: show all CLOUDINARY_* Secrets inputs immediately after the choice (same Question 2 — one multi-field dialog).
19
+ Question 3: one multi-field dialog with all seven branding inputs stacked (all optional; blanks keep brand.json defaults). Never ask branding fields one-by-one with Next.
20
+ After all setup questions are answered, 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.
20
21
  Do not skip steps.
21
22
  ```
22
23
 
@@ -67,16 +68,24 @@ Replit Agent must ask **only** the questions defined in **this file**, in **this
67
68
 
68
69
  **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.
69
70
 
71
+ **Critical — UI shape (Replit multi-input dialogs):** Prefer Replit’s **Secrets / form UI with multiple input fields in one dialog**. Do **not** run a chat wizard that asks one field, waits, then “Next” for the next field.
72
+
73
+ | Step | Required UI |
74
+ |------|-------------|
75
+ | Q1 + Supabase | Database choice → **immediately** one Secrets input for `SUPABASE_URL` (still Question 1) |
76
+ | Q2 + Cloudinary | Media choice → **immediately** one multi-field Secrets dialog for all `CLOUDINARY_*` keys (still Question 2) |
77
+ | Q3 branding | **One** multi-field dialog with **all seven** branding inputs stacked (name, tagline, primary, background, text, secondary, logo) |
78
+
70
79
  **Critical — Question 1 option order (exact):** Present database choices in **this order only**:
71
80
  1. **Supabase Postgres (Recommended)**
72
81
  2. **Built-in Replit PostgreSQL**
73
82
  3. **SQLite (local file — fine for testing)**
74
83
 
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”).
84
+ **Critical — Supabase path collected once only:** If the user picks Supabase, collect `SUPABASE_URL` **immediately after the choice**, in **one** step (Secrets UI **or** a single chat paste — not both). After it is saved, **never** ask for the connection string / `SUPABASE_URL` again.
76
85
 
77
- **Critical:** Question 2 is **not complete** when the user says “Cloudinary” or “yes”. You **must** immediately show the Cloudinary Secrets input (same stepdo **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.
86
+ **Critical:** Question 2 is **not complete** when the user says “Cloudinary” or “yes”. You **must immediately** open the **multi-field** Cloudinary Secrets dialog (same Question 2 **not** “2b”, **not** one key per message) and collect `CLOUDINARY_CLOUD_NAME`, `CLOUDINARY_API_KEY`, and `CLOUDINARY_API_SECRET` **before** Question 3.
78
87
 
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.
88
+ **Critical — Question 3:** All seven branding fields are **optional** and must appear in **one stacked multi-input dialog**. Empty / skip / “use defaults” → keep starter **`brand.json`**. **Never** ask name, then colours, then logo across separate turns.
80
89
 
81
90
  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.
82
91
 
@@ -91,10 +100,13 @@ Then ask **Question 1** — do not switch to `LLM_PROMPT.md`.
91
100
  ## Hard rules
92
101
 
93
102
  1. **Ask only REPLIT_SETUP.md questions 1–3** (see **Questionnaire scope**). Never use `LLM_PROMPT.md` on Replit.
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 2bstep or announce a new question number.
103
+ 2. **One questionnaire step at a time (Q1 → Q2 → Q3).** Wait for that step to finish before opening the next. **Inside** a step, use **one multi-field dialog** when collecting Secrets or branding — **never** field-by-fieldNextwizards.
104
+ 2a. **Q1 Supabase:** choice → **immediately** `SUPABASE_URL` input (same Question 1).
105
+ 2b. **Q2 Cloudinary:** choice → **immediately** multi-field `CLOUDINARY_*` Secrets dialog (same Question 2).
106
+ 2c. **Q3:** one dialog, **seven inputs stacked** (all optional).
95
107
  3. **Do not invent** Cloudinary credentials, Supabase URLs, or brand colours. **Do not ask** screen / device count.
96
108
  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.
109
+ 4a. **If Supabase is chosen: never collect `SUPABASE_URL` twice.** One capture right after the choice is enough.
98
110
  4b. **Question 3 fields are all optional.** Missing values → keep starter `brand.json` defaults; do not block or re-prompt.
99
111
  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.
100
112
  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.
@@ -108,7 +120,7 @@ Then ask **Question 1** — do not switch to `LLM_PROMPT.md`.
108
120
 
109
121
  - Q1 → Q2 → Q3 → execution checklist
110
122
 
111
- Never insert extra questions between these steps. When the user chooses Cloudinary, **Question 2 continues** with the Secrets form immediately then proceed to Q3 only after secrets are saved.
123
+ Never insert extra questions between these steps. When the user chooses Supabase, **Question 1 continues** with the `SUPABASE_URL` Secrets input **immediately**. When the user chooses Cloudinary, **Question 2 continues** with the multi-field Cloudinary Secrets dialog **immediately**.
112
124
 
113
125
  ---
114
126
 
@@ -418,21 +430,24 @@ Present **exactly these three options, in this order** (Supabase first / recomme
418
430
 
419
431
  ### Step B1 — If **1 Supabase (Recommended)**
420
432
 
433
+ **Immediately after the user selects Supabase** (same Question 1 — do **not** go to Q2 first, do **not** announce “next question”): open the Secrets / connection-string input.
434
+
421
435
  Collect `SUPABASE_URL` **exactly once** (IRON RULE).
422
436
 
423
437
  | Allowed (pick **one**) | Forbidden |
424
438
  |------------------------|-----------|
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 |
439
+ | **Immediately** open Replit **Secrets** UI for `SUPABASE_URL` after the choice | Waiting until later / Q2 / execution to ask for the URI |
426
440
  | Save Secret once, then move on | Opening Secrets **and** also asking them to paste the same URI in chat |
427
441
  | Reuse the saved Secret in execution | Asking again in Q2 / Q3 / “confirm your Supabase Secret” / Publish / checklist |
442
+ | | Asking “Do you still want to add SUPABASE_URL?” as a separate step after they already chose Supabase |
428
443
 
429
444
  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
445
 
431
- **Preferred on Replit:** open an **input / Secrets** UI for:
446
+ **Preferred on Replit (same Question 1 turn after choice):** open an **input / Secrets** UI for:
432
447
 
433
448
  - `SUPABASE_URL`
434
449
 
435
- **Only if** the Secrets UI is unavailable, ask exactly in chat. **Do not** also open a second Secrets prompt for the same value:
450
+ **Only if** the Secrets UI is unavailable, ask exactly in chat **in the very next message after the choice**. **Do not** also open a second Secrets prompt for the same value:
436
451
 
437
452
  > 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).
438
453
  >
@@ -520,26 +535,32 @@ tomorrowos.listen({
520
535
  >
521
536
  > Do you want me to set up **Cloudinary**? (yes / no)
522
537
 
523
- ### Step B — If YES / Cloudinary (same Question 2 — no “2b”, no extra question)
538
+ ### Step B — If YES / Cloudinary (same Question 2 — immediate multi-field Secrets dialog)
539
+
540
+ **Do this in the same Question 2 flow, immediately when the user chooses Cloudinary.**
524
541
 
525
- **Do this immediately** when the user chooses Cloudinary. **Do not** say “Question 2b”, “next step”, or “Great — I’ll use Cloudinary” as a separate message before the form. **Go straight to the Secrets input.**
542
+ - Do **not** say “Question 2b”, “next step”, or “Great — I’ll use Cloudinary” as a separate message before the form
543
+ - ❌ Do **not** ask for cloud name, then API key, then secret across three turns
544
+ - ✅ **Go straight to one Secrets / form dialog with multiple inputs stacked**
526
545
 
527
- **Preferred on Replit:** open the **Replit Secrets** UI with fields for:
546
+ **Preferred on Replit:** open the **Replit Secrets** UI **immediately** with **all** of these fields in **one** dialog:
528
547
 
529
548
  - `CLOUDINARY_CLOUD_NAME`
530
549
  - `CLOUDINARY_API_KEY`
531
550
  - `CLOUDINARY_API_SECRET`
532
551
  - Optional: `CLOUDINARY_FOLDER` (e.g. `tomorrowos`)
533
552
 
534
- **If Secrets UI is unavailable, ask exactly (one message with all fields):**
553
+ **If Secrets UI is unavailable, ask exactly once (one message all fields listed together; user may paste all in one reply):**
535
554
 
536
- > Paste your Cloudinary credentials (from [cloudinary.com/console](https://cloudinary.com/console) → Dashboard → **API Keys**). I will store them as Replit **Secrets**:
555
+ > Paste your Cloudinary credentials (from [cloudinary.com/console](https://cloudinary.com/console) → Dashboard → **API Keys**). I will store them as Replit **Secrets** in one go:
537
556
  >
538
557
  > 1. **`CLOUDINARY_CLOUD_NAME`**
539
558
  > 2. **`CLOUDINARY_API_KEY`**
540
559
  > 3. **`CLOUDINARY_API_SECRET`**
541
560
  >
542
561
  > Optional: **`CLOUDINARY_FOLDER`**
562
+ >
563
+ > You can send all values in a single reply.
543
564
 
544
565
  **You must then:**
545
566
 
@@ -564,36 +585,60 @@ The SDK auto-detects these env vars and uploads media to Cloudinary. New uploads
564
585
 
565
586
  ---
566
587
 
567
- ## Question 3 — Brand / TomorrowOS app look (`brand.json` only; **all fields optional**)
588
+ ## Question 3 — Brand / TomorrowOS app look (`brand.json` only; **one multi-field dialog**)
568
589
 
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.
590
+ > **Scope:** Question 3 answers **only** update **`brand.json`**.
591
+ > **Question 3 does NOT affect:** Replit Publish, artifact `kind`, `.replit`, `server.ts`, Secrets, Supabase, Cloudinary, ports, deployment target, or CMS server wiring.
592
+ >
593
+ > **IRON RULE — one dialog, seven stacked inputs:** Present **all seven** branding fields in a **single** Replit form / multi-input dialog (or one chat message that lists all seven for one reply). The user fills what they want and submits **once**.
570
594
  >
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.
595
+ > **Forbidden:** asking product name wait Next tagline Next primary colour
596
+ > ❌ **Forbidden:** splitting branding into multiple questionnaire steps
597
+ > ✅ **Required:** one stacked form with every field visible together
572
598
  >
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.
599
+ > **Every field is optional.** Blank / skip / “use defaults keep starter `brand.json`. **Do not** re-ask for missing fields.
600
+
601
+ > **This is the only branding block.** Do not ask separate LLM_PROMPT “target platform”, “use case”, or “hosting” questions. **Do not** ask screen count.
574
602
 
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.
603
+ ### Preferred UI (Replit form / multi-input dialog)
576
604
 
577
- **Ask exactly (one message; user may answer in one reply, partially, or skip):**
605
+ Open **one** dialog titled e.g. **Brand your TomorrowOS experience** with these **seven inputs stacked in this order** (labels may match exactly):
578
606
 
579
- > Let’s brand your TomorrowOS experience (**all optional**reply “skip” or leave blank to keep the starter `brand.json` defaults):
607
+ 1. **Product / venue name** (optional — default from `brand.json`, e.g. `My Venue`)
608
+ 2. **Tagline** (optional — default e.g. `Digital signage`)
609
+ 3. **Primary colour** (optional hex — default e.g. `#FF8A3D`)
610
+ 4. **Background colour** (optional — default e.g. `#FAFAF9`)
611
+ 5. **Text colour** (optional — default e.g. `#0A0908`)
612
+ 6. **Secondary / accent colour** (optional — default e.g. `#F5F3EF`)
613
+ 7. **Logo** (optional) — upload SVG/PNG or paste a URL (otherwise keep `./assets/logo.svg`)
614
+
615
+ Helper text on the dialog: *All fields optional. Leave blank to keep starter brand.json defaults. Submit once.*
616
+
617
+ ### If a form UI is unavailable — one chat message only
618
+
619
+ Ask **exactly once** (user may answer all fields in a single reply, partially, or “skip”):
620
+
621
+ > Let’s brand your TomorrowOS experience (**all optional — one reply**). Leave any field blank to keep starter `brand.json` defaults:
622
+ >
623
+ > 1. **Product / venue name**
624
+ > 2. **Tagline**
625
+ > 3. **Primary colour** (hex)
626
+ > 4. **Background colour** (hex)
627
+ > 5. **Text colour** (hex)
628
+ > 6. **Secondary / accent colour** (hex)
629
+ > 7. **Logo** (upload or URL)
580
630
  >
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`)
631
+ > Reply with whatever you have in one message, or say **skip**.
588
632
 
589
633
  **Defaults rule (mandatory):**
590
634
 
591
635
  1. Start from the current project **`brand.json`** (after `init`, this matches the SDK starter).
592
- 2. Overlay **only** fields the user actually provided.
636
+ 2. Overlay **only** fields the user actually provided in that single submission.
593
637
  3. If the user gives **no** product name → keep existing `"name"`.
594
638
  4. If the user gives **no** colours → keep existing `primaryColor` / `backgroundColor` / `textColor` / `secondaryColor`.
595
639
  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
640
  6. **Never** re-ask for missing branding fields. **Never** treat blank name/colour as an error.
641
+ 7. **Never** follow up with “What about primary colour?” after they already submitted the branding dialog.
597
642
 
598
643
  **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.
599
644
 
@@ -753,6 +798,9 @@ Do **not**:
753
798
  - Change the production start command away from `tsx server.ts` / `npm run start`
754
799
  - Point Supabase at Replit’s reserved `DATABASE_URL` when `SUPABASE_URL` should be used
755
800
  - List Q1 databases with **Replit PostgreSQL first** or mark it recommended instead of Supabase
801
+ - Delay `SUPABASE_URL` until after Q1 is “done” or until Q2 — it must follow the Supabase choice **immediately**
802
+ - Collect Cloudinary keys one-at-a-time with Next / separate turns — must be **one multi-field dialog** right after Cloudinary choice
803
+ - Ask Q3 branding fields one-by-one (name → Next → colour → …) — must be **one stacked seven-field dialog**
756
804
  - **Ask for `SUPABASE_URL` / the Postgres connection string more than once** (Secrets UI + chat, or again during execution / Publish)
757
805
  - Ask the user to type `TOMORROWOS_STORE` or `DATABASE_SSL`
758
806
  - Skip Cloudinary recommendation without stating the persistence tradeoff
@@ -810,7 +858,9 @@ TOMORROWOS_STORE=sqlite
810
858
 
811
859
  ## Protocol version
812
860
 
813
- `replit-setup/1.10` — aligned with TomorrowOS protocol `1.0` and `@tomorrowos/sdk` store drivers `sqlite` | `supabase` | `postgres` | `memory`.
861
+ `replit-setup/1.11` — aligned with TomorrowOS protocol `1.0` and `@tomorrowos/sdk` store drivers `sqlite` | `supabase` | `postgres` | `memory`.
862
+
863
+ **Changelog 1.11:** Q1 Supabase → **immediate** `SUPABASE_URL` Secrets input; Q2 Cloudinary → **immediate multi-field** `CLOUDINARY_*` dialog; Q3 = **one stacked dialog with all seven** branding inputs (no field-by-field Next).
814
864
 
815
865
  **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
866
 
@@ -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.41+)
3
+ > **Protocol:** `vercel-questions/1.0` (pairs with `VERCEL_SETUP.md` 1.9+ / `@tomorrowos/sdk` 0.9.43+)
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 鈥淏lob recommended鈥? no 鈥渄isable 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鈥檚 **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 鈥淪upabase鈥?/ 鈥淣eon鈥?/ 鈥淪QLite鈥?.
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 鈥淐loudinary鈥?/ 鈥淰ercel Blob鈥?/ 鈥渓ocal鈥?.
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_*`; 鈥渘o 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
- Let’s brand your TomorrowOS experience. Choose **one**:
54
+ Let鈥檚 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).