@tomorrowos/sdk 1.1.0 → 1.1.1

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
@@ -78,7 +78,7 @@ Replit Agent must ask **only** the questions defined in **this file**, in **this
78
78
  | Q1 + Supabase | Database choice ?**immediately** one Secrets input for `SUPABASE_URL` (still Question 1) |
79
79
  | Q2 + Cloudinary | Media choice ?**immediately** one multi-field Secrets dialog for all `CLOUDINARY_*` keys (still Question 2) |
80
80
  | Q3 branding | **One** multi-field dialog with **all seven** branding inputs stacked (name, tagline, primary, background, text, secondary, logo) |
81
- | Q4 CMS password | Optional single Secrets input for `CMS_PASSWORD` (blank/skip allowed; can set later) |
81
+ | Q4 CMS password | **Immediately after Q3** ? one Secrets input for `CMS_PASSWORD` (blank/skip allowed). **Never** ask a yes/no "do you want a password?" first. |
82
82
 
83
83
  **Critical ?Question 1 option order (exact):** Present database choices in **this order only**:
84
84
  1. **Supabase Postgres (Recommended)**
@@ -88,13 +88,15 @@ Replit Agent must ask **only** the questions defined in **this file**, in **this
88
88
 
89
89
  **Critical:** Question 2 is **not complete** when the user says Cloudinary?or **1**. You **must immediately** open the **multi-field** Cloudinary Secrets dialog (same Question 2 ?**not** ?b? **not** one key per message) and collect `CLOUDINARY_CLOUD_NAME`, `CLOUDINARY_API_KEY`, and `CLOUDINARY_API_SECRET` **before** Question 3.
90
90
 
91
- **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.
91
+ **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.
92
+
93
+ **Critical ? Question 4 (right after Q3, no warm-up):** As soon as Q3 is submitted/skipped, open the **CMS password** Secrets input (or one chat prompt). **Forbidden before Q4:** "Do you want to protect the CMS?", "Add a password?", "Enable auth?", Option A/B for password yes/no. The password field itself is optional ? blank/skip is a complete Q4 answer.
92
94
 
93
95
  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?Q4 are complete (Question 2 includes Cloudinary Secrets when applicable; Q4 may be blank/skip). For Q3, "complete" includes the user skipping or leaving fields blank.
94
96
 
95
97
  If the user says just set it up, dont ask questions,?respond:
96
98
 
97
- > I need a few quick answers from REPLIT_SETUP.md (database choice ? Supabase recommended, then Replit Postgres ? media storage ? plus Cloudinary credentials if you choose Cloudinary ? optional branding ? and optional Control Panel password). It takes about a minute and ensures the CMS deploys correctly on Replit.
99
+ > I need a few quick answers from REPLIT_SETUP.md (database choice ? Supabase recommended, then Replit Postgres ? media storage ? plus Cloudinary credentials if you choose Cloudinary ? optional branding ? and optional CMS password). It takes about a minute and ensures the CMS deploys correctly on Replit.
98
100
 
99
101
  Then ask **Question 1** ?stay on this file.
100
102
 
@@ -107,7 +109,7 @@ Then ask **Question 1** ?stay on this file.
107
109
  2a. **Q1 Supabase:** choice ?**immediately** `SUPABASE_URL` input (same Question 1).
108
110
  2b. **Q2 Cloudinary:** choice ?**immediately** multi-field `CLOUDINARY_*` Secrets dialog (same Question 2).
109
111
  2c. **Q3:** one dialog, **seven inputs stacked** (all optional).
110
- 2d. **Q4:** Optional CMS password. If blank/skip ? omit `CMS_PASSWORD`. If set ? store as Secret `CMS_PASSWORD` (Control Panel only; never enable Replit whole-site password).
112
+ 2d. **Q4:** Right after Q3 ? open the CMS password input immediately. **No** yes/no prelude. Blank/skip ? omit `CMS_PASSWORD`. If set ? Secret `CMS_PASSWORD` (app login only; never Replit whole-site password). Ensure Publish readiness uses **`GET /status`** (see **Publish health check**).
111
113
  3. **Do not invent** Cloudinary credentials, Supabase URLs, or brand colours. **Do not ask** screen / device count.
112
114
  4. **Do not use Replits 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`.
113
115
  4a. **If Supabase is chosen: never collect `SUPABASE_URL` twice.** One capture right after the choice is enough.
@@ -161,7 +163,24 @@ Replit Publish must see:
161
163
  - A Node process started by `npm run start` ?`tsx server.ts`
162
164
  - `host: "0.0.0.0"` and `port: Number(process.env.PORT) || 3000`
163
165
  - An exposed port with `externalPort = 80` in `.replit` (see template below)
164
- - **Autoscale** (or Reserved VM) deployment ?**not** Static-only
166
+ - **Reserved VM** deployment preferred for TomorrowOS (persistent WebSockets) ? Autoscale is a fallback; **not** Static-only
167
+
168
+ ### Publish health check / readiness probe (IRON RULE)
169
+
170
+ Replit Publish readiness must **not** treat a password-gated Control Panel redirect as healthy.
171
+
172
+ **Cause of a common Publish failure:** build OK, then readiness hits `GET /`. With `CMS_PASSWORD` set, older auth wrappers **303 ? `/login`**, and the checker follows redirects until it fails.
173
+
174
+ **Required agent behavior before / during Publish:**
175
+
176
+ 1. Prefer the public JSON endpoint: configure the production **startup / health / readiness probe** to **`GET /status`** (returns HTTP 200, no auth, no redirect).
177
+ 2. Confirm starter `cms-auth` serves unauthenticated panel HTML at `/` with **HTTP 200** (login page ? **no 303 redirect loop**). Do **not** revert to redirecting `/` ? `/login`.
178
+ 3. Verify before telling the user Publish is ready:
179
+ - `GET /status` ? **200** JSON, **no** `Location` redirect
180
+ - `GET /` with `CMS_PASSWORD` set ? **200** (login HTML) or authenticated panel ? **not** a redirect chain
181
+ 4. In Publishing, choose **Reserved VM** (preferred for WebSocket pairing). Autoscale may drop long-lived sockets; Static is forbidden.
182
+
183
+ If Publish fails with redirect / readiness errors after setting `CMS_PASSWORD`: switch the probe to **`/status`**, confirm `cms-auth` uses 200-at-`/` (not 303), then republish.
165
184
 
166
185
  ### `.replit` (create or replace ?required for publish)
167
186
 
@@ -189,10 +208,11 @@ ignorePorts = false
189
208
  **Agent rules for `.replit`:**
190
209
 
191
210
  - Set `entrypoint = "server.ts"` (not `index.js` unless that file exists and starts TomorrowOS).
192
- - Set `deploymentTarget = "autoscale"` (WebSocket + long-lived connections; Static Deploy is wrong).
211
+ - Prefer **Reserved VM** in the Publishing UI for TomorrowOS WebSockets. Keep `deploymentTarget = "autoscale"` in `.replit` only if that is what the workspace template requires ? still choose **Reserved VM** when the Publish dialog offers it (see Replit Publishing pane).
193
212
  - Keep exactly **one** primary `[[ports]]` entry with `externalPort = 80` for the CMS server.
194
213
  - Ensure `.replit-artifact/artifact.toml` has **`kind = "web"`** (shipped in cms-starter). If `kind = "api"`, change to **`web`** before Publish.
195
- - If `.replit` contains `[[artifacts]]` with **only** `artifacts/api-server` and Publish fails, set artifact **`kind = "web"`** or create a **new Web app** Repl and migrate ?do not tell the user publish succeeded.
214
+ - If `.replit` contains `[[artifacts]]` with **only** `artifacts/api-server` and Publish fails, set artifact **`kind = "web"`** or create a **new Web app** Repl and migrate ? do not tell the user publish succeeded.
215
+ - **Always** set Publish readiness / health probe to **`/status`** when the UI allows (see **Publish health check**). Never leave the probe on `/` if it follows auth redirects.
196
216
 
197
217
  If Replit assigns a different `localPort` via `PORT`, update `localPort` to match the port in `[TomorrowOS] listening on http://0.0.0.0:<PORT>` logs, but keep `externalPort = 80`.
198
218
 
@@ -201,9 +221,10 @@ If Replit assigns a different `localPort` via `PORT`, update `localPort` to matc
201
221
  Before Publish:
202
222
 
203
223
  1. Confirm project filter / type shows **Web** (not API-only).
204
- 2. In Publishing, choose **Autoscale** (recommended) or **Reserved VM** ?**not Static**.
205
- 3. Stop stale processes; clear port conflicts; restart workspace if needed.
206
- 4. `npm run start` ?Preview at `/` shows Control Panel.
224
+ 2. In Publishing, choose **Reserved VM** (preferred for WebSockets) or Autoscale ? **not Static**.
225
+ 3. Set readiness / health check path to **`/status`** when the UI exposes it (mandatory if `CMS_PASSWORD` is set).
226
+ 4. Stop stale processes; clear port conflicts; restart workspace if needed.
227
+ 5. `npm run start` ? Preview at `/` shows Control Panel (or CMS login HTML with HTTP 200 if password set).
207
228
 
208
229
  After Publish, the public URL is:
209
230
 
@@ -706,14 +727,16 @@ Do **not** ask for screen count. Keep a default `cms.expectedScreens` (e.g. `5`)
706
727
 
707
728
  ## Question 4 ? Optional CMS password (`CMS_PASSWORD`)
708
729
 
709
- > **Optional.** Protect the Control Panel with app-level auth. You may **skip** and set Secret `CMS_PASSWORD` later (then Redeploy / restart).
730
+ > **Optional.** Protect the CMS with app-level auth. You may **skip** and set Secret `CMS_PASSWORD` later (then Redeploy / restart).
731
+ >
732
+ > **IRON RULE ? no prelude:** After Q3 finishes, ask **this** question next. Do **not** first ask whether they want a password / auth / lock. Open the password field (or one chat prompt) immediately; blank/skip is valid.
710
733
 
711
- **Ask (Secrets input or one chat reply):**
734
+ **Ask (Secrets input or one chat reply) ? copy this intent:**
712
735
 
713
- > Optional: set a Control Panel password now? If yes, enter the password to store as Secret **`CMS_PASSWORD`**. Leave blank / skip to keep the panel open for now ? you can add this Secret later and redeploy.
736
+ > Optional: set a CMS password now? If yes, enter the password to store as Secret **`CMS_PASSWORD`**. Leave blank / skip to keep the CMS open for now ? you can add this Secret later and redeploy.
714
737
 
715
738
  **Why:**
716
- - Locks the **CMS Control Panel only** (not the whole Repl).
739
+ - Locks the **CMS Control Panel** only (not the whole Repl).
717
740
  - Devices still reach WebSocket / brand / uploads without that password.
718
741
  - Safer than Replit **whole-site** password protection (that breaks Tizen/BrightSign).
719
742
  - Leaving it blank is fine; add `CMS_PASSWORD` later when you want the login page.
@@ -725,6 +748,9 @@ Do **not** ask for screen count. Keep a default `cms.expectedScreens` (e.g. `5`)
725
748
  **Reject / clarify:**
726
749
  - Do **not** enable Replit **site-wide** password / auth gate for this project.
727
750
  - Do **not** put the password in committed source (git, README, pasted into files).
751
+ - Do **not** ask a separate "enable password protection?" question before this step.
752
+
753
+ **Publish note (mandatory when `CMS_PASSWORD` is set or may be set):** See **Publish health check / readiness probe** ? probe **`/status`**, never rely on `/` redirecting. Starter `cms-auth` serves login at `/` with **HTTP 200** (no redirect loop); still configure the probe to `/status`.
728
754
 
729
755
  ---
730
756
 
@@ -807,6 +833,8 @@ Link an App Storage bucket in the Replit UI. Do **not** set Cloudinary Secrets f
807
833
  - If Q4 set a password: create Replit Secret `CMS_PASSWORD` (never commit it).
808
834
  - If Q4 blank/skipped: omit it ? panel stays open until they add the Secret later and Redeploy / restart.
809
835
  - Do **not** enable Replit whole-site password protection.
836
+ - **Before Publish:** set readiness probe to **`/status`**. Confirm `GET /status` is 200 with no redirects. Do **not** leave Publish probing `/` if auth redirects would break readiness (starter serves login at `/` with 200; still prefer `/status`).
837
+ - Prefer **Reserved VM** for published WebSocket pairing.
810
838
 
811
839
  ### C. Wire `server.ts`
812
840
 
@@ -921,7 +949,9 @@ Do **not**:
921
949
  - Omit `[[ports]]` / `deploymentTarget` from `.replit` when setting up for Publish
922
950
  - Run **database connection tests**, **WebSocket console tests**, or other post-setup smoke tests unless Preview fails or the user asks
923
951
  - **Debug or fix?Supabase** because dev logs show `getaddrinfo ENOTFOUND` or `database connection failure` ?save config and finish setup instead
924
- - Ask how many screens / devices (? vs >5)
952
+ - Ask a yes/no "do you want a CMS password / enable auth?" **before** Q4 ? after Q3 go **straight** to the CMS password field (blank/skip allowed)
953
+ - Leave Publish readiness probing `/` when `CMS_PASSWORD` is set if that path 303-redirects (use **`/status`**; serve login at `/` with **200**)
954
+ - Enable Replit **whole-site** password instead of `CMS_PASSWORD`
925
955
 
926
956
  ---
927
957
 
@@ -930,7 +960,8 @@ Do **not**:
930
960
  | Symptom | Likely cause | Fix |
931
961
  |---------|--------------|-----|
932
962
  | Agent installs older SDK for supply-chain safety window?| Agent age-gated `@latest` | **Override:** `npm install @tomorrowos/sdk@latest` in Shell until local version = `npm view @tomorrowos/sdk version` |
933
- | Publish: **api-kind artifact not deployable** | CMS artifact `kind = "api"` or API-only Repl | Set **`kind = "web"`** in `.replit-artifact/artifact.toml`; new **Web app** Repl if needed; `.replit` ports + `deploymentTarget=autoscale` |
963
+ | Publish fails after build: too many redirects / readiness on `/` | Health probe hit password-gated `/` (303 ? `/login` loop) | Set probe to **`GET /status`**; ensure `cms-auth` returns **200** login HTML at `/` (no 303); prefer **Reserved VM**; republish |
964
+ | Publish: **api-kind artifact not deployable** | CMS artifact `kind = "api"` or API-only Repl | Set **`kind = "web"`** in `.replit-artifact/artifact.toml`; new **Web app** Repl if needed; `.replit` ports + Reserved VM / autoscale |
934
965
  | Preview empty / wrong app at `/` | Project type API, wrong staticRoot, or stale process | Set project to **Web app**; confirm `staticRoot: public`; stop old servers; restart workspace |
935
966
  | TV / browser: WebSocket handshake timeout | Node CMS not running or crash-loop | Fix Run ?`npm run start`; check logs for `listening` |
936
967
  | Deploy/Publish fails / `tsx: not found` | `tsx` only in devDependencies | Move `tsx` to `dependencies`, reinstall, keep `start`: `tsx server.ts` |
@@ -946,7 +977,9 @@ Do **not**:
946
977
 
947
978
  ## Protocol version
948
979
 
949
- `replit-setup/1.17` - aligned with TomorrowOS protocol `1.0` and `@tomorrowos/sdk` store drivers `sqlite` | `supabase` | `postgres` | `memory`.
980
+ `replit-setup/1.18` - aligned with TomorrowOS protocol `1.0` and `@tomorrowos/sdk` store drivers `sqlite` | `supabase` | `postgres` | `memory`.
981
+
982
+ **Changelog 1.18:** Q4 is asked **immediately after Q3** with no yes/no password prelude. Publish readiness must use **`GET /status`** (and `cms-auth` serves login at `/` with HTTP 200 ? no redirect loop). Prefer **Reserved VM** for WebSockets.
950
983
 
951
984
  **Changelog 1.17:** Question 4 is optional Control Panel Secret `CMS_PASSWORD` (blank/skip allowed; can set later). Do not use Replit whole-site password. Devices stay reachable without it.
952
985
 
@@ -94,7 +94,7 @@ Blank fields -> keep existing `brand.json`. Logo blank -> `./assets/logo.svg`.
94
94
 
95
95
  ## Q4 - Optional CMS password (paste this entire block)
96
96
 
97
- Optional: set a Control Panel password now?
97
+ Optional: set a CMS password now?
98
98
 
99
99
  If yes, reply with the password to store as Environment Variable **`CMS_PASSWORD`**.
100
100
 
package/VERCEL_SETUP.md CHANGED
@@ -195,7 +195,7 @@ If the user **skips** a Question 2 option, do **not** invent a substitute questi
195
195
 
196
196
  If the user says ?just set it up, don?t ask questions,??respond:
197
197
 
198
- > I need a few quick answers from VERCEL_SETUP.md (database — Supabase or Neon preferred — media storage with Cloudinary recommended, branding, and optional Control Panel password). It takes about a minute and ensures the CMS works on Vercel.
198
+ > I need a few quick answers from VERCEL_SETUP.md (database — Supabase or Neon preferred — media storage with Cloudinary recommended, branding, and optional CMS password). It takes about a minute and ensures the CMS works on Vercel.
199
199
 
200
200
  Then ask **Question 1**.
201
201
 
@@ -856,7 +856,7 @@ Set `cmsEndpoint` only if they already know the public `https://?vercel.app` URL
856
856
 
857
857
  **Ask (Env input or one chat reply):**
858
858
 
859
- > Optional: set a Control Panel password now? If yes, enter the password to store as Environment Variable **`CMS_PASSWORD`**. Leave blank / skip to keep the panel open for now — you can add this later in Vercel → Settings → Environment Variables and redeploy.
859
+ > Optional: set a CMS password now? If yes, enter the password to store as Environment Variable **`CMS_PASSWORD`**. Leave blank / skip to keep the panel open for now — you can add this later in Vercel → Settings → Environment Variables and redeploy.
860
860
 
861
861
  **Why:**
862
862
  - Locks the **CMS Control Panel only** (not the whole deployment).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tomorrowos/sdk",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Server SDK for building and self-hosting digital signage CMS products with TomorrowOS players.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -7,6 +7,7 @@ run = "npm run start"
7
7
  channel = "stable-24_11"
8
8
 
9
9
  # Expose the CMS HTTP + WebSocket server (TomorrowOS Control Panel at /).
10
+ # Publish readiness: probe GET /status (public). Do not rely on auth redirects from /.
10
11
  [[ports]]
11
12
  localPort = 3000
12
13
  externalPort = 80
@@ -15,3 +16,5 @@ externalPort = 80
15
16
  run = ["sh", "-c", "npm run start"]
16
17
  deploymentTarget = "autoscale"
17
18
  ignorePorts = false
19
+ # Prefer Reserved VM in the Publishing UI for persistent WebSockets.
20
+ # If the UI allows a health/readiness path, set it to /status.
@@ -200,12 +200,16 @@ export async function handleCmsAuth(
200
200
 
201
201
  if (isDevicePublicPath(method, pathname)) return false;
202
202
 
203
- if (method === "GET" && pathname === "/login") {
203
+ if ((method === "GET" || method === "HEAD") && pathname === "/login") {
204
204
  res.writeHead(200, {
205
205
  "Content-Type": "text/html; charset=utf-8",
206
206
  "Cache-Control": "no-store"
207
207
  });
208
- res.end(loginPageHtml());
208
+ if (method === "HEAD") {
209
+ res.end();
210
+ } else {
211
+ res.end(loginPageHtml());
212
+ }
209
213
  return true;
210
214
  }
211
215
 
@@ -244,16 +248,25 @@ export async function handleCmsAuth(
244
248
  const cookies = parseCookies(req);
245
249
  if (verifySession(password, cookies[COOKIE_NAME])) return false;
246
250
 
247
- if (wantsHtml(req) && method === "GET") {
248
- res.writeHead(303, { Location: "/login" });
249
- res.end();
251
+ // Serve login with HTTP 200 (no 303). Replit Publish readiness probes `/` and
252
+ // fails if it follows a redirect chain to /login.
253
+ if (wantsHtml(req) && (method === "GET" || method === "HEAD")) {
254
+ res.writeHead(200, {
255
+ "Content-Type": "text/html; charset=utf-8",
256
+ "Cache-Control": "no-store"
257
+ });
258
+ if (method === "HEAD") {
259
+ res.end();
260
+ } else {
261
+ res.end(loginPageHtml());
262
+ }
250
263
  return true;
251
264
  }
252
265
 
253
266
  sendJson(res, 401, {
254
267
  status: "failed",
255
268
  error:
256
- "Control Panel authentication required. Sign in at /login or set cookie after POST /login."
269
+ "CMS authentication required. Sign in at /login or set cookie after POST /login."
257
270
  });
258
271
  return true;
259
272
  }