@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 +50 -17
- package/VERCEL_QUESTIONS.md +1 -1
- package/VERCEL_SETUP.md +2 -2
- package/package.json +1 -1
- package/templates/cms-starter/.replit +3 -0
- package/templates/cms-starter/cms-auth.ts +19 -6
- package/templates/cms-starter/package-lock.json +2462 -605
- package/templates/cms-starter/package.json +2 -2
- package/templates/cms-starter/tsconfig.json +1 -1
- package/templates/cms-starter-v0/cms-auth.ts +19 -6
- package/templates/cms-starter-v0/package-lock.json +3772 -0
- package/templates/cms-starter-v0/package.json +2 -2
- package/templates/cms-starter-v0/tsconfig.json +1 -1
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 |
|
|
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
|
|
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
|
|
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:**
|
|
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
|
-
- **
|
|
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
|
-
-
|
|
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 **
|
|
205
|
-
3.
|
|
206
|
-
4.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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.
|
|
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
|
|
package/VERCEL_QUESTIONS.md
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
@@ -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
|
-
|
|
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
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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
|
-
"
|
|
269
|
+
"CMS authentication required. Sign in at /login or set cookie after POST /login."
|
|
257
270
|
});
|
|
258
271
|
return true;
|
|
259
272
|
}
|