@tomorrowos/sdk 0.9.18 → 0.9.20
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/README.md +24 -8
- package/VERCEL_SETUP.md +427 -0
- package/dist/store/types.d.ts +0 -1
- package/dist/store/types.d.ts.map +1 -1
- package/dist/tomorrowos.d.ts.map +1 -1
- package/dist/tomorrowos.js +11 -3
- package/package.json +2 -1
- package/templates/cms-starter/package.json +2 -2
- package/templates/cms-starter/public/methods.js +62 -1
- package/templates/cms-starter/public/panel.css +68 -0
package/README.md
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
**You are building a digital signage CMS using TomorrowOS. Before writing any code, follow the elicitation protocol in `LLM_PROMPT.md`. Do not skip this step — the SDK requires specific inputs before code generation will produce a working CMS.**
|
|
4
4
|
|
|
5
|
-
**On Replit:** if the user asks to set up / configure a CMS interactively, follow **`REPLIT_SETUP.md`** instead (Q&A for
|
|
5
|
+
**On Replit:** if the user asks to set up / configure a CMS interactively, follow **`REPLIT_SETUP.md`** instead (Q&A for store → media → `brand.json`).
|
|
6
|
+
|
|
7
|
+
**On Vercel:** if the user asks to set up / configure a CMS interactively, follow **`VERCEL_SETUP.md`** (Session pooler Supabase URL, Fluid compute + WebSocket, Cloudinary, `cms.hostingTarget: "vercel"`).
|
|
6
8
|
|
|
7
9
|
TomorrowOS is the open standard for digital signage. This SDK lets you build a CMS that controls Samsung Tizen, BrightSign, LG webOS, Android, ChromeOS, and Windows screens through a single unified API.
|
|
8
10
|
|
|
@@ -13,13 +15,14 @@ TomorrowOS is the open standard for digital signage. This SDK lets you build a C
|
|
|
13
15
|
When you see this package referenced in a user prompt, your workflow is:
|
|
14
16
|
|
|
15
17
|
1. **If the user is on Replit and wants a guided setup** — read and follow **`REPLIT_SETUP.md`** (preferred for Replit Agent)
|
|
16
|
-
2. **If the user is on
|
|
17
|
-
3. **
|
|
18
|
-
4. **
|
|
19
|
-
5. **Read `
|
|
20
|
-
6.
|
|
21
|
-
7.
|
|
22
|
-
8. **
|
|
18
|
+
2. **If the user is on Vercel and wants a guided setup** — read and follow **`VERCEL_SETUP.md`** (preferred for Vercel Agent)
|
|
19
|
+
3. **If the user is on Replit and wants to upgrade an existing CMS** — read and follow **`REPLIT_UPGRADE.md`** (never `init --force`)
|
|
20
|
+
4. **Otherwise read `LLM_PROMPT.md`** — five required questions before generating code
|
|
21
|
+
5. **Read `BUILD_GUARDRAILS.md`** — mandatory components every generated CMS must include
|
|
22
|
+
6. **Read `brand.schema.json`** — validated shape of `brand.json`
|
|
23
|
+
7. Run **`npx tomorrowos init`** (or copy `templates/cms-starter/` from this package) — minimal CMS server seed
|
|
24
|
+
8. **Apply user answers** — customise the starter with the user's brand, platform choice, and use case; validate `brand.json` against `brand.schema.json`
|
|
25
|
+
9. **Show `PLAYER_INSTALL.md`** — after the CMS is working, show how to build and install the player on a screen
|
|
23
26
|
|
|
24
27
|
You must not write WebSocket transport, pairing wire format, or platform-specific player bridge code in the CMS — the SDK handles server-side device sessions and HTTP helpers. You customise UI, branding, and how you call the SDK.
|
|
25
28
|
|
|
@@ -59,6 +62,18 @@ That wizard configures Supabase, prefers Cloudinary for media, and writes
|
|
|
59
62
|
`brand.json` from your answers. On Replit, Supabase must use Secret
|
|
60
63
|
**`SUPABASE_URL`** (not the reserved `DATABASE_URL`).
|
|
61
64
|
|
|
65
|
+
### Vercel guided setup
|
|
66
|
+
|
|
67
|
+
In Vercel Agent (or any AI setup prompt), paste:
|
|
68
|
+
|
|
69
|
+
```text
|
|
70
|
+
Follow @tomorrowos/sdk VERCEL_SETUP.md and set up my TomorrowOS CMS.
|
|
71
|
+
IF any step contradicts Vercel, adapt to settings that Vercel can accept.
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
That wizard requires the Supabase **Session pooler** URL, prefers Cloudinary,
|
|
75
|
+
enables Fluid compute for WebSockets, and sets `cms.hostingTarget` to `"vercel"`.
|
|
76
|
+
|
|
62
77
|
### Replit upgrade (existing CMS)
|
|
63
78
|
|
|
64
79
|
In Replit Agent, paste:
|
|
@@ -153,6 +168,7 @@ See `PLAYER_INSTALL.md` for installation notes.
|
|
|
153
168
|
|------|---------|
|
|
154
169
|
| `LLM_PROMPT.md` | Five-question elicitation for LLMs |
|
|
155
170
|
| `REPLIT_SETUP.md` | Replit Agent guided CMS setup |
|
|
171
|
+
| `VERCEL_SETUP.md` | Vercel Agent guided CMS setup |
|
|
156
172
|
| `REPLIT_UPGRADE.md` | Replit Agent upgrade to latest SDK (no init) |
|
|
157
173
|
| `BUILD_GUARDRAILS.md` | Mandatory CMS components |
|
|
158
174
|
| `PLAYER_INSTALL.md` | Player build / install / pairing |
|
package/VERCEL_SETUP.md
ADDED
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
# Vercel Setup Protocol for TomorrowOS CMS
|
|
2
|
+
|
|
3
|
+
> **This file is a contract for Vercel Agent / AI coding assistants setting up TomorrowOS on Vercel.**
|
|
4
|
+
> Follow it literally. Ask questions in order. Do not invent secrets. Prefer configuring **Vercel Environment Variables**, `.env` (non-secrets only), `brand.json`, and `server.ts` store wiring over inventing a custom CMS.
|
|
5
|
+
>
|
|
6
|
+
> **On Vercel, this file is the setup questionnaire** (not `REPLIT_SETUP.md`, not `LLM_PROMPT.md`).
|
|
7
|
+
>
|
|
8
|
+
> Protocol id: `vercel-setup/1.0` — pairs with `@tomorrowos/sdk` packages that ship `templates/cms-starter` and this file.
|
|
9
|
+
|
|
10
|
+
## User trigger (copy-paste)
|
|
11
|
+
|
|
12
|
+
When a human pastes a prompt like this, **read this file and start Question 1 immediately**:
|
|
13
|
+
|
|
14
|
+
```text
|
|
15
|
+
Follow @tomorrowos/sdk VERCEL_SETUP.md and set up my TomorrowOS CMS.
|
|
16
|
+
Ask only the questions listed in VERCEL_SETUP.md, in order. Do not use LLM_PROMPT.md or REPLIT_SETUP.md.
|
|
17
|
+
If any step contradicts Vercel, adapt to settings Vercel can accept (document the adaptation).
|
|
18
|
+
After all setup questions are answered (including Cloudinary Env Vars if you chose Cloudinary), configure and deploy — minimal verification only (Control Panel at /). Prefer Supabase Session pooler URL. Do not treat sandbox ENETUNREACH / IPv6 :5432 errors as a hard setup failure when the pooler URL is configured for production.
|
|
19
|
+
Question 3 only updates brand.json. Do not skip steps.
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Alternate triggers:
|
|
23
|
+
|
|
24
|
+
- “Set up TomorrowOS on Vercel using VERCEL_SETUP.md”
|
|
25
|
+
- “Configure my TomorrowOS CMS (Vercel wizard)”
|
|
26
|
+
|
|
27
|
+
When any of these fire, **ignore `REPLIT_SETUP.md` and `LLM_PROMPT.md`** for the Q&A phase (you may skim Replit docs only for shared SDK semantics).
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## IRON RULE: long-lived Node + WebSocket (not classic static / short Functions)
|
|
32
|
+
|
|
33
|
+
> **This is the #1 deployability rule on Vercel. Non-negotiable.**
|
|
34
|
+
|
|
35
|
+
TomorrowOS CMS is **one Node process** that serves:
|
|
36
|
+
|
|
37
|
+
- Browser Control Panel at `GET /` (`public/` via `staticRoot`)
|
|
38
|
+
- Device **WebSocket** on the same origin (`https` → players use `wss`)
|
|
39
|
+
|
|
40
|
+
| Required | Forbidden |
|
|
41
|
+
|----------|-----------|
|
|
42
|
+
| Single **Node HTTP server** entry (`tsx server.ts` / exported `http.Server` from `listen()`) | Next.js / Remix / static-only site as the CMS |
|
|
43
|
+
| **Fluid compute** enabled (needed for WebSockets on Vercel) | Classic short-lived serverless with no WebSocket / no Fluid |
|
|
44
|
+
| Durable store = **Supabase Postgres** (pooler URL) | **SQLite / `data/*.db` as production store** on Vercel (ephemeral filesystem) |
|
|
45
|
+
| Media = **Cloudinary** (recommended) or other durable HTTPS URLs | Relying on local `public/uploads` alone for production fleets |
|
|
46
|
+
| Env vars in **Vercel Project → Settings → Environment Variables** | Committing passwords / Cloudinary secrets / `.env` with secrets |
|
|
47
|
+
| `cms.hostingTarget`: `"vercel"` in `brand.json` | Creating `.replit` / Replit artifact files |
|
|
48
|
+
|
|
49
|
+
**Do not** scaffold a leftover Next.js app “and put TomorrowOS beside it.” Remove Next scaffolds so TomorrowOS `server.ts` is the sole app.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Questionnaire scope (STRICT)
|
|
54
|
+
|
|
55
|
+
Ask **only** these questions, in **this exact order**:
|
|
56
|
+
|
|
57
|
+
| Step | Section | When |
|
|
58
|
+
|------|---------|------|
|
|
59
|
+
| 1 | **Question 1** — Supabase **Session pooler** connection string | Always |
|
|
60
|
+
| 2 | **Question 2** — Media storage (+ Cloudinary Env Vars if chosen) | Always (after Q1) |
|
|
61
|
+
| 3 | **Question 3** — Brand / TomorrowOS look (`brand.json` only) | Always last |
|
|
62
|
+
|
|
63
|
+
**That is the complete list.** Do **not** ask how many screens / devices. There is **no** “Question 2b” — Cloudinary credentials stay **inside Question 2**.
|
|
64
|
+
|
|
65
|
+
If the user says “just set it up, don’t ask questions,” respond:
|
|
66
|
+
|
|
67
|
+
> I need a few quick answers from VERCEL_SETUP.md (Supabase Session pooler URL, media storage — plus Cloudinary credentials if you choose Cloudinary — and branding). It takes about a minute and ensures the CMS works on Vercel.
|
|
68
|
+
|
|
69
|
+
Then ask **Question 1**.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Hard rules
|
|
74
|
+
|
|
75
|
+
1. **Ask only VERCEL_SETUP.md questions 1–3.** Never use `LLM_PROMPT.md` or `REPLIT_SETUP.md` as the questionnaire.
|
|
76
|
+
2. **One question at a time** (exception: after Cloudinary choice, immediately collect the three Cloudinary env vars in the same Question 2).
|
|
77
|
+
3. **Do not invent** Cloudinary credentials, Supabase URLs, or brand colours.
|
|
78
|
+
4. **Prefer `SUPABASE_URL`** for Supabase (same as Replit). `DATABASE_URL` may be used as fallback on Vercel, but document one canonical name — prefer **`SUPABASE_URL`**.
|
|
79
|
+
5. **Always prefer the Supabase Session pooler URL** (port **6543**, host like `*.pooler.supabase.com`). Direct `db.*.supabase.co:5432` URLs commonly fail on Vercel/sandbox with **`ENETUNREACH`** (often IPv6).
|
|
80
|
+
6. **Prefer `npx @tomorrowos/sdk init`** (or `templates/cms-starter`) as the seed. Do not rebuild pairing / WebSocket / playlist APIs.
|
|
81
|
+
7. **Never commit secrets.** Use Vercel Environment Variables (+ optional non-secret flags in committed `.env.example`).
|
|
82
|
+
8. **Skip Replit-only files:** do **not** create `.replit`, `.replit-artifact`, or Replit `deploymentTarget`.
|
|
83
|
+
9. **SQLite is not a production store on Vercel.** Configure Supabase for pairing/playlists. Keep `sqlitePath` only as a local-dev fallback in `createTomorrowOSStore`.
|
|
84
|
+
10. **Enable Fluid compute** for WebSockets. Confirm Project settings after scaffold.
|
|
85
|
+
11. **Export the HTTP server** Vercel can capture: `const server = tomorrowos.listen(...); export default server;` (or equivalent). Do not export only the `TomorrowOS` class instance unless the platform documents another capture path.
|
|
86
|
+
12. After Q&A: **configure → install → run/deploy → minimal verify** (Control Panel at `/`). Do not run a long test suite.
|
|
87
|
+
|
|
88
|
+
**Question order:** Q1 → Q2 → Q3 → execution checklist.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Runtime & Vercel deploy rules (mandatory)
|
|
93
|
+
|
|
94
|
+
### Runtime
|
|
95
|
+
|
|
96
|
+
- Serve with **`tsx server.ts`** (or compiled Node that still calls `TomorrowOS.listen` with `staticRoot`).
|
|
97
|
+
- Keep **`@tomorrowos/sdk`** and put **`tsx` in `dependencies`** (not only `devDependencies`) if production runs via `tsx`.
|
|
98
|
+
- Node **20+**.
|
|
99
|
+
- **Do not** replace the SDK with a hand-rolled server that drops WebSocket upgrades.
|
|
100
|
+
|
|
101
|
+
### Expected `package.json` scripts
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"scripts": {
|
|
106
|
+
"dev": "tsx watch server.ts",
|
|
107
|
+
"start": "tsx server.ts"
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**Forbidden as the Deploy entrypoint:** `next start`, static export only, or any command that does not start `TomorrowOS.listen(...)`.
|
|
113
|
+
|
|
114
|
+
### Minimal dependencies
|
|
115
|
+
|
|
116
|
+
```json
|
|
117
|
+
{
|
|
118
|
+
"type": "module",
|
|
119
|
+
"engines": {
|
|
120
|
+
"node": ">=20"
|
|
121
|
+
},
|
|
122
|
+
"dependencies": {
|
|
123
|
+
"@tomorrowos/sdk": "latest",
|
|
124
|
+
"dotenv": "^17.2.3",
|
|
125
|
+
"tsx": "^4.19.0"
|
|
126
|
+
},
|
|
127
|
+
"devDependencies": {
|
|
128
|
+
"@types/node": "^20.0.0",
|
|
129
|
+
"typescript": "^5.5.0"
|
|
130
|
+
},
|
|
131
|
+
"scripts": {
|
|
132
|
+
"dev": "tsx watch server.ts",
|
|
133
|
+
"start": "tsx server.ts"
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Use the latest published `@tomorrowos/sdk` when scaffolding (do not invent versions).
|
|
139
|
+
|
|
140
|
+
### `server.ts` pattern (Vercel-adapted)
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
import "dotenv/config";
|
|
144
|
+
import { readFileSync } from "fs";
|
|
145
|
+
import { fileURLToPath } from "url";
|
|
146
|
+
import { dirname, join } from "path";
|
|
147
|
+
import { createTomorrowOSStore, TomorrowOS } from "@tomorrowos/sdk";
|
|
148
|
+
|
|
149
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
150
|
+
const brand = JSON.parse(readFileSync(join(__dirname, "brand.json"), "utf8"));
|
|
151
|
+
|
|
152
|
+
const store = createTomorrowOSStore({
|
|
153
|
+
// Prefer SUPABASE_URL. On Vercel use the Session pooler connection string.
|
|
154
|
+
databaseUrl: process.env.SUPABASE_URL || process.env.DATABASE_URL,
|
|
155
|
+
sqlitePath: join(__dirname, "data", "tomorrowos.db") // local/dev fallback only
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const tomorrowos = new TomorrowOS({ brand, store });
|
|
159
|
+
|
|
160
|
+
const server = tomorrowos.listen({
|
|
161
|
+
port: Number(process.env.PORT) || 3000,
|
|
162
|
+
host: "0.0.0.0",
|
|
163
|
+
staticRoot: join(__dirname, "public")
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Vercel / Node server capture: export the HTTP server, not only TomorrowOS.
|
|
167
|
+
export default server;
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Load env with `dotenv` for local/`vercel dev`. On production Deploy, Vercel injects Environment Variables natively.
|
|
171
|
+
|
|
172
|
+
### `vercel.json` (recommended minimum)
|
|
173
|
+
|
|
174
|
+
Adapt to the project’s current Vercel Node-server / Fluid conventions. At minimum ensure:
|
|
175
|
+
|
|
176
|
+
```json
|
|
177
|
+
{
|
|
178
|
+
"fluid": true
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Also:
|
|
183
|
+
|
|
184
|
+
1. Project → **Fluid compute** enabled (required for WebSockets).
|
|
185
|
+
2. Framework Preset: **Other** (or Node) — **not** Next.js unless wrapping is intentional and still exposes TomorrowOS `listen()`.
|
|
186
|
+
3. Install / Start commands must end in running the CMS server (e.g. `npm run start` or Vercel’s detected `server.ts` entry).
|
|
187
|
+
4. Do **not** enable “Output Directory” static-only hosting for this CMS.
|
|
188
|
+
|
|
189
|
+
If Vercel’s UI and this file disagree on bundling, **prefer whatever keeps a single long-lived Node HTTP server with WebSocket upgrade on `/`**. Document the adaptation in the final summary.
|
|
190
|
+
|
|
191
|
+
### WebSocket / multi-instance note
|
|
192
|
+
|
|
193
|
+
- Device sockets are pinned to **one** function/instance. TomorrowOS device maps are **in-memory**.
|
|
194
|
+
- For large fleets on multi-instance Vercel, prefer **one stable instance / region**, or accept that reconnects may land on another instance until the product adds shared presence (out of scope for this setup).
|
|
195
|
+
- Always use **Supabase** so pairings/playlists survive cold starts and deploys.
|
|
196
|
+
|
|
197
|
+
### What not to create
|
|
198
|
+
|
|
199
|
+
- ❌ `.replit`, `.replit-artifact`
|
|
200
|
+
- ❌ Next.js `app/` / `pages/` CMS
|
|
201
|
+
- ❌ Separate “API project” without the Control Panel
|
|
202
|
+
- ❌ Committing `SUPABASE_URL` passwords or Cloudinary secrets
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## Question 1 — Supabase Session pooler (always)
|
|
207
|
+
|
|
208
|
+
> Always configure Supabase for Vercel. Do **not** ask screen counts.
|
|
209
|
+
|
|
210
|
+
**Ask exactly:**
|
|
211
|
+
|
|
212
|
+
> Paste your Supabase Postgres **Session pooler** connection string. I will store it as the Vercel Environment Variable **`SUPABASE_URL`**.
|
|
213
|
+
>
|
|
214
|
+
> In Supabase: **Project Settings → Database → Connection string → Connection pooling** (Session mode).
|
|
215
|
+
> Preferred shape:
|
|
216
|
+
> `postgresql://postgres.[PROJECT]:[PASSWORD]@aws-0-[REGION].pooler.supabase.com:6543/postgres`
|
|
217
|
+
>
|
|
218
|
+
> **Do not** use the direct host `db.*.supabase.co:5432` for Vercel — sandbox/serverless often fails with `ENETUNREACH` (commonly on IPv6).
|
|
219
|
+
>
|
|
220
|
+
> Confirm SSL is required (usually **yes** for Supabase).
|
|
221
|
+
|
|
222
|
+
**You must then:**
|
|
223
|
+
|
|
224
|
+
1. Set Vercel Env Vars (Production + Preview + Development as appropriate):
|
|
225
|
+
- `SUPABASE_URL=<pooler string>`
|
|
226
|
+
- `TOMORROWOS_STORE=supabase`
|
|
227
|
+
- `DATABASE_SSL=true` (unless user explicitly disables SSL)
|
|
228
|
+
2. Wire `server.ts` as in **Runtime & Vercel deploy rules**.
|
|
229
|
+
3. Optional committed `.env.example` with **placeholders only** (no real password).
|
|
230
|
+
4. **Do not** commit the real connection string.
|
|
231
|
+
5. **Configuration over probing:** if local/`vercel dev` still shows `ENETUNREACH` / IPv6 errors, verify the string is the **pooler** (`pooler.supabase.com` + `:6543`). Treat remaining sandbox DNS issues as non-fatal when Production deploy Env Vars are correct — but **do** fix an obvious direct `:5432` URL before finishing.
|
|
232
|
+
|
|
233
|
+
If the user refuses Supabase, warn that Vercel cannot use durable SQLite for fleets, then only continue with SQLite after explicit confirmation (local/demo only).
|
|
234
|
+
|
|
235
|
+
**Later in Question 3:** set `cms.hostingTarget` to **`"vercel"`**.
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## Question 2 — Media storage
|
|
240
|
+
|
|
241
|
+
### Step A — Ask storage choice
|
|
242
|
+
|
|
243
|
+
**Ask exactly:**
|
|
244
|
+
|
|
245
|
+
> How should playlist media (images/videos) be stored?
|
|
246
|
+
>
|
|
247
|
+
> **Recommended on Vercel: Cloudinary** — durable public HTTPS URLs (`https://res.cloudinary.com/...`). Local `public/uploads` is ephemeral on serverless/filesystem deploys and is a poor fleet default.
|
|
248
|
+
>
|
|
249
|
+
> Alternatives:
|
|
250
|
+
> - **Local disk only** — `public/uploads` (OK for quick local tests; **not** for production Vercel fleets).
|
|
251
|
+
>
|
|
252
|
+
> Do you want me to set up **Cloudinary**? (yes / no)
|
|
253
|
+
|
|
254
|
+
### Step B — If YES / Cloudinary (same Question 2)
|
|
255
|
+
|
|
256
|
+
Immediately collect and store as **Vercel Environment Variables**:
|
|
257
|
+
|
|
258
|
+
- `CLOUDINARY_CLOUD_NAME`
|
|
259
|
+
- `CLOUDINARY_API_KEY`
|
|
260
|
+
- `CLOUDINARY_API_SECRET`
|
|
261
|
+
- Optional: `CLOUDINARY_FOLDER` (e.g. `tomorrowos`)
|
|
262
|
+
|
|
263
|
+
Do **not** invent placeholders. Do **not** proceed to Question 3 until the three required vars exist. Do **not** run a formal Cloudinary upload test unless the user asks.
|
|
264
|
+
|
|
265
|
+
The SDK auto-detects these env vars.
|
|
266
|
+
|
|
267
|
+
### If NO — local uploads
|
|
268
|
+
|
|
269
|
+
1. `mkdir -p public/uploads`
|
|
270
|
+
2. Warn clearly: files may vanish on redeploy; players need stable HTTPS URLs — Cloudinary is strongly preferred for Vercel.
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## Question 3 — Brand (`brand.json` only)
|
|
275
|
+
|
|
276
|
+
> Updates **only** `brand.json`. Does **not** change Vercel project settings, Env Vars, or `server.ts` store wiring beyond what Q1–Q2 already required.
|
|
277
|
+
|
|
278
|
+
**Ask exactly:**
|
|
279
|
+
|
|
280
|
+
> Let’s brand your TomorrowOS experience. Please provide:
|
|
281
|
+
>
|
|
282
|
+
> 1. **Product / venue name**
|
|
283
|
+
> 2. **Tagline** (optional)
|
|
284
|
+
> 3. **Primary colour** (hex, e.g. `#FF8A3D`)
|
|
285
|
+
> 4. **Background colour** (hex, optional — default `#FAFAF9`)
|
|
286
|
+
> 5. **Text colour** (hex, optional — default `#0A0908`)
|
|
287
|
+
> 6. **Secondary / accent colour** (hex, optional)
|
|
288
|
+
> 7. **Logo** — upload SVG/PNG into the project, or a URL I can fetch into `./assets/`
|
|
289
|
+
|
|
290
|
+
Then write `brand.json` (validate mentally against `brand.schema.json`). Minimum:
|
|
291
|
+
|
|
292
|
+
```json
|
|
293
|
+
{
|
|
294
|
+
"name": "<user name>",
|
|
295
|
+
"tagline": "<user tagline or Digital signage>",
|
|
296
|
+
"targetPlatforms": ["tizen"],
|
|
297
|
+
"primaryColor": "#FF8A3D",
|
|
298
|
+
"secondaryColor": "#F5F3EF",
|
|
299
|
+
"backgroundColor": "#FAFAF9",
|
|
300
|
+
"textColor": "#0A0908",
|
|
301
|
+
"logoPath": "./assets/logo.svg",
|
|
302
|
+
"fontFamily": "Inter",
|
|
303
|
+
"cms": {
|
|
304
|
+
"useCase": "other",
|
|
305
|
+
"hostingTarget": "vercel",
|
|
306
|
+
"expectedScreens": 5,
|
|
307
|
+
"features": {
|
|
308
|
+
"bulkCommands": false,
|
|
309
|
+
"proofOfPlay": false,
|
|
310
|
+
"contentScheduling": true,
|
|
311
|
+
"userManagement": false
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
"protocolVersion": "1.0"
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
Always set **`cms.hostingTarget`: `"vercel"`** without asking. Default `expectedScreens` to `5` unless the user already volunteered a number. Set `cmsEndpoint` only if they already know the public `https://…vercel.app` URL; otherwise tell them to pair TVs with the deployed HTTPS origin (player maps `https://` → `wss://`).
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
## After all answers — execution checklist
|
|
323
|
+
|
|
324
|
+
### A. Seed the project
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
npx @tomorrowos/sdk@latest init .
|
|
328
|
+
# If not empty and user confirms starter overwrite:
|
|
329
|
+
# npx @tomorrowos/sdk@latest init . --force
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
Then:
|
|
333
|
+
|
|
334
|
+
1. Remove leftover Next.js / other frameworks so TomorrowOS is sole app.
|
|
335
|
+
2. Apply Vercel-adapted `server.ts` (**export `listen()` HTTP server**).
|
|
336
|
+
3. Add/adjust `vercel.json` with **`fluid: true`** (or enable Fluid in the dashboard).
|
|
337
|
+
4. Do **not** add `.replit*`.
|
|
338
|
+
|
|
339
|
+
### B. Environment Variables (Vercel dashboard + local)
|
|
340
|
+
|
|
341
|
+
| Name | Required | Notes |
|
|
342
|
+
|------|----------|--------|
|
|
343
|
+
| `SUPABASE_URL` | Yes (production) | **Session pooler** `:6543` |
|
|
344
|
+
| `TOMORROWOS_STORE` | Yes | `supabase` |
|
|
345
|
+
| `DATABASE_SSL` | Yes (typical) | `true` |
|
|
346
|
+
| `CLOUDINARY_*` | If Q2 = Cloudinary | Three keys |
|
|
347
|
+
| `PORT` | Optional | Vercel injects as needed |
|
|
348
|
+
|
|
349
|
+
Mirror non-secrets in `.env` for local `npm run dev` if helpful; never commit secrets.
|
|
350
|
+
|
|
351
|
+
### C. Install and run
|
|
352
|
+
|
|
353
|
+
```bash
|
|
354
|
+
npm install
|
|
355
|
+
npm run start
|
|
356
|
+
# or: vercel dev
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
Confirm logs: `[TomorrowOS] listening on http://0.0.0.0:…`
|
|
360
|
+
Confirm browser: Control Panel at `/`.
|
|
361
|
+
|
|
362
|
+
### D. Deploy
|
|
363
|
+
|
|
364
|
+
```bash
|
|
365
|
+
vercel --prod
|
|
366
|
+
# or git push → Vercel Git integration
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
Tell the user:
|
|
370
|
+
|
|
371
|
+
- **Control Panel:** `https://YOUR-PROJECT.vercel.app`
|
|
372
|
+
- **TV CMS endpoint:** same HTTPS origin (players use `wss://`)
|
|
373
|
+
|
|
374
|
+
### E. Minimal verification only
|
|
375
|
+
|
|
376
|
+
**Do:**
|
|
377
|
+
|
|
378
|
+
1. Control Panel HTML at `/`
|
|
379
|
+
2. Server status: Media OK when Cloudinary configured
|
|
380
|
+
3. Database: if ERROR shows **`ENETUNREACH` … IPv6 … `:5432`**, switch `SUPABASE_URL` to **pooler `:6543`** and redeploy — call this out explicitly to the user
|
|
381
|
+
4. Fluid compute on
|
|
382
|
+
|
|
383
|
+
**Do not block** on long WebSocket/device pairing tests unless the user asks.
|
|
384
|
+
|
|
385
|
+
---
|
|
386
|
+
|
|
387
|
+
## Failure recovery cheat sheet
|
|
388
|
+
|
|
389
|
+
| Symptom | Likely cause | Fix |
|
|
390
|
+
|---------|--------------|-----|
|
|
391
|
+
| `ENETUNREACH` / IPv6 / `:5432` | Direct Supabase DB URL | Use **Session pooler** `*.pooler.supabase.com:6543` |
|
|
392
|
+
| Control Panel OK, devices never stay paired after restart | SQLite / ephemeral disk | Must use Supabase |
|
|
393
|
+
| Uploads break / broken thumbs in prod | Local `public/uploads` | Cloudinary |
|
|
394
|
+
| WebSocket fails / devices drop immediately | Fluid off / static-only / Next wrapper | Enable Fluid; sole Node `listen()` server |
|
|
395
|
+
| Agent created Next.js + TomorrowOS | Wrong scaffold | Remove Next; keep cms-starter |
|
|
396
|
+
| Secrets in git | Mistake | Rotate keys; move to Vercel Env Vars |
|
|
397
|
+
| Replit files present | Copied wrong protocol | Delete `.replit*`; use this file |
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
## How this differs from `REPLIT_SETUP.md`
|
|
402
|
+
|
|
403
|
+
| Topic | Replit | Vercel |
|
|
404
|
+
|-------|--------|--------|
|
|
405
|
+
| Secrets | Replit Secrets | Vercel Environment Variables |
|
|
406
|
+
| Deploy config | `.replit`, artifact `kind=web` | `vercel.json` + Fluid; **no** `.replit` |
|
|
407
|
+
| `hostingTarget` | `"here"` | `"vercel"` |
|
|
408
|
+
| Supabase URL | Prefer pooler; Preview DNS may fail | **Require pooler**; direct `:5432` often `ENETUNREACH` |
|
|
409
|
+
| SQLite | Demo OK on persistent Repl disk | **Not** production on Vercel |
|
|
410
|
+
| Object Storage option | Replit Object Storage | N/A — prefer Cloudinary |
|
|
411
|
+
| WebSocket | Long-lived Node (Autoscale) | Fluid compute + Node server export |
|
|
412
|
+
|
|
413
|
+
---
|
|
414
|
+
|
|
415
|
+
## Final summary template (Agent → human)
|
|
416
|
+
|
|
417
|
+
After setup, report:
|
|
418
|
+
|
|
419
|
+
1. **Live URL** (Production)
|
|
420
|
+
2. **Env Vars set** (names only — never values)
|
|
421
|
+
3. **Store:** Supabase pooler / SSL flags
|
|
422
|
+
4. **Media:** Cloudinary or local (with warning)
|
|
423
|
+
5. **Brand:** name + `hostingTarget: vercel`
|
|
424
|
+
6. **Adaptations from Replit protocol:** list (e.g. skipped `.replit`, exported HTTP server, Fluid, pooler URL)
|
|
425
|
+
7. **Open action:** if Database still ERROR, paste the pooler instruction again
|
|
426
|
+
|
|
427
|
+
Protocol complete when the Control Panel renders at `/` on the deployed (or local) server and Q1–Q3 configuration is saved — even if a sandbox DB probe still flakes **after** the pooler URL is correctly set for Production.
|
package/dist/store/types.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/store/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wFAAwF;AACxF,MAAM,WAAW,oBAAoB;IACnC,oBAAoB,EAAE,MAAM,CAAC;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6DAA6D;IAC7D,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/store/types.ts"],"names":[],"mappings":"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"}
|
package/dist/tomorrowos.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tomorrowos.d.ts","sourceRoot":"","sources":["../src/tomorrowos.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,IAAI,MAAM,MAAM,CAAC;AAYxB,OAAO,EAAE,eAAe,EAAE,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAChF,OAAO,KAAK,EACV,gBAAgB,EAKhB,eAAe,EAEhB,MAAM,kBAAkB,CAAC;AAe1B,YAAY,EACV,cAAc,EACd,eAAe,EACf,kBAAkB,EAClB,aAAa,EACd,MAAM,oBAAoB,CAAC;AAE5B,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,eAAe,CAAC;IACvB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,eAAe,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAoED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,2EAA2E;IAC3E,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,kBAAkB,EAAE,OAAO,CAAC;IAC5B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,mEAAmE;IACnE,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,kBAAkB,EAAE,KAAK,CAAC;QACxB,UAAU,EAAE,MAAM,CAAC;QACnB,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,QAAQ,CAAC,EAAE;YACT,SAAS,CAAC,EAAE,MAAM,CAAC;YACnB,OAAO,CAAC,EAAE,MAAM,CAAC;YACjB,KAAK,CAAC,EAAE,MAAM,CAAC;YACf,GAAG,CAAC,EAAE,MAAM,CAAC;SACd,CAAC;KACH,CAAC,CAAC;IACH,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,gBAAgB,EAAE;QAChB,GAAG,EAAE,MAAM,CAAC;QACZ,UAAU,EAAE,MAAM,CAAC;KACpB,GAAG,IAAI,CAAC;IACT,2DAA2D;IAC3D,UAAU,EAAE,gBAAgB,GAAG,IAAI,CAAC;CACrC;
|
|
1
|
+
{"version":3,"file":"tomorrowos.d.ts","sourceRoot":"","sources":["../src/tomorrowos.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,IAAI,MAAM,MAAM,CAAC;AAYxB,OAAO,EAAE,eAAe,EAAE,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAChF,OAAO,KAAK,EACV,gBAAgB,EAKhB,eAAe,EAEhB,MAAM,kBAAkB,CAAC;AAe1B,YAAY,EACV,cAAc,EACd,eAAe,EACf,kBAAkB,EAClB,aAAa,EACd,MAAM,oBAAoB,CAAC;AAE5B,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,eAAe,CAAC;IACvB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,eAAe,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAoED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,2EAA2E;IAC3E,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,kBAAkB,EAAE,OAAO,CAAC;IAC5B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,mEAAmE;IACnE,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,kBAAkB,EAAE,KAAK,CAAC;QACxB,UAAU,EAAE,MAAM,CAAC;QACnB,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,QAAQ,CAAC,EAAE;YACT,SAAS,CAAC,EAAE,MAAM,CAAC;YACnB,OAAO,CAAC,EAAE,MAAM,CAAC;YACjB,KAAK,CAAC,EAAE,MAAM,CAAC;YACf,GAAG,CAAC,EAAE,MAAM,CAAC;SACd,CAAC;KACH,CAAC,CAAC;IACH,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,gBAAgB,EAAE;QAChB,GAAG,EAAE,MAAM,CAAC;QACZ,UAAU,EAAE,MAAM,CAAC;KACpB,GAAG,IAAI,CAAC;IACT,2DAA2D;IAC3D,UAAU,EAAE,gBAAgB,GAAG,IAAI,CAAC;CACrC;AAoBD,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAwKD,qBAAa,UAAW,SAAQ,YAAY;IAC1C,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAC;IAChC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAkB;IACxC,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmC;IAC3D,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAsC;IACxE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAuC;IAClE,OAAO,CAAC,UAAU,CAA4B;IAC9C,OAAO,CAAC,GAAG,CAAgC;IAC3C,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,eAAe,CAAgB;IACvC,wFAAwF;IACxF,OAAO,CAAC,eAAe,CAAuB;IAC9C,+DAA+D;IAC/D,OAAO,CAAC,UAAU,CAAuB;gBAE7B,OAAO,EAAE,iBAAiB;IAOtC,yFAAyF;IACnF,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;QACxD,MAAM,EAAE,OAAO,CAAC;QAChB,MAAM,CAAC,EAAE,iBAAiB,CAAC,QAAQ,CAAC,CAAC;KACtC,CAAC;YAkBY,iBAAiB;IAY/B,6EAA6E;IACvE,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IA4EhE,6EAA6E;IACvE,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAC;IAuCvF,OAAO;uBACU,MAAM;sBArHgC,MAAM;;2BAsHxC,MAAM;sBAzCgC,MAAM;sBAAY,OAAO;;MA0ClF;IAEF,kFAAkF;IAC5E,WAAW,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;IAgF9C;;;OAGG;IACG,mBAAmB,CACvB,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,OAAO,GAClB,OAAO,CAAC;QACT,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,gBAAgB,CAAC;QAC7B,MAAM,EAAE,OAAO,CAAC;KACjB,CAAC;YAqBY,sBAAsB;IAsB9B,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC;QACjE,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;IAyBF,0FAA0F;YAC5E,2BAA2B;YAW3B,0BAA0B;IAYxC,qFAAqF;YACvE,8BAA8B;IAY5C,OAAO,CAAC,aAAa;IASrB,OAAO,CAAC,aAAa;IAMrB,OAAO,CAAC,iBAAiB;IAKzB,OAAO,CAAC,gBAAgB;YAoBV,+BAA+B;YAsC/B,iBAAiB;YASjB,iBAAiB;YAqCjB,kBAAkB;IAUhC,uFAAuF;IACvF,OAAO,CAAC,kBAAkB;YAmBZ,gBAAgB;YAMhB,uBAAuB;IA0CrC,MAAM,CAAC,QAAQ,EAAE,MAAM;oBAGD,CAAC,oBACT,MAAM,WACN,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,OAAO,CAAC;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAAC,KAAK,CAAC,EAAE,MAAM,CAAC;YAAC,KAAK,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;;IAU5E,OAAO,CAAC,mBAAmB;IA6D3B,6EAA6E;IAC7E,OAAO,CAAC,kBAAkB;IAO1B,MAAM,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,CAAC,MAAM;YAsD7B,iBAAiB;YAgDjB,uBAAuB;IA6ErC,OAAO,CAAC,oBAAoB;YASd,yBAAyB;YAQzB,4BAA4B;YAa5B,2CAA2C;YAO3C,iCAAiC;YA6BjC,wBAAwB;YAiBxB,cAAc;IAiD5B,OAAO,CAAC,uBAAuB;IAO/B,OAAO,CAAC,mBAAmB;YAOb,yBAAyB;YAwBzB,oBAAoB;YAkDpB,yBAAyB;YAsBzB,uBAAuB;YAcvB,UAAU;YA0WV,gBAAgB;IAqG9B,OAAO,CAAC,iBAAiB;IAWzB,OAAO,CAAC,gBAAgB;CAkJzB"}
|
package/dist/tomorrowos.js
CHANGED
|
@@ -52,7 +52,6 @@ function normalizeOnOffTimer(input) {
|
|
|
52
52
|
throw new Error("onOffTimer object is required");
|
|
53
53
|
}
|
|
54
54
|
const body = input;
|
|
55
|
-
const enabled = Boolean(body.enabled);
|
|
56
55
|
const turnOnAt = String(body.turnOnAt ?? "").trim();
|
|
57
56
|
const turnOffAt = String(body.turnOffAt ?? "").trim();
|
|
58
57
|
if (!ON_OFF_TIME_RE.test(turnOnAt) || !ON_OFF_TIME_RE.test(turnOffAt)) {
|
|
@@ -61,7 +60,7 @@ function normalizeOnOffTimer(input) {
|
|
|
61
60
|
if (turnOnAt === turnOffAt) {
|
|
62
61
|
throw new Error("turnOnAt and turnOffAt must be different");
|
|
63
62
|
}
|
|
64
|
-
return {
|
|
63
|
+
return { turnOnAt, turnOffAt };
|
|
65
64
|
}
|
|
66
65
|
function isRemoteUploadedAssetUrl(url) {
|
|
67
66
|
return /^https?:\/\//i.test(String(url || "").trim());
|
|
@@ -408,7 +407,16 @@ export class TomorrowOS extends EventEmitter {
|
|
|
408
407
|
latestErrorAt: latestError?.timestamp ?? null,
|
|
409
408
|
latestErrorMessage: latestError?.message ?? null,
|
|
410
409
|
latestScreenshot,
|
|
411
|
-
onOffTimer:
|
|
410
|
+
onOffTimer: (() => {
|
|
411
|
+
try {
|
|
412
|
+
return record.onOffTimer
|
|
413
|
+
? normalizeOnOffTimer(record.onOffTimer)
|
|
414
|
+
: null;
|
|
415
|
+
}
|
|
416
|
+
catch {
|
|
417
|
+
return null;
|
|
418
|
+
}
|
|
419
|
+
})()
|
|
412
420
|
};
|
|
413
421
|
}));
|
|
414
422
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tomorrowos/sdk",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.20",
|
|
4
4
|
"description": "TomorrowOS CMS server SDK - WebSocket transport, pairing, device commands, optional static CMS UI. Includes CLI (tomorrowos init / build) and starter templates.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"README.md",
|
|
21
21
|
"LLM_PROMPT.md",
|
|
22
22
|
"REPLIT_SETUP.md",
|
|
23
|
+
"VERCEL_SETUP.md",
|
|
23
24
|
"REPLIT_UPGRADE.md",
|
|
24
25
|
"BUILD_GUARDRAILS.md",
|
|
25
26
|
"PLAYER_INSTALL.md",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "my-cms",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.20",
|
|
4
4
|
"description": "CMS server on @tomorrowos/sdk. Add your UI (React, static files, etc.) alongside this server.",
|
|
5
5
|
"private": true,
|
|
6
6
|
"type": "module",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"build-player": "tomorrowos build --platform tizen"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@tomorrowos/sdk": "^0.9.
|
|
16
|
+
"@tomorrowos/sdk": "^0.9.20",
|
|
17
17
|
"dotenv": "^17.2.3",
|
|
18
18
|
"tsx": "^4.19.0"
|
|
19
19
|
},
|
|
@@ -1084,6 +1084,67 @@ function appendDeviceMetaRow(meta, label, value) {
|
|
|
1084
1084
|
meta.appendChild(row);
|
|
1085
1085
|
}
|
|
1086
1086
|
|
|
1087
|
+
function hasConfiguredOnOffTimer(timer) {
|
|
1088
|
+
return !!(
|
|
1089
|
+
timer &&
|
|
1090
|
+
typeof timer.turnOnAt === "string" &&
|
|
1091
|
+
typeof timer.turnOffAt === "string" &&
|
|
1092
|
+
timer.turnOnAt &&
|
|
1093
|
+
timer.turnOffAt
|
|
1094
|
+
);
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
function appendHardwareTimerStatusRow(meta, device) {
|
|
1098
|
+
const timer = device.onOffTimer || null;
|
|
1099
|
+
const configured = hasConfiguredOnOffTimer(timer);
|
|
1100
|
+
|
|
1101
|
+
const row = document.createElement("div");
|
|
1102
|
+
row.className = "device-meta-row device-meta-row--timer-status";
|
|
1103
|
+
|
|
1104
|
+
const dt = document.createElement("dt");
|
|
1105
|
+
dt.textContent = "Hardware Timer Status";
|
|
1106
|
+
|
|
1107
|
+
const dd = document.createElement("dd");
|
|
1108
|
+
const statusWrap = document.createElement("span");
|
|
1109
|
+
statusWrap.className = "hardware-timer-status";
|
|
1110
|
+
statusWrap.tabIndex = 0;
|
|
1111
|
+
|
|
1112
|
+
const statusValue = document.createElement("span");
|
|
1113
|
+
statusValue.className = configured
|
|
1114
|
+
? "hardware-timer-status__value hardware-timer-status__value--on"
|
|
1115
|
+
: "hardware-timer-status__value hardware-timer-status__value--off";
|
|
1116
|
+
statusValue.textContent = configured ? "On" : "Off";
|
|
1117
|
+
|
|
1118
|
+
const popup = document.createElement("span");
|
|
1119
|
+
popup.className = "hardware-timer-status__popup";
|
|
1120
|
+
popup.setAttribute("role", "tooltip");
|
|
1121
|
+
if (configured) {
|
|
1122
|
+
const onLine = document.createElement("div");
|
|
1123
|
+
const onLabel = document.createElement("strong");
|
|
1124
|
+
onLabel.textContent = "Turn on";
|
|
1125
|
+
onLine.appendChild(onLabel);
|
|
1126
|
+
onLine.appendChild(document.createTextNode(` ${timer.turnOnAt}`));
|
|
1127
|
+
|
|
1128
|
+
const offLine = document.createElement("div");
|
|
1129
|
+
const offLabel = document.createElement("strong");
|
|
1130
|
+
offLabel.textContent = "Turn off";
|
|
1131
|
+
offLine.appendChild(offLabel);
|
|
1132
|
+
offLine.appendChild(document.createTextNode(` ${timer.turnOffAt}`));
|
|
1133
|
+
|
|
1134
|
+
popup.appendChild(onLine);
|
|
1135
|
+
popup.appendChild(offLine);
|
|
1136
|
+
} else {
|
|
1137
|
+
popup.textContent = "No timer configured";
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
statusWrap.appendChild(statusValue);
|
|
1141
|
+
statusWrap.appendChild(popup);
|
|
1142
|
+
dd.appendChild(statusWrap);
|
|
1143
|
+
row.appendChild(dt);
|
|
1144
|
+
row.appendChild(dd);
|
|
1145
|
+
meta.appendChild(row);
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1087
1148
|
function createDeviceScreenshotThumb(device) {
|
|
1088
1149
|
const slot = document.createElement("div");
|
|
1089
1150
|
slot.className = "device-screenshot-slot";
|
|
@@ -1250,6 +1311,7 @@ function renderDeviceCards() {
|
|
|
1250
1311
|
for (const [label, value] of secondaryRows) {
|
|
1251
1312
|
appendDeviceMetaRow(metaSecondary, label, value);
|
|
1252
1313
|
}
|
|
1314
|
+
appendHardwareTimerStatusRow(metaSecondary, device);
|
|
1253
1315
|
|
|
1254
1316
|
metaBlock.appendChild(metaTop);
|
|
1255
1317
|
metaBlock.appendChild(metaSecondary);
|
|
@@ -1857,7 +1919,6 @@ async function saveOnOffTimerModal() {
|
|
|
1857
1919
|
if (!onEl || !offEl) return;
|
|
1858
1920
|
|
|
1859
1921
|
const onOffTimer = {
|
|
1860
|
-
enabled: true,
|
|
1861
1922
|
turnOnAt: normalizeTimeInputValue(onEl.value, "06:00"),
|
|
1862
1923
|
turnOffAt: normalizeTimeInputValue(offEl.value, "18:00")
|
|
1863
1924
|
};
|
|
@@ -283,6 +283,7 @@ body {
|
|
|
283
283
|
border-radius: 8px;
|
|
284
284
|
padding: 0.85rem 0.95rem;
|
|
285
285
|
background: #fafaf9;
|
|
286
|
+
overflow: visible;
|
|
286
287
|
}
|
|
287
288
|
|
|
288
289
|
.device-card-header {
|
|
@@ -439,6 +440,73 @@ body {
|
|
|
439
440
|
display: block;
|
|
440
441
|
}
|
|
441
442
|
|
|
443
|
+
.device-meta-row--timer-status {
|
|
444
|
+
position: relative;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
.hardware-timer-status {
|
|
448
|
+
position: relative;
|
|
449
|
+
display: inline-block;
|
|
450
|
+
cursor: default;
|
|
451
|
+
outline: none;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
.hardware-timer-status__value--on {
|
|
455
|
+
color: #027a48;
|
|
456
|
+
font-weight: 600;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
.hardware-timer-status__value--off {
|
|
460
|
+
color: #667085;
|
|
461
|
+
font-weight: 600;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
.hardware-timer-status__popup {
|
|
465
|
+
position: absolute;
|
|
466
|
+
left: 0;
|
|
467
|
+
bottom: calc(100% + 0.45rem);
|
|
468
|
+
z-index: 20;
|
|
469
|
+
min-width: 9.5rem;
|
|
470
|
+
padding: 0.55rem 0.7rem;
|
|
471
|
+
border: 1px solid #e4e1dc;
|
|
472
|
+
border-radius: 6px;
|
|
473
|
+
background: #fff;
|
|
474
|
+
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
|
|
475
|
+
color: #333;
|
|
476
|
+
font-size: 0.75rem;
|
|
477
|
+
font-weight: 400;
|
|
478
|
+
line-height: 1.45;
|
|
479
|
+
white-space: nowrap;
|
|
480
|
+
opacity: 0;
|
|
481
|
+
visibility: hidden;
|
|
482
|
+
pointer-events: none;
|
|
483
|
+
transform: translateY(4px);
|
|
484
|
+
transition: opacity 0.12s ease, transform 0.12s ease, visibility 0.12s;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
.hardware-timer-status__popup::after {
|
|
488
|
+
content: "";
|
|
489
|
+
position: absolute;
|
|
490
|
+
top: 100%;
|
|
491
|
+
left: 0.85rem;
|
|
492
|
+
border: 6px solid transparent;
|
|
493
|
+
border-top-color: #fff;
|
|
494
|
+
filter: drop-shadow(0 1px 0 #e4e1dc);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
.hardware-timer-status__popup strong {
|
|
498
|
+
font-weight: 600;
|
|
499
|
+
color: #555;
|
|
500
|
+
margin-right: 0.35rem;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
.hardware-timer-status:hover .hardware-timer-status__popup,
|
|
504
|
+
.hardware-timer-status:focus-within .hardware-timer-status__popup {
|
|
505
|
+
opacity: 1;
|
|
506
|
+
visibility: visible;
|
|
507
|
+
transform: translateY(0);
|
|
508
|
+
}
|
|
509
|
+
|
|
442
510
|
.device-card-actions {
|
|
443
511
|
display: flex;
|
|
444
512
|
flex-wrap: wrap;
|