@tomorrowos/sdk 1.0.1 → 1.1.0
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 +229 -193
- package/VERCEL_QUESTIONS.md +118 -90
- package/VERCEL_SETUP.md +1087 -1056
- package/package.json +1 -1
- package/templates/cms-starter/.env.example +4 -0
- package/templates/cms-starter/README.md +12 -0
- package/templates/cms-starter/cms-auth.ts +292 -0
- package/templates/cms-starter/package.json +2 -2
- package/templates/cms-starter/public/assets/player-download/windows.svg +1 -0
- package/templates/cms-starter/public/index.html +10 -0
- package/templates/cms-starter/public/panel.css +4 -4
- package/templates/cms-starter/server.ts +17 -2
- package/templates/cms-starter-v0/.env.example +4 -0
- package/templates/cms-starter-v0/README.md +4 -0
- package/templates/cms-starter-v0/cms-app.ts +3 -0
- package/templates/cms-starter-v0/cms-auth.ts +292 -0
- package/templates/cms-starter-v0/cms-panel/assets/player-download/windows.svg +1 -0
- package/templates/cms-starter-v0/cms-panel/index.html +10 -0
- package/templates/cms-starter-v0/cms-panel/panel.css +4 -4
- package/templates/cms-starter-v0/package.json +2 -2
package/VERCEL_SETUP.md
CHANGED
|
@@ -1,1056 +1,1087 @@
|
|
|
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`).
|
|
7
|
-
>
|
|
8
|
-
> Protocol id: `vercel-setup/1.
|
|
9
|
-
>
|
|
10
|
-
> **Questions source of truth:** `VERCEL_QUESTIONS.md` - Agents must **open that file and paste each Q block verbatim**. Do not paraphrase.
|
|
11
|
-
> **Q1/Q2 = choice questions. Q3 = form only:** open **all seven optional inputs at once**, then **HARD STOP and WAIT** for submit or "skip". Never auto-skip because fields are optional. Never ask a branding choice first. Never show only Name + Primary colour. Logo = **Upload** above **Logo URL**.
|
|
12
|
-
> **
|
|
13
|
-
>
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
- `GET
|
|
26
|
-
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
For
|
|
42
|
-
|
|
43
|
-
After
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
never
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
- ?
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
| **
|
|
80
|
-
| **
|
|
81
|
-
|
|
|
82
|
-
|
|
|
83
|
-
|
|
|
84
|
-
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
- `GET
|
|
122
|
-
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
#
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
|
149
|
-
|
|
|
150
|
-
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
|
166
|
-
|
|
|
167
|
-
|
|
168
|
-
**
|
|
169
|
-
|
|
170
|
-
**
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
- ???
|
|
175
|
-
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
- ??
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
- ??
|
|
188
|
-
- ??
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
If the user
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
**
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
> **
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
|
239
|
-
|
|
240
|
-
|
|
|
241
|
-
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
- ??
|
|
274
|
-
- ??
|
|
275
|
-
- ??
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
"dev
|
|
285
|
-
"dev:
|
|
286
|
-
"
|
|
287
|
-
"
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
```
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
"
|
|
325
|
-
"
|
|
326
|
-
"
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
"
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
**
|
|
344
|
-
|
|
345
|
-
**
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
- `"
|
|
350
|
-
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
|
357
|
-
|
|
358
|
-
|
|
|
359
|
-
|
|
|
360
|
-
|
|
|
361
|
-
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
-H "
|
|
374
|
-
"
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
- ??No
|
|
388
|
-
- ??No
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
**Path
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
-
|
|
415
|
-
-
|
|
416
|
-
-
|
|
417
|
-
- **
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
"dev
|
|
427
|
-
"dev:
|
|
428
|
-
"
|
|
429
|
-
"
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
-
|
|
439
|
-
- **
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
"
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
"
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
import
|
|
480
|
-
import {
|
|
481
|
-
import {
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
const
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
process.env.
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
const
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
//
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
**
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
-
|
|
541
|
-
-
|
|
542
|
-
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
"
|
|
553
|
-
"
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
**
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
|
566
|
-
|
|
567
|
-
|
|
|
568
|
-
|
|
|
569
|
-
|
|
|
570
|
-
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
-
|
|
586
|
-
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
-
|
|
593
|
-
- ??
|
|
594
|
-
- ??
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
> **
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
>
|
|
620
|
-
>
|
|
621
|
-
> **
|
|
622
|
-
>
|
|
623
|
-
>
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
>
|
|
632
|
-
>
|
|
633
|
-
>
|
|
634
|
-
>
|
|
635
|
-
>
|
|
636
|
-
|
|
637
|
-
**
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
- `
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
>
|
|
655
|
-
>
|
|
656
|
-
>
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
- `
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
> **
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
>
|
|
686
|
-
>
|
|
687
|
-
> **
|
|
688
|
-
>
|
|
689
|
-
>
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
**
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
|
702
|
-
|
|
703
|
-
|
|
|
704
|
-
|
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
>
|
|
711
|
-
>
|
|
712
|
-
>
|
|
713
|
-
>
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
**
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
>
|
|
731
|
-
>
|
|
732
|
-
>
|
|
733
|
-
>
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
>
|
|
752
|
-
>
|
|
753
|
-
> **
|
|
754
|
-
>
|
|
755
|
-
>
|
|
756
|
-
>
|
|
757
|
-
> - **Forbidden:**
|
|
758
|
-
> - **Forbidden:**
|
|
759
|
-
> - **Forbidden:**
|
|
760
|
-
> - **Forbidden:**
|
|
761
|
-
> - **
|
|
762
|
-
> - **
|
|
763
|
-
>
|
|
764
|
-
> **
|
|
765
|
-
>
|
|
766
|
-
> **
|
|
767
|
-
>
|
|
768
|
-
>
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
|
777
|
-
|
|
778
|
-
|
|
|
779
|
-
|
|
|
780
|
-
|
|
|
781
|
-
|
|
|
782
|
-
|
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
>
|
|
795
|
-
>
|
|
796
|
-
>
|
|
797
|
-
>
|
|
798
|
-
>
|
|
799
|
-
>
|
|
800
|
-
>
|
|
801
|
-
>
|
|
802
|
-
>
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
"
|
|
826
|
-
"
|
|
827
|
-
"
|
|
828
|
-
"
|
|
829
|
-
"
|
|
830
|
-
"
|
|
831
|
-
"
|
|
832
|
-
"
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
"
|
|
836
|
-
"
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
"
|
|
840
|
-
"
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
**Do
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
|
1003
|
-
|
|
1004
|
-
|
|
|
1005
|
-
|
|
|
1006
|
-
|
|
|
1007
|
-
|
|
|
1008
|
-
|
|
|
1009
|
-
|
|
|
1010
|
-
|
|
|
1011
|
-
|
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
**
|
|
1054
|
-
|
|
1055
|
-
**
|
|
1056
|
-
|
|
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`).
|
|
7
|
+
>
|
|
8
|
+
> Protocol id: `vercel-setup/1.16` - pairs with `@tomorrowos/sdk` **0.9.70+** (`templates/cms-starter-v0`, Fluid Functions WebSockets).
|
|
9
|
+
>
|
|
10
|
+
> **Questions source of truth:** `VERCEL_QUESTIONS.md` - Agents must **open that file and paste each Q block verbatim**. Do not paraphrase.
|
|
11
|
+
> **Q1/Q2 = choice questions. Q3 = form only:** open **all seven optional inputs at once**, then **HARD STOP and WAIT** for submit or "skip". Never auto-skip because fields are optional. Never ask a branding choice first. Never show only Name + Primary colour. Logo = **Upload** above **Logo URL**.
|
|
12
|
+
> **Q4 = optional `CMS_PASSWORD`:** blank/skip is valid; can set later. Control Panel auth only — never Vercel Deployment Protection / whole-site password.
|
|
13
|
+
> **Copy questions verbatim from `VERCEL_QUESTIONS.md`.** Do not invent alternate Q2 options (no "Supabase Storage", no S3, no "disable media", no making Blob the recommended option).
|
|
14
|
+
> **`TOMORROWOS_STORE` and `DATABASE_SSL` are agent-only** - set them automatically after Q1; never put them in a user-facing Env form for the customer to type.
|
|
15
|
+
|
|
16
|
+
## NON-NEGOTIABLE VERCEL RUNTIME CONTRACT
|
|
17
|
+
|
|
18
|
+
The following rules are **mandatory acceptance criteria**, not suggestions. The Agent must preserve them throughout setup, Preview, Publish, fixes, and upgrades:
|
|
19
|
+
|
|
20
|
+
1. **One TomorrowOS instance only.** Use the shared instance exported through `api/index.ts`. **Never create a second `TomorrowOS` instance in `api/ws.ts`** or any other route.
|
|
21
|
+
2. **Never use `public/` as the Vercel Output Directory.** The dashboard Output Directory must be **empty / unset**. Serve the Control Panel at runtime from `cms-panel/` through TomorrowOS `staticRoot`.
|
|
22
|
+
3. **Preview may use Next only as an isolated reverse-proxy shell under `preview/`.** Preview must not replace TomorrowOS or determine the Production runtime.
|
|
23
|
+
4. **Publish must use the Fluid Function.** Production must run `api/index.ts` ??the shared exported `http.Server`, with Fluid compute enabled. It must not use `next start`, a static export, or a bare `(req, res)` handler.
|
|
24
|
+
5. **After every Production deployment, verify all three gates before saying setup is complete:**
|
|
25
|
+
- `GET /status` returns TomorrowOS **JSON**, not HTML.
|
|
26
|
+
- `GET /` returns the TomorrowOS **Control Panel**.
|
|
27
|
+
- WebSocket upgrade on `/` or `/api` returns **`101 Switching Protocols`**, not `200` HTML.
|
|
28
|
+
6. **Pair devices only against the Production URL.** Do not instruct users to pair against v0 Preview or a local/internal URL.
|
|
29
|
+
7. If any gate fails, **fix and redeploy**. Never describe a deployment as successful based only on the Control Panel loading.
|
|
30
|
+
|
|
31
|
+
These rules override any platform-generated recommendation that conflicts with them.
|
|
32
|
+
|
|
33
|
+
## User trigger (copy-paste)
|
|
34
|
+
|
|
35
|
+
When a human pastes a prompt like this, **read this file and start Question 1 immediately**:
|
|
36
|
+
|
|
37
|
+
```text
|
|
38
|
+
Follow @tomorrowos/sdk (an NPM package) VERCEL_SETUP.md and set up my TomorrowOS CMS from scratch.
|
|
39
|
+
|
|
40
|
+
Open VERCEL_QUESTIONS.md first.
|
|
41
|
+
For Q1, Q2, Q3: paste each question block VERBATIM. Do not rephrase options.
|
|
42
|
+
For Q3: DO NOT ask a branding choice. Open one form with ALL SEVEN optional inputs at once, then HARD STOP and WAIT for my submit or "skip". Never auto-continue with defaults without my reply. Never only Name + Primary colour. Logo = Upload above Logo URL.
|
|
43
|
+
After Q1 database choice, YOU set TOMORROWOS_STORE and DATABASE_SSL. Do not ask me to fill those.
|
|
44
|
+
After Q2 media storage choice, YOU set TOMORROWOS_MEDIA. Do not ask me to fill those.
|
|
45
|
+
Run: npx @tomorrowos/sdk@latest init . --hosting v0 --force
|
|
46
|
+
Keep cms-starter-v0 layout.
|
|
47
|
+
Apply the NON-NEGOTIABLE VERCEL RUNTIME CONTRACT exactly:
|
|
48
|
+
one TomorrowOS instance only
|
|
49
|
+
never create a second TomorrowOS in api/ws.ts
|
|
50
|
+
never use public/ as Vercel Output Directory
|
|
51
|
+
Preview Next only under preview/
|
|
52
|
+
Publish must be the Fluid Function
|
|
53
|
+
after deploy verify: GET /status ? JSON, GET / ? Control Panel, WebSocket on / or /api ? 101
|
|
54
|
+
pair devices against the Production URL only
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Alternate triggers:
|
|
58
|
+
|
|
59
|
+
- ?Set up TomorrowOS on Vercel using VERCEL_SETUP.md?
|
|
60
|
+
- ?Configure my TomorrowOS CMS (Vercel wizard)?
|
|
61
|
+
|
|
62
|
+
When any of these fire, **ignore `REPLIT_SETUP.md`** for the Q&A phase (you may skim Replit docs only for shared SDK semantics).
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## IRON RULE: Preview vs Publish (dual runtime)
|
|
67
|
+
|
|
68
|
+
> **This is the #1 deployability rule on Vercel. Non-negotiable.**
|
|
69
|
+
|
|
70
|
+
TomorrowOS CMS is **one Node process** that serves:
|
|
71
|
+
|
|
72
|
+
- Browser Control Panel at `GET /` (`public/` via `staticRoot`)
|
|
73
|
+
- Device **WebSocket** on the same origin (`https` ??players use `wss`)
|
|
74
|
+
|
|
75
|
+
### Production / Publish ??Node only (no Next.js)
|
|
76
|
+
|
|
77
|
+
| Required | Forbidden |
|
|
78
|
+
|----------|-----------|
|
|
79
|
+
| Single **Node HTTP server** entry (`tsx server.ts` / exported `http.Server` from `listen()`) | **Next.js as the Production CMS** (`next start`, Next App Router as the live app) |
|
|
80
|
+
| **Framework Preset = Other / `null`** (dashboard **and** project settings API) | Leaving Framework Preset as **Next.js** (causes `404: NOT_FOUND`) |
|
|
81
|
+
| **Fluid compute** enabled (needed for WebSockets on Vercel) | Classic short-lived serverless with no WebSocket / no Fluid |
|
|
82
|
+
| Durable store = **Postgres** (Supabase or Neon, pooled URL) | **SQLite / `data/*.db` as production store** on Vercel (ephemeral filesystem) |
|
|
83
|
+
| Media = **Cloudinary** or **Vercel Blob** (durable HTTPS URLs) | Relying on local `public/uploads` alone for production fleets |
|
|
84
|
+
| Env vars in **Vercel Project ??Settings ??Environment Variables** | Committing passwords / API secrets / `.env` with secrets |
|
|
85
|
+
| `cms.hostingTarget`: `"vercel"` in `brand.json` | Creating `.replit` / Replit artifact files |
|
|
86
|
+
|
|
87
|
+
**Publish / Production Deploy** must run **`TomorrowOS.listen(...)`** directly. Do **not** put Next.js in front of Production traffic.
|
|
88
|
+
|
|
89
|
+
### Publish `404: NOT_FOUND` ??mandatory prevention (read before every deploy)
|
|
90
|
+
|
|
91
|
+
The production URL showing:
|
|
92
|
+
|
|
93
|
+
```text
|
|
94
|
+
404: NOT_FOUND
|
|
95
|
+
Code: NOT_FOUND
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
almost always means **Vercel still thinks this is a Next.js / static deploy**, not a Node TomorrowOS server.
|
|
99
|
+
|
|
100
|
+
**Before every Production deploy, you MUST verify all of the following:**
|
|
101
|
+
|
|
102
|
+
1. **Project ??Settings ??General ??Framework Preset = Other** (or unset / null).
|
|
103
|
+
If it still says **Next.js**, patch it via Vercel API / CLI / dashboard **before** redeploying.
|
|
104
|
+
`"framework": null` in `vercel.json` alone is **not enough** if the dashboard still says Next.js.
|
|
105
|
+
2. **Install Command** = `npm install` / `pnpm install` / `yarn` (normal install).
|
|
106
|
+
3. **Build Command** = a **no-op that exits 0** (e.g. `node -e "process.exit(0)"` or `npm run build` where `build` is that no-op).
|
|
107
|
+
**Never** run `next build` for Production.
|
|
108
|
+
4. **Production entry** = Fluid **Vercel Function** `api/index.ts` that **`export default server`** from shared `cms-app.ts` (see [WebSockets docs](https://vercel.com/docs/functions/websockets)). Local `npm start` may still use root `server.ts`.
|
|
109
|
+
5. **Output Directory** = **empty / unset**. Do **not** set `outputDirectory: "."` as a fake static site.
|
|
110
|
+
6. **Do** use a single `api/index.ts` that exports the TomorrowOS `http.Server`. **Do not** create a second TomorrowOS in `api/ws.ts`, and **do not** use bare `(req, res)` handlers that drop upgrades.
|
|
111
|
+
7. If Preview needs Next, keep Next files **isolated** so Production auto-detect cannot latch onto them:
|
|
112
|
+
- Prefer `preview/` (or similar) for `next.config.*`, `app/`, `pages/` used only by v0 Preview, **or**
|
|
113
|
+
- Ensure Production Framework Preset is forced to **Other** and never runs `next` as the CMS.
|
|
114
|
+
|
|
115
|
+
**Setup is NOT complete** while Production `GET /` returns `404: NOT_FOUND` or WebSocket upgrade returns 200 HTML.
|
|
116
|
+
|
|
117
|
+
### Publish static trap ??`public/` served without Node (read this)
|
|
118
|
+
|
|
119
|
+
A common **worse** failure mode than `404: NOT_FOUND`:
|
|
120
|
+
|
|
121
|
+
- `GET /` loads the Control Panel HTML (from static `public/index.html`)
|
|
122
|
+
- `GET /status`, `/devices`, etc. return **HTML** (Vercel 404 page)
|
|
123
|
+
- The panel shows **CMS unreachable** and console:
|
|
124
|
+
`Unexpected token 'T', "The page c"... is not valid JSON`
|
|
125
|
+
|
|
126
|
+
**Root cause:** Vercel deployed `public/` as a **static site** (Output Directory = `public` or Framework=Other default *?public if it exists??). **No Function** is running TomorrowOS.
|
|
127
|
+
|
|
128
|
+
**Fix (Scheme A):** use `cms-panel/` (not `public/`), Fluid Function `api/index.ts` + rewrites, Output Directory empty. Static UI is served **at runtime** via `staticRoot` in `listen()`.
|
|
129
|
+
|
|
130
|
+
**Publish gate (mandatory):** after deploy, `GET /status` must return **JSON** starting with `{`, not HTML starting with `<` or `The page`. **And** WebSocket upgrade on `/` or `/api` must return **101**.
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
curl -s https://YOUR-PROJECT.vercel.app/status | head -c 80
|
|
134
|
+
# Expected: {"status":"success",...}
|
|
135
|
+
# Failure: The page could not be found OR <!DOCTYPE html>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**Setup is NOT complete** while `/status` returns HTML, the panel shows the JSON parse error, or devices cannot open `wss://`.
|
|
139
|
+
|
|
140
|
+
### Preview (v0 / Vercel Agent sandbox) ??Next.js shell allowed
|
|
141
|
+
|
|
142
|
+
v0 Preview is often a **Next.js runtime** hardwired to `next dev`. A raw `tsx server.ts` may listen successfully while Preview still shows a blank page, because the sandbox intercepts the public port with Next before requests reach TomorrowOS.
|
|
143
|
+
|
|
144
|
+
**For Preview only**, you **must** keep a thin Next.js host that **reverse-proxies** HTTP to TomorrowOS on an **internal** port.
|
|
145
|
+
|
|
146
|
+
| Preview requirement | Detail |
|
|
147
|
+
|---------------------|--------|
|
|
148
|
+
| Next.js remains the Preview host | Satisfies v0 / sandbox expectations |
|
|
149
|
+
| TomorrowOS on internal port | e.g. `TOMORROWOS_INTERNAL_PORT=3001` (not the public Preview port) |
|
|
150
|
+
| Transparent HTTP reverse proxy | Next route/middleware forwards `/` (and Control Panel paths) ??`http://127.0.0.1:3001` |
|
|
151
|
+
| Start both processes in Preview | Next + `tsx server.ts` (or a `dev:preview` script that starts both) |
|
|
152
|
+
|
|
153
|
+
**Preview WebSocket note:** HTTP proxy is enough for Control Panel HTML/API smoke checks. Device `wss` pairing may not work through a Next proxy in Preview ??that is acceptable. **Pair devices against the Production Publish URL**, not v0 Preview.
|
|
154
|
+
|
|
155
|
+
**Do not** replace TomorrowOS with a Next-only CMS. Next is a **Preview adapter**, not the product.
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Questionnaire scope (STRICT)
|
|
160
|
+
|
|
161
|
+
Ask **only** these questions, in **this exact order**. **Copy the ?Ask exactly??blocks verbatim** ??do not paraphrase options.
|
|
162
|
+
|
|
163
|
+
| Step | Section | When |
|
|
164
|
+
|------|---------|------|
|
|
165
|
+
| 1 | **Question 1** ? Database (Supabase ? Neon) | Always |
|
|
166
|
+
| 2 | **Question 2** ? Media (**Cloudinary recommended** ? Vercel Blob) | Always (after Q1) |
|
|
167
|
+
| 3 | **Question 3** - Brand form (`brand.json` only; **not a choice** - open all 7 optional inputs at once) | Always (after Q2) |
|
|
168
|
+
| 4 | **Question 4** - Optional CMS password Env `CMS_PASSWORD` (**optional**; blank = skip, can set later) | Always last |
|
|
169
|
+
|
|
170
|
+
**That is the complete list (Q1–Q4).** Do **not** ask how many screens / devices.
|
|
171
|
+
|
|
172
|
+
**Forbidden invented Question 2 options (never offer these in chat or UI):**
|
|
173
|
+
|
|
174
|
+
- ???Vercel Blob **(recommended)**??as option 1 ??Blob is option **2**, not recommended over Cloudinary
|
|
175
|
+
- ???Supabase Storage??/ S3 bucket / `SUPABASE_SERVICE_ROLE_KEY` for media
|
|
176
|
+
- ???No media storage??/ ?disable media uploads??/ ?remote URLs only??as a numbered choice
|
|
177
|
+
- ??Any media list that does not start with **Cloudinary (recommended)**
|
|
178
|
+
|
|
179
|
+
**Forbidden Env UX after Question 1:**
|
|
180
|
+
|
|
181
|
+
- ??Showing the user empty fields for `TOMORROWOS_STORE` or `DATABASE_SSL` to fill
|
|
182
|
+
- ??Asking in chat ?what should TOMORROWOS_STORE be???
|
|
183
|
+
- ??After DB choice + URL paste: **you** write those two vars silently (see Q1 Steps B/C/D)
|
|
184
|
+
|
|
185
|
+
**Forbidden extra questions (never ask, even if the user skips a step):**
|
|
186
|
+
|
|
187
|
+
- ??OpenAI / Anthropic / Gemini / any LLM API key (`sk-?`, etc.)
|
|
188
|
+
- ???AI pairing?? ?AI feature logic?? or inventing auth that needs an LLM key
|
|
189
|
+
- ??Asking the user to type `TOMORROWOS_STORE` or `DATABASE_SSL`
|
|
190
|
+
- Any secret not listed in Questions 1–4 (DB URL, media credentials, brand, optional `CMS_PASSWORD`)
|
|
191
|
+
|
|
192
|
+
TomorrowOS pairing and Control Panel logic live in `@tomorrowos/sdk`. They do **not** call OpenAI.
|
|
193
|
+
|
|
194
|
+
If the user **skips** a Question 2 option, do **not** invent a substitute question. Re-state Question 2 choices **verbatim** ? never pivot to LLM keys, Supabase Storage, or local disk uploads.
|
|
195
|
+
|
|
196
|
+
If the user says ?just set it up, don?t ask questions,??respond:
|
|
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.
|
|
199
|
+
|
|
200
|
+
Then ask **Question 1**.
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## Hard rules
|
|
205
|
+
|
|
206
|
+
1. **Open `VERCEL_QUESTIONS.md` first.** Paste Q1 → configure → paste Q2 → configure → paste Q3 → paste Q4. Never paraphrase question options. Never use `REPLIT_SETUP.md` as the questionnaire. **Never** ask for OpenAI or other LLM API keys.
|
|
207
|
+
2. **Question 2 options are fixed:** (1) Cloudinary **(recommended)**, (2) Vercel Blob. Do not reorder; do not invent Supabase Storage, local disk, or disable-uploads.
|
|
208
|
+
3. **Cloudinary:** open **one** Vercel Env popup with **all** Cloudinary fields together. Do **not** quiz one credential at a time.
|
|
209
|
+
4. **`TOMORROWOS_STORE` + `DATABASE_SSL` = agent-owned.** After Q1 choice (and URL if needed), set them yourself in Vercel Env Vars. **Never** include them as blank fields for the customer. Mapping: Supabase ? `TOMORROWOS_STORE=supabase` + `DATABASE_SSL=true`; Neon ? `TOMORROWOS_STORE=postgres` + `DATABASE_SSL=true`. **Do not offer SQLite as a questionnaire choice** ? it is ephemeral on Vercel and unsuitable for production.
|
|
210
|
+
5. **Do not invent** Cloudinary credentials, database URLs, Vercel Blob tokens, brand colours, **or LLM API keys**.
|
|
211
|
+
6. **Postgres env naming:** prefer **`SUPABASE_URL`** for Supabase; prefer **`DATABASE_URL`** for Neon.
|
|
212
|
+
7. **Always prefer pooled connection strings** (Supabase Session pooler **6543**; Neon pooled host).
|
|
213
|
+
8. **Prefer `npx @tomorrowos/sdk@latest init --hosting v0`**. For Replit/Railway use `npx @tomorrowos/sdk@latest init .`. Do not rebuild pairing / WebSocket APIs. Untagged `npx @tomorrowos/sdk init` is forbidden (stale cache).
|
|
214
|
+
9. **Never commit secrets.** Use Vercel Environment Variables.
|
|
215
|
+
10. **Skip Replit-only files.**
|
|
216
|
+
11. **SQLite is not a production store on Vercel.**
|
|
217
|
+
12. **Enable Fluid compute** for Production WebSockets.
|
|
218
|
+
13. **Production:** `api/index.ts` exports `http.Server` from `TomorrowOS.listen`. Not `next start`. No second TomorrowOS in `api/ws.ts`.
|
|
219
|
+
14. **Preview:** Next reverse-proxy shell when v0 is Next-hardwired.
|
|
220
|
+
15. After Q&A: **configure ??install ??deploy ??minimal verify**.
|
|
221
|
+
16. **No inventing CMS login** from a branding URL.
|
|
222
|
+
17. **Skip → invent.** Stay on Q1–Q4 only.
|
|
223
|
+
18. **Publish gate:** `/status` JSON + WebSocket 101; no static `public/` trap.
|
|
224
|
+
19. **Q3 is a form, not a question ? and you must WAIT.** After Q2, open one dialog with **all seven** optional inputs visible together, then **stop and wait** for submit or explicit **"skip"**. **Never** auto-continue because fields are optional. **Never** ask a preliminary branding choice. **Never** show only Product Name + Primary colour. **Never** use multiple-choice / Option A-B for branding. Logo = **Upload** above **Logo URL**.
|
|
225
|
+
|
|
226
|
+
**Question order:** Q1 -> (auto-set store env) -> Q2 -> Q3 form (**wait for user**) -> Q4 optional `CMS_PASSWORD` -> execution checklist.
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Scheme A ??Vercel Functions + WebSockets (mandatory for Production)
|
|
231
|
+
|
|
232
|
+
> **Goal:** Preview may use Next + proxy; **Publish** runs TomorrowOS as a **Vercel Function** (`api/index.ts`) on **Fluid**, with WebSocket support per [Vercel WebSockets docs](https://vercel.com/docs/functions/websockets).
|
|
233
|
+
> **Scope:** Layout + `vercel.json` + dashboard. Prefer `npx @tomorrowos/sdk@latest init --hosting v0` (`cms-starter-v0`).
|
|
234
|
+
> **Do not** break Replit/Railway: those keep using default `init` ??`cms-starter` (root `server.ts` + `server.listen`, no `api/`).
|
|
235
|
+
|
|
236
|
+
### Why Scheme A exists
|
|
237
|
+
|
|
238
|
+
| Layer | Preview (v0) | Publish (Production) |
|
|
239
|
+
|-------|----------------|----------------------|
|
|
240
|
+
| Host | Next.js shell + HTTP proxy | **Vercel Function** `api/index.ts` ??`export default server` |
|
|
241
|
+
| Static UI | Proxied from TomorrowOS | Served by **`staticRoot`** (`cms-panel/`) inside `listen()` ??**not** Vercel Output Directory |
|
|
242
|
+
| WebSocket | Optional / may fail in Preview | **Required** ??Fluid Function upgrade (`wss://` ??`/` rewritten to `/api`, or direct `/api`) |
|
|
243
|
+
| Next files | Allowed under `preview/` | **Must not** live at project root |
|
|
244
|
+
|
|
245
|
+
**Root cause of ?Control Panel works, TV cannot connect??** HTTP can be served without WebSocket upgrades. Classic root-only `server.ts` / plain serverless handlers often return **200 HTML** on `Upgrade` instead of **101**. Official pattern is a Function that exports `http.Server` with `ws` attached ([docs](https://vercel.com/docs/functions/websockets)).
|
|
246
|
+
|
|
247
|
+
### A1 ??Project layout (Agent must enforce)
|
|
248
|
+
|
|
249
|
+
**Production (Vercel / v0 starter):**
|
|
250
|
+
|
|
251
|
+
```
|
|
252
|
+
cms-app.ts ??shared TomorrowOS.listen + export const server
|
|
253
|
+
api/index.ts ??export { server as default } from "../cms-app.js"
|
|
254
|
+
server.ts ??local/Preview: same default export (npm start / internal port)
|
|
255
|
+
vercel.json ??fluid + rewrites + functions.maxDuration
|
|
256
|
+
package.json
|
|
257
|
+
brand.json
|
|
258
|
+
cms-panel/ ??CMS static UI (NOT named public/ ??avoids static-output trap)
|
|
259
|
+
preview/ ??Next proxy for v0 Preview only
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
**Preview only ??keep ALL Next under `preview/`:**
|
|
263
|
+
|
|
264
|
+
```
|
|
265
|
+
preview/
|
|
266
|
+
next.config.mjs
|
|
267
|
+
app/
|
|
268
|
+
??
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
**Do NOT:**
|
|
272
|
+
|
|
273
|
+
- ??Leave `next.config.mjs` + `app/` at project root
|
|
274
|
+
- ??Set Output Directory to `public`, `cms-panel`, or `.`
|
|
275
|
+
- ??Create a **second** TomorrowOS instance in `api/ws.ts` (separate isolate = broken pairing memory)
|
|
276
|
+
- ??Use `(req, res) =>` serverless handlers that drop `upgrade`
|
|
277
|
+
- ??Put `startCommand` / `processes` in `vercel.json` (v0 schema rejects them)
|
|
278
|
+
|
|
279
|
+
**`package.json` scripts:**
|
|
280
|
+
|
|
281
|
+
```json
|
|
282
|
+
{
|
|
283
|
+
"scripts": {
|
|
284
|
+
"dev": "tsx watch server.ts",
|
|
285
|
+
"dev:preview": "concurrently -k \"npm:dev:tomorrowos\" \"npm:dev:next\"",
|
|
286
|
+
"dev:tomorrowos": "cross-env TOMORROWOS_INTERNAL_PORT=3001 tsx watch server.ts",
|
|
287
|
+
"dev:next": "cd preview && next dev -p 3000",
|
|
288
|
+
"start": "tsx server.ts",
|
|
289
|
+
"build": "node -e \"process.exit(0)\""
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
- **Local / Railway-style:** `npm start` ??`tsx server.ts` ??SDK calls `server.listen` (no `VERCEL` env).
|
|
295
|
+
- **Vercel Production:** Function loads `api/index.ts`; SDK skips `listen` when `process.env.VERCEL` is set and Vercel owns the socket.
|
|
296
|
+
|
|
297
|
+
### A2 ??Function entry (matches Vercel WebSockets docs)
|
|
298
|
+
|
|
299
|
+
```ts
|
|
300
|
+
// api/index.ts
|
|
301
|
+
export { server as default } from "../cms-app.js";
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
```ts
|
|
305
|
+
// cms-app.ts (shared)
|
|
306
|
+
const server = tomorrowos.listen({
|
|
307
|
+
port: Number(process.env.PORT) || 3000,
|
|
308
|
+
host: "0.0.0.0",
|
|
309
|
+
staticRoot: join(__dirname, "cms-panel"),
|
|
310
|
+
});
|
|
311
|
+
export { server, tomorrowos };
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
SDK behaviour (0.9.32+):
|
|
315
|
+
|
|
316
|
+
- Accepts WebSocket upgrades on `/`, `/api`, `/api/ws` (players may use either)
|
|
317
|
+
- When `VERCEL` is set: **`autoListen` defaults to false** ??do not bind a port yourself
|
|
318
|
+
- Pattern aligns with docs: create `http.Server`, attach `ws`, **`export default server`**
|
|
319
|
+
|
|
320
|
+
### A3 ??`vercel.json` (Production)
|
|
321
|
+
|
|
322
|
+
```json
|
|
323
|
+
{
|
|
324
|
+
"fluid": true,
|
|
325
|
+
"framework": null,
|
|
326
|
+
"installCommand": "npm install",
|
|
327
|
+
"buildCommand": "npm run build",
|
|
328
|
+
"functions": {
|
|
329
|
+
"api/index.ts": {
|
|
330
|
+
"maxDuration": 300,
|
|
331
|
+
"memory": 1024,
|
|
332
|
+
"includeFiles": "{cms-panel/**,brand.json,assets/**}"
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
"rewrites": [
|
|
336
|
+
{ "source": "/((?!api/).*)", "destination": "/api" }
|
|
337
|
+
]
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
**`maxDuration` plan limits:** Hobby / many default plans allow **1??00** seconds. **800** requires a higher Vercel plan. Prefer **`300`** unless the account already allows more ??otherwise Publish fails with *?maxDuration must be between 1 and 300??upgrade your plan??.
|
|
342
|
+
|
|
343
|
+
**Why rewrites:** Public URLs stay `https://app.vercel.app/` and `/status` while the Function mount is `/api`. Device `wss://app.vercel.app/` is rewritten to the same Function so one isolate handles HTTP + WS.
|
|
344
|
+
|
|
345
|
+
**v0 schema:** never add `startCommand` or `processes`.
|
|
346
|
+
|
|
347
|
+
**Critical:**
|
|
348
|
+
|
|
349
|
+
- `"fluid": true` is required for WebSockets
|
|
350
|
+
- **Do not** set `outputDirectory` to `cms-panel` / `public`
|
|
351
|
+
- `"buildCommand"` = no-op ??**never** `next build` for Production
|
|
352
|
+
- Dashboard Output Directory must stay **empty**
|
|
353
|
+
|
|
354
|
+
### A4 ??Vercel Project Settings (dashboard ??mandatory)
|
|
355
|
+
|
|
356
|
+
| Setting | Required value |
|
|
357
|
+
|---------|----------------|
|
|
358
|
+
| Framework Preset | **Other** / null ??**not Next.js** |
|
|
359
|
+
| Install Command | `npm install` |
|
|
360
|
+
| Build Command | `npm run build` (no-op) |
|
|
361
|
+
| **Output Directory** | **empty** |
|
|
362
|
+
| Fluid compute | **On** |
|
|
363
|
+
| WebSockets | Account must allow Functions WebSockets (see docs ?Permissions Required?? |
|
|
364
|
+
|
|
365
|
+
### A5 ??Publish acceptance (all must pass)
|
|
366
|
+
|
|
367
|
+
1. `curl -s https://PROD/status` ??JSON (`{"status":...}`)
|
|
368
|
+
2. `curl -s https://PROD/` ??Control Panel HTML
|
|
369
|
+
3. **WebSocket upgrade returns 101**, not 200 HTML:
|
|
370
|
+
|
|
371
|
+
```bash
|
|
372
|
+
curl.exe -i --http1.1 ^
|
|
373
|
+
-H "Connection: Upgrade" -H "Upgrade: websocket" ^
|
|
374
|
+
-H "Sec-WebSocket-Version: 13" ^
|
|
375
|
+
-H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" ^
|
|
376
|
+
"https://PROD/"
|
|
377
|
+
# Also try: https://PROD/api
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
4. Control Panel does **not** show CMS unreachable / `Unexpected token 'T'`
|
|
381
|
+
5. Fluid on; Framework = Other
|
|
382
|
+
|
|
383
|
+
If (1)+(2) pass but (3) fails ??Function is serving HTTP only. Re-check Fluid, `api/index.ts` default export, rewrites, and WebSockets permission. **Do not** invent a second `api/ws.ts` TomorrowOS instance.
|
|
384
|
+
|
|
385
|
+
### A6 ??What Scheme A does **not** require
|
|
386
|
+
|
|
387
|
+
- ??No Next.js as Production CMS
|
|
388
|
+
- ??No replacing SDK transport with hand-rolled pairing
|
|
389
|
+
- ??No SQLite as production store on Vercel
|
|
390
|
+
- ??No changes to Replit `cms-starter` layout for Vercel-only fixes
|
|
391
|
+
|
|
392
|
+
### A7 ??If v0 Publish still fails (Path B / C)
|
|
393
|
+
|
|
394
|
+
**Path B:** Deploy with `vercel --prod` or Git integration (full Vercel), same Scheme A layout.
|
|
395
|
+
|
|
396
|
+
**Path C:** Host CMS on **Railway / Fly.io / Replit** (long-lived Node). Use Vercel only for Preview if needed.
|
|
397
|
+
|
|
398
|
+
### A8 ??Migrate an existing broken Vercel project
|
|
399
|
+
|
|
400
|
+
1. Scaffold/compare with `cms-starter-v0` (`init --hosting v0`) or copy:
|
|
401
|
+
- `cms-app.ts`, `api/index.ts`, `server.ts`, `vercel.json`
|
|
402
|
+
- rename `public/` ??`cms-panel/` if still present
|
|
403
|
+
2. Ensure **one** Function exports the **same** `server` from `cms-app.ts`
|
|
404
|
+
3. Fluid on ??Redeploy
|
|
405
|
+
4. Verify A5 (especially WebSocket 101)
|
|
406
|
+
5. On TVs: enter `https://YOUR.vercel.app/` (players also try `/api` automatically on vercel.app hosts)
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
## Runtime & Vercel deploy rules (mandatory)
|
|
411
|
+
|
|
412
|
+
### Production runtime (Publish)
|
|
413
|
+
|
|
414
|
+
- Serve TomorrowOS via **Vercel Function** `api/index.ts` ??`export default server` (Fluid + WebSockets).
|
|
415
|
+
- Keep root **`server.ts`** for **local** `npm start` / Preview internal port only (same `cms-app.ts`).
|
|
416
|
+
- Keep **`@tomorrowos/sdk`** (0.9.32+) and put **`tsx` in `dependencies`** for local start.
|
|
417
|
+
- Node **20+**.
|
|
418
|
+
- **Do not** replace the SDK with a hand-rolled server that drops WebSocket upgrades.
|
|
419
|
+
- **Forbidden as the Production CMS:** `next start`, static export only, or a bare `(req,res)` handler without `http.Server` + `ws`.
|
|
420
|
+
|
|
421
|
+
### Expected `package.json` scripts
|
|
422
|
+
|
|
423
|
+
```json
|
|
424
|
+
{
|
|
425
|
+
"scripts": {
|
|
426
|
+
"dev": "tsx watch server.ts",
|
|
427
|
+
"dev:preview": "concurrently -k \"npm:dev:tomorrowos\" \"npm:dev:next\"",
|
|
428
|
+
"dev:tomorrowos": "cross-env PORT=3001 TOMORROWOS_INTERNAL_PORT=3001 tsx watch server.ts",
|
|
429
|
+
"dev:next": "cd preview && next dev -p 3000",
|
|
430
|
+
"start": "tsx server.ts",
|
|
431
|
+
"build": "node -e \"process.exit(0)\""
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
Adapt script names / process runners as needed. **Invariant:**
|
|
437
|
+
|
|
438
|
+
- **Local `npm start`** = TomorrowOS Node with `server.listen`.
|
|
439
|
+
- **Vercel Production** = Function `api/index.ts` (SDK `autoListen=false` when `VERCEL` is set).
|
|
440
|
+
- **`build`** = no-op exit 0 (never `next build` for Production).
|
|
441
|
+
- **Preview / v0** = Next on public port + TomorrowOS on internal port + proxy.
|
|
442
|
+
|
|
443
|
+
### Minimal dependencies (Production)
|
|
444
|
+
|
|
445
|
+
```json
|
|
446
|
+
{
|
|
447
|
+
"type": "module",
|
|
448
|
+
"engines": {
|
|
449
|
+
"node": ">=20"
|
|
450
|
+
},
|
|
451
|
+
"dependencies": {
|
|
452
|
+
"@tomorrowos/sdk": "latest",
|
|
453
|
+
"dotenv": "^17.2.3",
|
|
454
|
+
"tsx": "^4.19.0"
|
|
455
|
+
},
|
|
456
|
+
"devDependencies": {
|
|
457
|
+
"@types/node": "^20.0.0",
|
|
458
|
+
"typescript": "^5.5.0"
|
|
459
|
+
},
|
|
460
|
+
"scripts": {
|
|
461
|
+
"dev": "tsx watch server.ts",
|
|
462
|
+
"start": "tsx server.ts",
|
|
463
|
+
"build": "node -e \"process.exit(0)\""
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
Use the latest published `@tomorrowos/sdk` when scaffolding (do not invent versions).
|
|
469
|
+
|
|
470
|
+
Add nothing extra for Blob when using `@tomorrowos/sdk@0.9.70+` (`@vercel/blob` is bundled). Only ensure `BLOB_READ_WRITE_TOKEN` + `TOMORROWOS_MEDIA=vercel-blob` when the user chooses **Vercel Blob** in Question 2.
|
|
471
|
+
|
|
472
|
+
For Preview shell only, add as needed: `next`, `react`, `react-dom`, and a process runner (`concurrently` or equivalent). These must **not** become the Production CMS.
|
|
473
|
+
|
|
474
|
+
### `server.ts` pattern (Vercel-adapted ??Production)
|
|
475
|
+
|
|
476
|
+
Keep the server at **project root** (e.g. `server.ts` / `server.mts`). Do **not** move Production into `api/` solely to satisfy Vercel routing myths.
|
|
477
|
+
|
|
478
|
+
```ts
|
|
479
|
+
import "dotenv/config";
|
|
480
|
+
import { readFileSync } from "fs";
|
|
481
|
+
import { fileURLToPath } from "url";
|
|
482
|
+
import { dirname, join } from "path";
|
|
483
|
+
import { createTomorrowOSStore, TomorrowOS } from "@tomorrowos/sdk";
|
|
484
|
+
|
|
485
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
486
|
+
const brand = JSON.parse(readFileSync(join(__dirname, "brand.json"), "utf8"));
|
|
487
|
+
|
|
488
|
+
const store = createTomorrowOSStore({
|
|
489
|
+
// Supabase: SUPABASE_URL (pooler). Neon: DATABASE_URL (pooled).
|
|
490
|
+
databaseUrl:
|
|
491
|
+
process.env.SUPABASE_URL ||
|
|
492
|
+
process.env.NEON_DATABASE_URL ||
|
|
493
|
+
process.env.DATABASE_URL,
|
|
494
|
+
sqlitePath: join(__dirname, "data", "tomorrowos.db") // local/dev fallback only
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
const tomorrowos = new TomorrowOS({ brand, store });
|
|
498
|
+
|
|
499
|
+
const port =
|
|
500
|
+
Number(process.env.TOMORROWOS_INTERNAL_PORT) ||
|
|
501
|
+
Number(process.env.PORT) ||
|
|
502
|
+
3000;
|
|
503
|
+
|
|
504
|
+
const server = tomorrowos.listen({
|
|
505
|
+
port,
|
|
506
|
+
host: "0.0.0.0",
|
|
507
|
+
staticRoot: join(__dirname, "public")
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
// Vercel / Node server capture: export the HTTP server, not only TomorrowOS.
|
|
511
|
+
export default server;
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
- **Production:** `PORT` from Vercel ??TomorrowOS listens on that port (no Next).
|
|
515
|
+
- **Preview:** set `TOMORROWOS_INTERNAL_PORT=3001` (or similar) so Next can own `:3000` and proxy inward.
|
|
516
|
+
|
|
517
|
+
Load env with `dotenv` for local/`vercel dev`. On production Deploy, Vercel injects Environment Variables natively.
|
|
518
|
+
|
|
519
|
+
### Preview adapter (v0 / Next reverse proxy) ??required when Preview is Next-hardwired
|
|
520
|
+
|
|
521
|
+
Detect Preview / v0 (Next already present, or Preview blank while TomorrowOS logs ?listening??. Then:
|
|
522
|
+
|
|
523
|
+
1. Keep a **minimal** Next app **isolated when possible** (e.g. under `preview/`) so Production Framework detection does not latch onto root `next.config.*`.
|
|
524
|
+
2. TomorrowOS listens on **`127.0.0.1:TOMORROWOS_INTERNAL_PORT`** (default **3001**).
|
|
525
|
+
3. Add a **catch-all** Next route (or middleware) that proxies HTTP to TomorrowOS:
|
|
526
|
+
|
|
527
|
+
```ts
|
|
528
|
+
// Example: preview app proxy handler
|
|
529
|
+
// Forward method, headers, and body to http://127.0.0.1:3001/<path>
|
|
530
|
+
// Return the upstream status, headers, and body to the browser.
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
Preferred UX: proxy **`/`** itself (rewrites) so the Preview URL shows the Control Panel at `/`, not only under `/api/...`.
|
|
534
|
+
|
|
535
|
+
4. Start **both** processes for Preview (`dev:preview` pattern above).
|
|
536
|
+
5. Document in the final summary: **Preview = Next shell + proxy; Publish = Node only.**
|
|
537
|
+
|
|
538
|
+
**Forbidden:**
|
|
539
|
+
|
|
540
|
+
- Making Next the Production CMS or the only server after Publish.
|
|
541
|
+
- Deleting TomorrowOS `server.ts` because Preview needed Next.
|
|
542
|
+
- Claiming setup complete if Production entrypoint is still `next start`.
|
|
543
|
+
- Claiming setup complete if Production `GET /` is `404: NOT_FOUND`.
|
|
544
|
+
- Creating root `api/index.*` + `routes` catch-all that exports a raw `http.Server` ?to fix 404??
|
|
545
|
+
|
|
546
|
+
### `vercel.json` (Production ??explicit Node, not Next)
|
|
547
|
+
|
|
548
|
+
Write `vercel.json` so Production cannot be mistaken for Next. Minimum:
|
|
549
|
+
|
|
550
|
+
```json
|
|
551
|
+
{
|
|
552
|
+
"fluid": true,
|
|
553
|
+
"framework": null,
|
|
554
|
+
"installCommand": "npm install",
|
|
555
|
+
"buildCommand": "npm run build"
|
|
556
|
+
}
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
Use `pnpm` / `yarn` variants of install/build if that is the project?s package manager.
|
|
560
|
+
|
|
561
|
+
**Do not** add `startCommand`, `processes`, or `outputDirectory` to `vercel.json` when v0 schema rejects them. Start = `package.json` `"start": "tsx server.ts"`. Output Directory = **empty in dashboard**.
|
|
562
|
+
|
|
563
|
+
**Also force Project Settings (dashboard or Vercel API) ??this is mandatory (see Scheme A4):**
|
|
564
|
+
|
|
565
|
+
| Setting | Required value |
|
|
566
|
+
|---------|----------------|
|
|
567
|
+
| Framework Preset | **Other** / null ??**not Next.js** |
|
|
568
|
+
| Install Command | `npm install` (or pnpm/yarn) |
|
|
569
|
+
| Build Command | `npm run build` (no-op exit 0) |
|
|
570
|
+
| Output Directory | **empty / null** ??**NOT `public`** |
|
|
571
|
+
| Start Command | `npm run start` ??`tsx server.ts` (or `.mts`) ??set in **dashboard**, not `vercel.json` |
|
|
572
|
+
| Fluid compute | **On** |
|
|
573
|
+
|
|
574
|
+
If `next` / `next.config.*` exist for Preview and the dashboard still auto-selects Next.js:
|
|
575
|
+
|
|
576
|
+
1. Patch Framework Preset to **Other** via API/CLI/dashboard.
|
|
577
|
+
2. Redeploy.
|
|
578
|
+
3. Hit Production `GET /status` ??must be **JSON**, not HTML.
|
|
579
|
+
4. Hit Production `GET /` ??must be Control Panel HTML, not `404: NOT_FOUND`.
|
|
580
|
+
|
|
581
|
+
If Vercel?s UI and this file disagree on bundling, **prefer whatever keeps a single long-lived Node HTTP server with WebSocket upgrade on `/` for Production**. Document the adaptation in the final summary.
|
|
582
|
+
|
|
583
|
+
### WebSocket / multi-instance note
|
|
584
|
+
|
|
585
|
+
- Device sockets are pinned to **one** function/instance. TomorrowOS device maps are **in-memory**.
|
|
586
|
+
- 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).
|
|
587
|
+
- Always use **Postgres** (Supabase or Neon) so pairings/playlists survive cold starts and deploys.
|
|
588
|
+
- **Pair TVs to the Production HTTPS origin**, not the v0 Preview URL.
|
|
589
|
+
|
|
590
|
+
### What not to create
|
|
591
|
+
|
|
592
|
+
- ??`.replit`, `.replit-artifact`
|
|
593
|
+
- ??Next.js as the **Production** CMS (App Router replacing TomorrowOS)
|
|
594
|
+
- ??Root `api/` catch-all exporting raw `http.Server` to ?fix??Publish 404
|
|
595
|
+
- ??Separate ?API project??without the Control Panel
|
|
596
|
+
- ??Committing database passwords, Cloudinary secrets, or Blob tokens
|
|
597
|
+
|
|
598
|
+
---
|
|
599
|
+
|
|
600
|
+
## Question 1 ? Database (always)
|
|
601
|
+
|
|
602
|
+
> **Do not** ask screen counts. Present both options; recommend **Supabase** for Vercel fleets.
|
|
603
|
+
> **After the user picks a database:** collect the URL if needed, then **you** set `TOMORROWOS_STORE` + `DATABASE_SSL` (see mapping below). The customer must **never** be asked to type those two names or values.
|
|
604
|
+
> **Forbidden:** offering SQLite as a user-facing Q1 choice (ephemeral filesystem on Vercel).
|
|
605
|
+
|
|
606
|
+
### Agent-owned env mapping (mandatory)
|
|
607
|
+
|
|
608
|
+
| User chose | You set (silently) | User only provides |
|
|
609
|
+
|------------|--------------------|--------------------|
|
|
610
|
+
| 1 Supabase | `TOMORROWOS_STORE=supabase`, `DATABASE_SSL=true` | `SUPABASE_URL` (pooler string) |
|
|
611
|
+
| 2 Neon | `TOMORROWOS_STORE=postgres`, `DATABASE_SSL=true` | `DATABASE_URL` (pooled string) |
|
|
612
|
+
|
|
613
|
+
If you open an Env configuration UI for Q1, fields visible to the user may include **only** `SUPABASE_URL` or `DATABASE_URL`. **Do not** show blank `TOMORROWOS_STORE` / `DATABASE_SSL` inputs ? write those yourself in the same step.
|
|
614
|
+
|
|
615
|
+
### Step A ? Ask storage choice
|
|
616
|
+
|
|
617
|
+
**Ask exactly:**
|
|
618
|
+
|
|
619
|
+
> Which database should TomorrowOS use on Vercel?
|
|
620
|
+
>
|
|
621
|
+
> **1. Supabase Postgres (recommended)** ? durable pairing/playlists; use the **Session pooler** URL (`*.pooler.supabase.com:6543`).
|
|
622
|
+
>
|
|
623
|
+
> **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).
|
|
624
|
+
>
|
|
625
|
+
> Reply with **1** or **2** (or "Supabase" / "Neon").
|
|
626
|
+
|
|
627
|
+
### Step B ??If **1 / Supabase**
|
|
628
|
+
|
|
629
|
+
**Ask exactly (connection string only ??not TOMORROWOS_STORE / DATABASE_SSL):**
|
|
630
|
+
|
|
631
|
+
> Paste your Supabase Postgres connection string. I will store it as **`SUPABASE_URL`**. I will set **`TOMORROWOS_STORE`** and **`DATABASE_SSL`** for you automatically.
|
|
632
|
+
>
|
|
633
|
+
> In Supabase: **Project Settings ??Database ??Connection string ??Connection pooling** (Session mode).
|
|
634
|
+
> Preferred shape:
|
|
635
|
+
> `postgresql://postgres.[PROJECT]:[PASSWORD]@aws-0-[REGION].pooler.supabase.com:6543/postgres`
|
|
636
|
+
>
|
|
637
|
+
> **Do not** use the direct host `db.*.supabase.co:5432` for Vercel.
|
|
638
|
+
|
|
639
|
+
**You must then (automatic ??do NOT ask the user to type store/ssl vars):**
|
|
640
|
+
|
|
641
|
+
1. Set Vercel Env Vars in one step:
|
|
642
|
+
- `SUPABASE_URL=<user pooler string>` ??from user
|
|
643
|
+
- `TOMORROWOS_STORE=supabase` ??**you set; never ask**
|
|
644
|
+
- `DATABASE_SSL=true` ??**you set; never ask**
|
|
645
|
+
2. Wire `cms-app.ts` / `server.ts` store as in **Runtime & Vercel deploy rules**.
|
|
646
|
+
3. Optional committed `.env.example` with **placeholders only**.
|
|
647
|
+
4. **Do not** commit the real connection string.
|
|
648
|
+
5. **Do not** open a form whose blank fields include `TOMORROWOS_STORE` or `DATABASE_SSL`.
|
|
649
|
+
|
|
650
|
+
### Step C ??If **2 / Neon**
|
|
651
|
+
|
|
652
|
+
**Ask exactly (connection string only):**
|
|
653
|
+
|
|
654
|
+
> Paste your **Neon pooled** Postgres connection string. I will store it as **`DATABASE_URL`**. I will set **`TOMORROWOS_STORE`** and **`DATABASE_SSL`** for you automatically.
|
|
655
|
+
>
|
|
656
|
+
> In Neon: **Dashboard ??Connection details ??Pooled connection**.
|
|
657
|
+
> Typical shape:
|
|
658
|
+
> `postgresql://[user]:[password]@[endpoint]-pooler.[region].aws.neon.tech/[dbname]?sslmode=require`
|
|
659
|
+
|
|
660
|
+
**You must then (automatic):**
|
|
661
|
+
|
|
662
|
+
1. Set Vercel Env Vars:
|
|
663
|
+
- `DATABASE_URL=<user pooled string>` ??from user
|
|
664
|
+
- `TOMORROWOS_STORE=postgres` ??**you set; never ask**
|
|
665
|
+
- `DATABASE_SSL=true` ??**you set; never ask**
|
|
666
|
+
2. Wire `createTomorrowOSStore` for Postgres.
|
|
667
|
+
3. **Do not** commit the real connection string.
|
|
668
|
+
4. **Do not** ask the user to fill `TOMORROWOS_STORE` or `DATABASE_SSL` in chat or Env UI.
|
|
669
|
+
5. If you see `ENETUNREACH` on `:5432`, switch to Neon's **pooled** URL.
|
|
670
|
+
|
|
671
|
+
**Later in Question 3:** set `cms.hostingTarget` to **`"vercel"`** (all Q1 branches).
|
|
672
|
+
|
|
673
|
+
---
|
|
674
|
+
|
|
675
|
+
## Question 2 ??Media storage
|
|
676
|
+
|
|
677
|
+
> **This question is only about media files (images/videos).**
|
|
678
|
+
> It is **not** about OpenAI, AI pairing, or LLM keys. If you are about to ask for an `sk-` key, **stop** ??you are off-protocol.
|
|
679
|
+
> **Copy the two options below verbatim.** Wrong examples that must **never** appear: "Vercel Blob (recommended)", "Supabase Storage", "No media storage / disable uploads", "local disk only".
|
|
680
|
+
|
|
681
|
+
### Step A ? Ask storage choice
|
|
682
|
+
|
|
683
|
+
**Ask exactly (wording must match ? Cloudinary is recommended):**
|
|
684
|
+
|
|
685
|
+
> How should playlist media (images/videos) be stored?
|
|
686
|
+
>
|
|
687
|
+
> **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.**
|
|
688
|
+
>
|
|
689
|
+
> **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.
|
|
690
|
+
>
|
|
691
|
+
> Reply with **1** or **2** (or "Cloudinary" / "Vercel Blob").
|
|
692
|
+
|
|
693
|
+
If you catch yourself about to offer Blob-as-recommended, Supabase Storage, local disk, or "no media" ? **stop and paste the block above instead**.
|
|
694
|
+
|
|
695
|
+
### Step B ??If **1 / Cloudinary** (same Question 2 ??**one Env popup**)
|
|
696
|
+
|
|
697
|
+
> **Critical UX rule:** Immediately open the **Vercel Environment Variables configuration popup / form** with **all Cloudinary fields in one UI**. Do **not** ask for cloud name, then key, then secret in separate chat messages.
|
|
698
|
+
|
|
699
|
+
**Preferred:** Vercel Env Vars UI (or Agent secrets form) with these fields together:
|
|
700
|
+
|
|
701
|
+
| Field | Env name | Required |
|
|
702
|
+
|-------|----------|----------|
|
|
703
|
+
| Cloud name | `CLOUDINARY_CLOUD_NAME` | Yes |
|
|
704
|
+
| API Key | `CLOUDINARY_API_KEY` | Yes |
|
|
705
|
+
| API Secret | `CLOUDINARY_API_SECRET` | Yes |
|
|
706
|
+
| Folder (optional) | `CLOUDINARY_FOLDER` | No (e.g. `tomorrowos`) |
|
|
707
|
+
|
|
708
|
+
**If a multi-field popup is unavailable, ask once in a single message** (still not three turns):
|
|
709
|
+
|
|
710
|
+
> Paste Cloudinary credentials from [cloudinary.com/console](https://cloudinary.com/console) (I will store them as Vercel Env Vars):
|
|
711
|
+
>
|
|
712
|
+
> 1. `CLOUDINARY_CLOUD_NAME`
|
|
713
|
+
> 2. `CLOUDINARY_API_KEY`
|
|
714
|
+
> 3. `CLOUDINARY_API_SECRET`
|
|
715
|
+
> 4. optional `CLOUDINARY_FOLDER`
|
|
716
|
+
|
|
717
|
+
**You must then:**
|
|
718
|
+
|
|
719
|
+
1. Save all three required Env Vars for Production + Preview + Development as appropriate.
|
|
720
|
+
2. **Do not** invent placeholders.
|
|
721
|
+
3. **Do not** proceed to Question 3 until all three required vars exist.
|
|
722
|
+
4. **Do not** run a formal Cloudinary upload test unless the user asks.
|
|
723
|
+
|
|
724
|
+
The SDK auto-detects these env vars.
|
|
725
|
+
|
|
726
|
+
### Step C - If **2 / Vercel Blob** (same Question 2)
|
|
727
|
+
|
|
728
|
+
**Ask exactly:**
|
|
729
|
+
|
|
730
|
+
> I will enable **Vercel Blob** for media uploads.
|
|
731
|
+
>
|
|
732
|
+
> 1. In the Vercel project: **Storage ? Create ? Blob** (or link an existing Blob store to this project).
|
|
733
|
+
> 2. Confirm **`BLOB_READ_WRITE_TOKEN`** is available (Vercel usually injects it when Blob is linked).
|
|
734
|
+
>
|
|
735
|
+
> Paste the token only if it is not already set in your project Env Vars. Do you already have Blob linked on this Vercel project? (yes / no)
|
|
736
|
+
|
|
737
|
+
**You must then:**
|
|
738
|
+
|
|
739
|
+
1. Set Vercel Env Var: `BLOB_READ_WRITE_TOKEN=<token>` (if not auto-injected). Prefer the Env popup when available.
|
|
740
|
+
2. Set Vercel Env Var yourself (agent-owned): `TOMORROWOS_MEDIA=vercel-blob`. **Do not** ask the user to type this.
|
|
741
|
+
3. Ensure `@tomorrowos/sdk@0.9.70+` is installed (`@vercel/blob` is a transitive dependency ? no project-level Blob bridge / custom `put()` middleware).
|
|
742
|
+
4. The SDK **natively** uploads via Vercel Blob when the token is present (same HTTP routes as local: `/media/upload` and chunked complete). Returned asset URLs are absolute `https://*.public.blob.vercel-storage.com/...`.
|
|
743
|
+
5. Confirm Control Panel **Media Server** shows provider **Blob** (not Local). Do **not** invent status copy about "Blob bridge" or paste the URL pattern as a status detail.
|
|
744
|
+
6. Do **not** proceed to Question 3 until `BLOB_READ_WRITE_TOKEN` exists (or Blob is linked and token is confirmed in the Vercel dashboard).
|
|
745
|
+
|
|
746
|
+
**Forbidden for Q2=Blob:** scaffolding a custom "Blob bridge", writing status text like "uploads persist to durable https://*.public.blob.vercel-storage.com/ URLs via the Blob bridge", or leaving uploads on local `cms-panel/uploads`.
|
|
747
|
+
---
|
|
748
|
+
|
|
749
|
+
## Question 3 - Brand form (`brand.json` only; **not a choice question**; **must wait**)
|
|
750
|
+
|
|
751
|
+
> Updates **only** `brand.json`. Does **not** change Vercel project settings, Env Vars, `vercel.json`, or `server.ts` store wiring beyond what Q1-Q2 already required.
|
|
752
|
+
>
|
|
753
|
+
> **IRON RULE - form, not question; show then WAIT:** As soon as Q2 is done, **open the brand form**. Do **not** ask a choice first. Show **all seven optional fields on one screen at the same time**. Then **HARD STOP** until the user submits the form, replies with values, or says **"skip"**.
|
|
754
|
+
>
|
|
755
|
+
> **Optional fields ? optional step.** Leaving a field blank keeps the starter default **after the user responds**. It does **not** authorize you to skip waiting or auto-apply defaults in the same turn.
|
|
756
|
+
>
|
|
757
|
+
> - **Forbidden:** any preliminary question ("customize branding?", "skip or set brand?", Option A/B)
|
|
758
|
+
> - **Forbidden:** multiple-choice / radio / "reply 1 or 2" for branding
|
|
759
|
+
> - **Forbidden:** showing only Product Name + Primary colour (or any subset under 7)
|
|
760
|
+
> - **Forbidden:** field-by-field Next wizard (name -> wait -> tagline -> ...)
|
|
761
|
+
> - **Forbidden:** one freeform / Other box for all brand values
|
|
762
|
+
> - **Forbidden:** auto-continuing to init / scaffold / env / deploy because "fields are optional" or "I shouldn't re-ask"
|
|
763
|
+
> - **Required:** six text inputs + Logo Upload above Logo URL, all visible together
|
|
764
|
+
> - **Required:** wait for one user action (submit / values / "skip") before scaffolding continues
|
|
765
|
+
>
|
|
766
|
+
> **Self-check:** If the UI you are about to show has fewer than six text fields for name/tagline/four colours, **abort and rebuild** - that UI is off-protocol.
|
|
767
|
+
>
|
|
768
|
+
> **Every field is optional** once the form is shown. After the user submits (including all-blank) or says "skip": keep starter **`brand.json`** for blanks; **do not** re-ask Q3 a second time. **Do not** invent a second branding prompt.
|
|
769
|
+
>
|
|
770
|
+
> Without `CMS_PASSWORD`, the Control Panel has **no login**. Do **not** invent custom login/auth. Optional **Question 4** may set Env `CMS_PASSWORD` for Control Panel auth only (never Vercel Deployment Protection / whole-site password).
|
|
771
|
+
|
|
772
|
+
### Preferred UI (open immediately - all seven at once - then wait)
|
|
773
|
+
|
|
774
|
+
Open **one** dialog titled **Brand your TomorrowOS experience**. Render **each row as its own control**. All rows must appear **together** on first paint - not revealed after a choice, not after filling a previous field. After opening, **end your turn** and wait.
|
|
775
|
+
|
|
776
|
+
| # | Exact label | Control type | Blank default |
|
|
777
|
+
|---|-------------|--------------|---------------|
|
|
778
|
+
| 1 | **Product Name** | text input | existing `brand.json` `name` (e.g. `My Venue`) |
|
|
779
|
+
| 2 | **Tagline** | text input | existing tagline (e.g. `Digital signage`) |
|
|
780
|
+
| 3 | **Primary colour** | text input (hex) | e.g. `#FF8A3D` |
|
|
781
|
+
| 4 | **Background colour** | text input (hex) | e.g. `#FAFAF9` |
|
|
782
|
+
| 5 | **Text colour** | text input (hex) | e.g. `#0A0908` |
|
|
783
|
+
| 6 | **Secondary colour** | text input (hex) | e.g. `#F5F3EF` |
|
|
784
|
+
| 7 | **Logo** | **Upload** (SVG/PNG file picker) **above** **Logo URL** (text input) | keep `./assets/logo.svg` |
|
|
785
|
+
|
|
786
|
+
Helper text: *All fields optional. Leave blank to keep starter brand.json defaults. Submit once, or reply "skip". I will wait before continuing setup.*
|
|
787
|
+
|
|
788
|
+
**Logo UX:** Upload control directly above Logo URL. Either is fine; if both provided, prefer the uploaded file.
|
|
789
|
+
|
|
790
|
+
### If a form UI is unavailable - one chat message only (still wait)
|
|
791
|
+
|
|
792
|
+
Only when the platform **cannot** render multi-field forms. Do **not** ask a choice first. Send **exactly one** message listing all seven fields, then **end your turn and wait** (user may answer partially, attach a logo, or say "skip"):
|
|
793
|
+
|
|
794
|
+
> Brand your TomorrowOS experience (**all optional - reply once**). Leave any field blank to keep starter `brand.json` defaults. I will wait for your reply before scaffolding:
|
|
795
|
+
>
|
|
796
|
+
> 1. **Product Name**
|
|
797
|
+
> 2. **Tagline**
|
|
798
|
+
> 3. **Primary colour** (hex)
|
|
799
|
+
> 4. **Background colour** (hex)
|
|
800
|
+
> 5. **Text colour** (hex)
|
|
801
|
+
> 6. **Secondary colour** (hex)
|
|
802
|
+
> 7. **Logo** - attach SVG/PNG or paste a Logo URL
|
|
803
|
+
>
|
|
804
|
+
> Or reply **skip** to keep all defaults.
|
|
805
|
+
|
|
806
|
+
### After the user submits
|
|
807
|
+
|
|
808
|
+
**You must:**
|
|
809
|
+
|
|
810
|
+
1. For each blank field, **keep the existing starter `brand.json` value** - do not invent new colours/names.
|
|
811
|
+
2. For filled fields, write them into `brand.json` only.
|
|
812
|
+
3. If Logo is a URL: fetch into `./assets/logo.png` or `./assets/logo.svg` when possible and set `logoPath`; if fetch fails, keep existing `logoPath` and note it.
|
|
813
|
+
4. If Logo is an upload: save under `./assets/` and set `logoPath` (prefer upload over URL when both are present).
|
|
814
|
+
5. Always set **`cms.hostingTarget`: `"vercel"`** without asking.
|
|
815
|
+
6. Default `cms.expectedScreens` to `5` unless the user already volunteered a number.
|
|
816
|
+
7. Show a one-line summary (name + primary hex + logo path) - **do not** ask a second branding question.
|
|
817
|
+
8. **Do not** build login, signup, OAuth, or clone a marketing site from branding answers.
|
|
818
|
+
|
|
819
|
+
### Write `brand.json`
|
|
820
|
+
|
|
821
|
+
Validate mentally against `brand.schema.json`. Minimum shape (values = user input **or** existing defaults):
|
|
822
|
+
|
|
823
|
+
```json
|
|
824
|
+
{
|
|
825
|
+
"name": "<Product Name or existing brand.json name>",
|
|
826
|
+
"tagline": "<Tagline or existing>",
|
|
827
|
+
"targetPlatforms": ["tizen"],
|
|
828
|
+
"primaryColor": "#FF8A3D",
|
|
829
|
+
"secondaryColor": "#F5F3EF",
|
|
830
|
+
"backgroundColor": "#FAFAF9",
|
|
831
|
+
"textColor": "#0A0908",
|
|
832
|
+
"logoPath": "./assets/logo.svg",
|
|
833
|
+
"fontFamily": "Inter",
|
|
834
|
+
"cms": {
|
|
835
|
+
"useCase": "other",
|
|
836
|
+
"hostingTarget": "vercel",
|
|
837
|
+
"expectedScreens": 5,
|
|
838
|
+
"features": {
|
|
839
|
+
"bulkCommands": false,
|
|
840
|
+
"proofOfPlay": false,
|
|
841
|
+
"contentScheduling": true,
|
|
842
|
+
"userManagement": false
|
|
843
|
+
}
|
|
844
|
+
},
|
|
845
|
+
"protocolVersion": "1.0"
|
|
846
|
+
}
|
|
847
|
+
```
|
|
848
|
+
|
|
849
|
+
Set `cmsEndpoint` only if they already know the public `https://?vercel.app` URL; otherwise tell them to pair TVs with the **Published** HTTPS origin (player maps `https://` ? `wss://`).
|
|
850
|
+
|
|
851
|
+
---
|
|
852
|
+
|
|
853
|
+
## Question 4 — Optional CMS password (`CMS_PASSWORD`)
|
|
854
|
+
|
|
855
|
+
> **Optional.** Protect the Control Panel with app-level auth. You may **skip** and set Env Var `CMS_PASSWORD` later (then Redeploy).
|
|
856
|
+
|
|
857
|
+
**Ask (Env input or one chat reply):**
|
|
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.
|
|
860
|
+
|
|
861
|
+
**Why:**
|
|
862
|
+
- Locks the **CMS Control Panel only** (not the whole deployment).
|
|
863
|
+
- Devices still reach WebSocket / brand / uploads without that password.
|
|
864
|
+
- Safer than Vercel **Deployment Protection** / whole-site password (those break Tizen/BrightSign).
|
|
865
|
+
- Leaving it blank is fine; add `CMS_PASSWORD` later when you want the login page.
|
|
866
|
+
|
|
867
|
+
**Accept:**
|
|
868
|
+
- blank / skip / "later" / "no" → do **not** set `CMS_PASSWORD`
|
|
869
|
+
- any non-empty password → set Vercel Env **`CMS_PASSWORD`** (Production + Preview as needed)
|
|
870
|
+
|
|
871
|
+
**Reject / clarify:**
|
|
872
|
+
- Do **not** enable Vercel Deployment Protection / whole-site password for device reachability.
|
|
873
|
+
- Do **not** put the password in committed source.
|
|
874
|
+
|
|
875
|
+
---
|
|
876
|
+
|
|
877
|
+
## After all answers ? execution checklist
|
|
878
|
+
|
|
879
|
+
### A. Seed the project
|
|
880
|
+
|
|
881
|
+
```bash
|
|
882
|
+
npx @tomorrowos/sdk@latest init .
|
|
883
|
+
# If not empty and user confirms starter overwrite:
|
|
884
|
+
# npx @tomorrowos/sdk@latest init . --force
|
|
885
|
+
```
|
|
886
|
+
|
|
887
|
+
Then (**Scheme A ??mandatory for Publish**):
|
|
888
|
+
|
|
889
|
+
1. Keep **`server.ts`** at **project root** with top-level `listen()` + `export default server`.
|
|
890
|
+
2. If Preview needs Next: **move** `next.config.*`, `app/`, Next `tsconfig` / `postcss` / `next-env.d.ts` into **`preview/`** ??remove them from root so Production is not mistaken for Next/static.
|
|
891
|
+
3. Write `vercel.json` per **Scheme A3** (`fluid`, `framework: null`, no-op `buildCommand` ??**no** `startCommand`).
|
|
892
|
+
4. **Force Project Settings** per **Scheme A4** (Framework = Other, Output Directory empty, Start = `npm run start`, Fluid on). Patch dashboard via API if it still says Next.js.
|
|
893
|
+
5. Do **not** add root `api/` catch-all for raw `http.Server`.
|
|
894
|
+
6. Do **not** add `.replit*`.
|
|
895
|
+
7. If Q2 = Vercel Blob: set `BLOB_READ_WRITE_TOKEN` + `TOMORROWOS_MEDIA=vercel-blob` (SDK-native; no custom bridge).
|
|
896
|
+
|
|
897
|
+
### B. Environment Variables (Vercel dashboard + local)
|
|
898
|
+
|
|
899
|
+
| Name | Required | Who fills it |
|
|
900
|
+
|------|----------|--------------|
|
|
901
|
+
| `SUPABASE_URL` | If Q1 = Supabase | User pastes URL |
|
|
902
|
+
| `DATABASE_URL` | If Q1 = Neon | User pastes URL |
|
|
903
|
+
| `TOMORROWOS_STORE` | Yes | **Agent auto-sets only** ? never a blank field for the user (`supabase` / `postgres`) |
|
|
904
|
+
| `DATABASE_SSL` | Yes for Postgres | **Agent auto-sets only** ??never a blank field for the user (`true`) |
|
|
905
|
+
| `CLOUDINARY_*` | If Q2 = Cloudinary | User via **one Env popup** (all fields) |
|
|
906
|
+
| `BLOB_READ_WRITE_TOKEN` | If Q2 = Vercel Blob | User / Blob link |
|
|
907
|
+
| `TOMORROWOS_MEDIA` | If Q2 = Vercel Blob | **Agent auto-sets** `vercel-blob` |
|
|
908
|
+
| `TOMORROWOS_INTERNAL_PORT` | Preview only | Agent (e.g. `3001`) |
|
|
909
|
+
| `PORT` | Optional | Vercel injects for Production |
|
|
910
|
+
| `CMS_PASSWORD` | Optional (Q4) | User if set; blank/skip = omit (can add later) |
|
|
911
|
+
|
|
912
|
+
Mirror non-secrets in `.env` for local `npm run dev` if helpful; never commit secrets.
|
|
913
|
+
|
|
914
|
+
### C. Install and run
|
|
915
|
+
|
|
916
|
+
```bash
|
|
917
|
+
npm install
|
|
918
|
+
npm run start
|
|
919
|
+
# Preview / v0: npm run dev:preview (Next + TomorrowOS internal)
|
|
920
|
+
```
|
|
921
|
+
|
|
922
|
+
Confirm logs: `[TomorrowOS] listening on http://0.0.0.0:?`
|
|
923
|
+
Confirm browser:
|
|
924
|
+
|
|
925
|
+
- **Preview:** Control Panel visible through the Next proxy at `/`
|
|
926
|
+
- **Production start:** Control Panel served directly by TomorrowOS at `/`
|
|
927
|
+
|
|
928
|
+
### D. Deploy (Publish ??Node only)
|
|
929
|
+
|
|
930
|
+
```bash
|
|
931
|
+
vercel --prod
|
|
932
|
+
# or git push ??Vercel Git integration
|
|
933
|
+
```
|
|
934
|
+
|
|
935
|
+
**Publish acceptance checklist (all must pass ??Scheme A5):**
|
|
936
|
+
|
|
937
|
+
1. Framework Preset = **Other** / null (dashboard confirms ??not Next.js)
|
|
938
|
+
2. Output Directory = **empty** (not `public`)
|
|
939
|
+
3. Start Command = Node `TomorrowOS.listen` (`npm run start` / `tsx server.ts`)
|
|
940
|
+
4. Fluid compute on
|
|
941
|
+
5. Env Vars present for Production
|
|
942
|
+
6. Production `GET /status` returns **JSON** ??**not** HTML (`Unexpected token 'T'` means this failed)
|
|
943
|
+
7. Production `GET /` returns Control Panel HTML ??**not** `404: NOT_FOUND`
|
|
944
|
+
|
|
945
|
+
If step 6 fails but step 7 passes: **static `public/` trap** ??Vercel is not running Node. Re-apply Scheme A3?A4; try **Path B** (Git / `vercel --prod`); do **not** invent `api/` + `rewrites`.
|
|
946
|
+
|
|
947
|
+
Tell the user:
|
|
948
|
+
|
|
949
|
+
- **Control Panel (Publish):** `https://YOUR-PROJECT.vercel.app`
|
|
950
|
+
- **TV CMS endpoint:** same HTTPS origin (players use `wss://`)
|
|
951
|
+
- **Preview:** Next shell is for v0 only — do not pair devices to Preview
|
|
952
|
+
|
|
953
|
+
Then **you must tell the user how to install a player and pair**. Do **not** only link a file — spell out the steps:
|
|
954
|
+
|
|
955
|
+
#### Samsung Tizen (6.5 and 7.0)
|
|
956
|
+
|
|
957
|
+
1. On the display, open **App Management** → install via **Custom App** URL:
|
|
958
|
+
|
|
959
|
+
```txt
|
|
960
|
+
https://tmr.sh/tizen
|
|
961
|
+
```
|
|
962
|
+
|
|
963
|
+
Or use Control Panel → **Download Players → Samsung** and install via USB.
|
|
964
|
+
2. Choose orientation, then enter the **Production** CMS URL: `https://YOUR-PROJECT.vercel.app/` (never Preview, never `localhost`).
|
|
965
|
+
3. Enter the **six-character** pairing code in Control Panel → **Pair**.
|
|
966
|
+
|
|
967
|
+
#### BrightSign (Series 3-6)
|
|
968
|
+
|
|
969
|
+
1. On the **Production** Control Panel → **Download Players → BrightSign** (preferred — `cmsEndpoint` is filled for this CMS),
|
|
970
|
+
or download `https://tmr.sh/app/brightsign/brightsign_package.zip` and set `cmsEndpoint` in `config.js` to `https://YOUR-PROJECT.vercel.app/`.
|
|
971
|
+
2. Unzip and copy the **contents** to the SD card root (`autorun.brs`, `config.js`, player files).
|
|
972
|
+
3. Power-cycle the player, wait ~10s through the black boot window, then pair with the six-character code.
|
|
973
|
+
|
|
974
|
+
4. Create a playlist, upload media, **Publish** to the device.
|
|
975
|
+
|
|
976
|
+
More detail: https://docs.tomorrowos.org/docs/os/tizen and https://docs.tomorrowos.org/docs/os/brightsign
|
|
977
|
+
|
|
978
|
+
### E. Minimal verification only
|
|
979
|
+
|
|
980
|
+
**Do:**
|
|
981
|
+
|
|
982
|
+
1. Preview: Control Panel HTML at `/` (via Next proxy in `preview/` if applicable)
|
|
983
|
+
2. Publish: `GET /status` ??JSON; `GET /` ??Control Panel from **Node** TomorrowOS
|
|
984
|
+
3. Publish: panel does **not** show `Unexpected token 'T'` / CMS unreachable
|
|
985
|
+
4. Server status: Media OK when Cloudinary or Blob configured
|
|
986
|
+
5. Database: if ERROR shows **`ENETUNREACH` ??IPv6 ??`:5432`**, switch to **pooled** URL (Supabase `:6543` or Neon pooler) and redeploy ??call this out explicitly to the user
|
|
987
|
+
6. Fluid compute on for Production
|
|
988
|
+
|
|
989
|
+
**Do not block** on long WebSocket/device pairing tests unless the user asks. Prefer pairing against **Publish**, not Preview.
|
|
990
|
+
|
|
991
|
+
---
|
|
992
|
+
|
|
993
|
+
## Failure recovery cheat sheet
|
|
994
|
+
|
|
995
|
+
| Symptom | Likely cause | Fix |
|
|
996
|
+
|---------|--------------|-----|
|
|
997
|
+
| Publish `404: NOT_FOUND` | Framework Preset still **Next.js** / no Function | Scheme A: Framework = **Other**, Fluid on, `api/index.ts` + rewrites; redeploy |
|
|
998
|
+
| Panel loads but **CMS unreachable** / `Unexpected token 'T', "The page c"...` | **`public/` deployed as static site** ??no Function | Rename to `cms-panel/`; Output empty; Scheme A Function entry; **Path B** if needed |
|
|
999
|
+
| Only `/api/status` returns JSON, `/status` is 404 | Missing **rewrites** to `/api` | Add Scheme A3 rewrite `/(.*) ??/api` (exclude existing `/api/`); redeploy |
|
|
1000
|
+
| Control Panel OK, TV **could not connect** | WebSocket upgrade not reaching Function (200 HTML) | Fluid on; `export default server`; verify 101 on `/` and `/api`; check WebSockets permission |
|
|
1001
|
+
| `vercel.json` error: invalid `startCommand` | v0 schema rejects it | Remove from `vercel.json`; Function entry does not need startCommand |
|
|
1002
|
+
| Separate `api/ws.ts` with a second `new TomorrowOS` | Two isolates ??pairing memory split | **One** Function only (`api/index.ts` ??shared `cms-app.ts`) |
|
|
1003
|
+
| `vercel inspect` shows Output = `public if it exists` | Framework=Other static default | Force Output Directory **empty**; use `cms-panel/`; Path B redeploy |
|
|
1004
|
+
| Build runs `next build` / missing routes-manifest | Next auto-detected from root `next.config` / `app/` | Move Next to `preview/`; Framework null/Other; `build` = no-op |
|
|
1005
|
+
| Preview blank but logs show TomorrowOS listening | v0 Next intercepts public port | Add Next reverse proxy ??internal TomorrowOS port |
|
|
1006
|
+
| Preview works, Publish broken / Next-only | Production still on Next | Switch Production to Fluid Function `api/index.ts` |
|
|
1007
|
+
| `ENETUNREACH` / IPv6 / `:5432` | Direct Postgres URL | Use **pooled** URL (Supabase `:6543` or Neon pooler) |
|
|
1008
|
+
| Control Panel OK, devices never stay paired after restart | SQLite / ephemeral disk | Use Supabase or Neon |
|
|
1009
|
+
| Uploads break / broken thumbs in prod | Local uploads only | Cloudinary or Vercel Blob |
|
|
1010
|
+
| Blob uploads 401 / missing token | Blob not linked | Vercel Storage ??Blob + `BLOB_READ_WRITE_TOKEN` |
|
|
1011
|
+
| WebSocket fails on Publish | Fluid off / static-only / Next as Production / no Function WS | Enable Fluid; Scheme A; test 101 upgrade |
|
|
1012
|
+
| Devices fail only on Preview | Next proxy cannot upgrade `wss` | Expected ??pair on Publish URL |
|
|
1013
|
+
| Agent asked user to fill `TOMORROWOS_STORE` / `DATABASE_SSL` | Off-protocol | Agent must auto-set after DB choice; remove those fields from user Env forms |
|
|
1014
|
+
| Agent offered Blob-as-recommended / Supabase Storage / local disk / "no media" for Q2 | Invented options | Re-ask Q2 verbatim: **1 Cloudinary (recommended)**, 2 Vercel Blob |
|
|
1015
|
+
| Agent asked Cloudinary key/secret in three chat turns | Off-protocol | Use **one Env popup** with all Cloudinary fields |
|
|
1016
|
+
| Agent asked for OpenAI / `sk-` key during setup | Hallucinated ?AI pairing??requirement | **Refuse.** TomorrowOS does not need LLM keys. Return to Q2 media choices only |
|
|
1017
|
+
| Agent asked a branding choice / Option A-B / "customize?" before the form | Off-protocol Q3 | Close it; immediately open the **7-field form** (no choice step) |
|
|
1018
|
+
| Agent showed only Name + Primary colour (or any subset) | Off-protocol Q3 | Rebuild with **all seven** inputs visible at once |
|
|
1019
|
+
| Agent auto-continued after showing Q3 because "fields are optional" / "shouldn't re-ask" | Off-protocol Q3 | **HARD STOP** and wait for submit or explicit "skip"; do not scaffold yet |
|
|
1020
|
+
| Agent asked branding as Option A URL vs Option B, or field-by-field Next | Off-protocol Q3 | Re-open Q3 as **seven separate optional inputs** in one dialog; blanks keep `brand.json` |
|
|
1021
|
+
| Agent enabled Vercel Deployment Protection / whole-site password | Blocks Tizen/BrightSign | Disable host gate; use optional `CMS_PASSWORD` for Control Panel only |
|
|
1022
|
+
| Agent built a login page from branding answers | Misread brand step as product scope | Remove login; keep starter Control Panel; apply answers only to `brand.json` |
|
|
1023
|
+
| Secrets in git | Mistake | Rotate keys; move to Vercel Env Vars |
|
|
1024
|
+
| Replit files present | Copied wrong protocol | Delete `.replit*`; use this file |
|
|
1025
|
+
|
|
1026
|
+
---
|
|
1027
|
+
|
|
1028
|
+
## How this differs from `REPLIT_SETUP.md`
|
|
1029
|
+
|
|
1030
|
+
| Topic | Replit | Vercel |
|
|
1031
|
+
|-------|--------|--------|
|
|
1032
|
+
| Secrets | Replit Secrets | Vercel Environment Variables |
|
|
1033
|
+
| Deploy config | `.replit`, artifact `kind=web` | `vercel.json` + Fluid; **Framework = Other**; **no** `.replit` |
|
|
1034
|
+
| `hostingTarget` | `"here"` | `"vercel"` |
|
|
1035
|
+
| Database | Supabase (primary) | Supabase **or Neon** (pooled) |
|
|
1036
|
+
| Media | Cloudinary or Replit Object Storage | Cloudinary **or Vercel Blob** |
|
|
1037
|
+
| Preview | Replit Preview / Autoscale Node | **Next shell + proxy** when v0 hardwires Next |
|
|
1038
|
+
| Publish | Long-lived Node | **Node `TomorrowOS.listen` only** (not Next); gate on no `404 NOT_FOUND` |
|
|
1039
|
+
| Brand | Replit 7-field optional dialog | **Form not question:** all 7 optional inputs at once; no choice first; Logo Upload above Logo URL |
|
|
1040
|
+
|
|
1041
|
+
---
|
|
1042
|
+
|
|
1043
|
+
## Final summary template (Agent — human)
|
|
1044
|
+
|
|
1045
|
+
After setup, report:
|
|
1046
|
+
|
|
1047
|
+
1. **Live URL** (Production Publish)
|
|
1048
|
+
2. **Publish health:** Framework = Other; Output — `public`; `GET /status` — JSON; `GET /` OK
|
|
1049
|
+
3. **Preview note:** Next proxy shell used / not needed
|
|
1050
|
+
4. **Env Vars set** (names only — never values); note which were auto-set (`TOMORROWOS_STORE`, `DATABASE_SSL`)
|
|
1051
|
+
5. **Store:** Supabase pooler or Neon pooled
|
|
1052
|
+
6. **Media:** Cloudinary or Vercel Blob
|
|
1053
|
+
7. **Brand:** name + `hostingTarget: vercel` (from optional Q3 fields; blanks = starter defaults)
|
|
1054
|
+
8. **CMS password:** set or skipped (yes/no only — never echo the password)
|
|
1055
|
+
9. **Runtime split:** Preview = …; Publish = Node TomorrowOS (confirm **not** Next)
|
|
1056
|
+
10. **Open action:** if Database still ERROR, paste the pooler instruction again
|
|
1057
|
+
11. **Connect a screen:** include the Tizen Custom App URL / BrightSign SD install + six-character pairing steps from section **D. Deploy** above (required — do not skip)
|
|
1058
|
+
|
|
1059
|
+
Protocol complete when:
|
|
1060
|
+
|
|
1061
|
+
- **Preview** shows the Control Panel at `/` (via Next proxy if required), **and**
|
|
1062
|
+
- **Publish** returns Control Panel at `/` **and** `/status` JSON from pure Node `TomorrowOS.listen` with Fluid (not static `public/`, not `404: NOT_FOUND`, not `Unexpected token 'T'`), **and**
|
|
1063
|
+
- Q1–Q4 configuration is saved, **and**
|
|
1064
|
+
- the user has been told how to install Tizen and/or BrightSign and pair
|
|
1065
|
+
|
|
1066
|
+
? even if a sandbox DB probe still flakes **after** the pooled URL is correctly set for Production.
|
|
1067
|
+
|
|
1068
|
+
---
|
|
1069
|
+
|
|
1070
|
+
## Protocol version
|
|
1071
|
+
|
|
1072
|
+
`vercel-setup/1.16` - pairs with `@tomorrowos/sdk` 0.9.70+ and `VERCEL_QUESTIONS.md` 1.6.
|
|
1073
|
+
|
|
1074
|
+
**Changelog 1.16:** Question 4 is optional Control Panel Env `CMS_PASSWORD` (blank/skip allowed; can set later). Do not use Vercel Deployment Protection / whole-site password for devices.
|
|
1075
|
+
|
|
1076
|
+
**Changelog 1.15:** After Publish is healthy, agents **must** tell the user how to install Tizen (Custom App `https://tmr.sh/tizen`) and BrightSign (CMS zip → SD root) and pair against the **Production** URL. Final summary requires this screen-connect step.
|
|
1077
|
+
|
|
1078
|
+
**Changelog 1.14:** Vercel Blob is **SDK-native** (`BLOB_READ_WRITE_TOKEN` + `TOMORROWOS_MEDIA=vercel-blob`). No project Blob bridge. Status providers use display names (Neon, Supabase, Blob, Cloudinary, Replit Object Storage). Forbidden marketing status copy about blob URL patterns.
|
|
1079
|
+
|
|
1080
|
+
**Changelog 1.13:** Q3 must **HARD STOP and WAIT** for user submit or "skip". Optional fields do **not** authorize auto-continuing with defaults. Clarifies "do not re-ask" = after the user already replied, not "skip waiting".
|
|
1081
|
+
|
|
1082
|
+
**Changelog 1.12:** Q3 is a **form, not a question**. No preliminary branding choice. Must show **all seven** optional inputs at once (never only Name + Primary colour). Logo = Upload above Logo URL.
|
|
1083
|
+
|
|
1084
|
+
**Changelog 1.11:** Q3 must render **seven separate input controls** (not one freeform / Other box). Logo field = **Upload** above **Logo URL**; blanks keep starter `brand.json`.
|
|
1085
|
+
|
|
1086
|
+
**Changelog 1.10:** Q3 is a **single** seven-field optional branding dialog (Product Name, Tagline, Primary / Background / Text / Secondary colour, Logo URL or upload). Removed Option A website-URL vs Option B split. Blanks keep starter `brand.json`. Pairs with new `VERCEL_UPGRADE.md`.
|
|
1087
|
+
|