@topolo/cli 0.1.22 → 0.1.24

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.
Files changed (3) hide show
  1. package/README.md +8 -2
  2. package/dist/index.js +1363 -340
  3. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import { platform as platform2 } from "os";
9
9
  import { createInterface } from "readline/promises";
10
10
  import { stdin as input } from "process";
11
11
 
12
- // node_modules/@topolo/sdk/dist/index.js
12
+ // ../topolo-sdk/dist/index.js
13
13
  function applyAuthHeaders(headers, credential) {
14
14
  if (credential.kind === "api_key") {
15
15
  headers.set("X-Api-Key", credential.apiKey);
@@ -67,6 +67,7 @@ var TopoloHttpError = class extends TopoloSdkError {
67
67
  var SERVICES = {
68
68
  agent: "https://agent-api.topolo.app",
69
69
  auth: "https://auth.topolo.app",
70
+ backup: "https://backup.topolo.app",
70
71
  books: "https://books.topolo.app",
71
72
  bugfix: "https://bugfix.topolo.app",
72
73
  bytes: "https://bytes-api.topolo.app",
@@ -77,7 +78,6 @@ var SERVICES = {
77
78
  compose: "https://compose.topolo.app",
78
79
  consent: "https://consent.topolo.app",
79
80
  crm: "https://crm-api.topolo.app",
80
- customer_success: "https://success.topolo.app",
81
81
  developers: "https://developers.topolo.app",
82
82
  director: "https://director.topolo.app",
83
83
  feed: "https://feed-api.topolo.app",
@@ -91,9 +91,10 @@ var SERVICES = {
91
91
  localize: "https://localize.topolo.app",
92
92
  mail: "https://mail.topolo.app",
93
93
  mdm: "https://mdm-api.topolo.app",
94
- messaging: "https://messages.topolo.app",
94
+ messages: "https://messages.topolo.app",
95
95
  nexus: "https://nexus-api.topolo.app",
96
96
  notify: "https://notify.topolo.app",
97
+ observability_watch: "https://observability-watch.topolo.app",
97
98
  one: "https://one.topolo.app",
98
99
  p2p: "https://p2p.topolo.app",
99
100
  pay: "https://pay.topolo.app",
@@ -107,6 +108,7 @@ var SERVICES = {
107
108
  socialize: "https://socialize-api.topolo.app",
108
109
  spaces: "https://spaces.topolo.app",
109
110
  status: "https://status.topolo.app",
111
+ success: "https://success.topolo.app",
110
112
  support: "https://support.topolo.app",
111
113
  survey: "https://survey.topolo.app",
112
114
  voice: "https://voice.topolo.app",
@@ -123,13 +125,21 @@ var APPLICATIONS = {
123
125
  "deployTargets": [
124
126
  {
125
127
  "name": "topolo-admin",
126
- "kind": "pages",
127
- "apiUrl": null,
128
+ "kind": "worker",
129
+ "apiUrl": "https://admin.topolo.app",
128
130
  "domains": [
129
131
  "admin.topolo.app"
130
132
  ],
131
133
  "routes": [],
132
134
  "serviceId": null
135
+ },
136
+ {
137
+ "name": "topolo-admin-api",
138
+ "kind": "worker",
139
+ "apiUrl": null,
140
+ "domains": [],
141
+ "routes": [],
142
+ "serviceId": null
133
143
  }
134
144
  ]
135
145
  },
@@ -146,7 +156,7 @@ var APPLICATIONS = {
146
156
  {
147
157
  "name": "topolo-agent",
148
158
  "kind": "worker",
149
- "apiUrl": null,
159
+ "apiUrl": "https://agent.topolo.app",
150
160
  "domains": [
151
161
  "agent.topolo.app"
152
162
  ],
@@ -154,7 +164,7 @@ var APPLICATIONS = {
154
164
  "serviceId": null
155
165
  },
156
166
  {
157
- "name": "topolo-agent-worker",
167
+ "name": "topolo-agent-api",
158
168
  "kind": "worker",
159
169
  "apiUrl": "https://agent-api.topolo.app",
160
170
  "domains": [
@@ -194,16 +204,20 @@ var APPLICATIONS = {
194
204
  "appDir": "TopoloBackup",
195
205
  "name": "topolo-backup",
196
206
  "packageName": "topolo-backup",
197
- "productionUrl": null,
198
- "services": [],
207
+ "productionUrl": "https://backup.topolo.app",
208
+ "services": [
209
+ "backup"
210
+ ],
199
211
  "deployTargets": [
200
212
  {
201
213
  "name": "topolo-backup",
202
214
  "kind": "worker",
203
- "apiUrl": null,
204
- "domains": [],
215
+ "apiUrl": "https://backup.topolo.app",
216
+ "domains": [
217
+ "backup.topolo.app"
218
+ ],
205
219
  "routes": [],
206
- "serviceId": null
220
+ "serviceId": "backup"
207
221
  }
208
222
  ]
209
223
  },
@@ -224,10 +238,16 @@ var APPLICATIONS = {
224
238
  "domains": [
225
239
  "books.topolo.app"
226
240
  ],
227
- "routes": [
228
- "books.topolo.app"
229
- ],
241
+ "routes": [],
230
242
  "serviceId": "books"
243
+ },
244
+ {
245
+ "name": "topolo-books-api",
246
+ "kind": "worker",
247
+ "apiUrl": null,
248
+ "domains": [],
249
+ "routes": [],
250
+ "serviceId": null
231
251
  }
232
252
  ]
233
253
  },
@@ -248,9 +268,7 @@ var APPLICATIONS = {
248
268
  "domains": [
249
269
  "bugfix.topolo.app"
250
270
  ],
251
- "routes": [
252
- "bugfix.topolo.app"
253
- ],
271
+ "routes": [],
254
272
  "serviceId": "bugfix"
255
273
  }
256
274
  ]
@@ -267,8 +285,8 @@ var APPLICATIONS = {
267
285
  "deployTargets": [
268
286
  {
269
287
  "name": "topolo-bytes",
270
- "kind": "pages",
271
- "apiUrl": null,
288
+ "kind": "worker",
289
+ "apiUrl": "https://bytes.topolo.app",
272
290
  "domains": [
273
291
  "bytes.topolo.app"
274
292
  ],
@@ -304,10 +322,16 @@ var APPLICATIONS = {
304
322
  "domains": [
305
323
  "calendar.topolo.app"
306
324
  ],
307
- "routes": [
308
- "calendar.topolo.app"
309
- ],
325
+ "routes": [],
310
326
  "serviceId": "calendar"
327
+ },
328
+ {
329
+ "name": "topolo-calendar-api",
330
+ "kind": "worker",
331
+ "apiUrl": null,
332
+ "domains": [],
333
+ "routes": [],
334
+ "serviceId": null
311
335
  }
312
336
  ]
313
337
  },
@@ -350,22 +374,19 @@ var APPLICATIONS = {
350
374
  "domains": [
351
375
  "chat.topolo.app"
352
376
  ],
353
- "routes": [
354
- "chat.topolo.app"
355
- ],
377
+ "routes": [],
356
378
  "serviceId": "chat"
379
+ },
380
+ {
381
+ "name": "topolo-chat-api",
382
+ "kind": "worker",
383
+ "apiUrl": null,
384
+ "domains": [],
385
+ "routes": [],
386
+ "serviceId": null
357
387
  }
358
388
  ]
359
389
  },
360
- "cloudcontrol": {
361
- "id": "cloudcontrol",
362
- "appDir": "TopoloCloudControl",
363
- "name": "cloudcontrol",
364
- "packageName": "topolo-cloudcontrol",
365
- "productionUrl": null,
366
- "services": [],
367
- "deployTargets": []
368
- },
369
390
  "commerce": {
370
391
  "id": "commerce",
371
392
  "appDir": "TopoloCommerce",
@@ -388,8 +409,8 @@ var APPLICATIONS = {
388
409
  },
389
410
  {
390
411
  "name": "topolo-commerce-guest-web",
391
- "kind": "pages",
392
- "apiUrl": null,
412
+ "kind": "worker",
413
+ "apiUrl": "https://guest.topolo.app",
393
414
  "domains": [
394
415
  "guest.topolo.app"
395
416
  ],
@@ -398,8 +419,8 @@ var APPLICATIONS = {
398
419
  },
399
420
  {
400
421
  "name": "topolo-commerce-ops-web",
401
- "kind": "pages",
402
- "apiUrl": null,
422
+ "kind": "worker",
423
+ "apiUrl": "https://commerce.topolo.app",
403
424
  "domains": [
404
425
  "commerce.topolo.app"
405
426
  ],
@@ -408,8 +429,8 @@ var APPLICATIONS = {
408
429
  },
409
430
  {
410
431
  "name": "topolo-commerce-station-web",
411
- "kind": "pages",
412
- "apiUrl": null,
432
+ "kind": "worker",
433
+ "apiUrl": "https://station-commerce.topolo.app",
413
434
  "domains": [
414
435
  "station-commerce.topolo.app"
415
436
  ],
@@ -435,10 +456,16 @@ var APPLICATIONS = {
435
456
  "domains": [
436
457
  "compose.topolo.app"
437
458
  ],
438
- "routes": [
439
- "compose.topolo.app"
440
- ],
459
+ "routes": [],
441
460
  "serviceId": "compose"
461
+ },
462
+ {
463
+ "name": "topolo-compose-api",
464
+ "kind": "worker",
465
+ "apiUrl": null,
466
+ "domains": [],
467
+ "routes": [],
468
+ "serviceId": null
442
469
  }
443
470
  ]
444
471
  },
@@ -459,10 +486,16 @@ var APPLICATIONS = {
459
486
  "domains": [
460
487
  "consent.topolo.app"
461
488
  ],
462
- "routes": [
463
- "consent.topolo.app"
464
- ],
489
+ "routes": [],
465
490
  "serviceId": "consent"
491
+ },
492
+ {
493
+ "name": "topolo-consent-api",
494
+ "kind": "worker",
495
+ "apiUrl": null,
496
+ "domains": [],
497
+ "routes": [],
498
+ "serviceId": null
466
499
  }
467
500
  ]
468
501
  },
@@ -489,7 +522,7 @@ var APPLICATIONS = {
489
522
  {
490
523
  "name": "topolo-crm-app",
491
524
  "kind": "worker",
492
- "apiUrl": null,
525
+ "apiUrl": "https://crm.topolo.app",
493
526
  "domains": [
494
527
  "crm.topolo.app"
495
528
  ],
@@ -498,30 +531,6 @@ var APPLICATIONS = {
498
531
  }
499
532
  ]
500
533
  },
501
- "customer_success": {
502
- "id": "customer_success",
503
- "appDir": "TopoloSuccess",
504
- "name": "topolo-success",
505
- "packageName": "topolo-success",
506
- "productionUrl": "https://success.topolo.app",
507
- "services": [
508
- "customer_success"
509
- ],
510
- "deployTargets": [
511
- {
512
- "name": "topolo-success",
513
- "kind": "worker",
514
- "apiUrl": "https://success.topolo.app",
515
- "domains": [
516
- "success.topolo.app"
517
- ],
518
- "routes": [
519
- "success.topolo.app"
520
- ],
521
- "serviceId": "customer_success"
522
- }
523
- ]
524
- },
525
534
  "developers": {
526
535
  "id": "developers",
527
536
  "appDir": "TopoloDevelopers",
@@ -549,6 +558,14 @@ var APPLICATIONS = {
549
558
  ],
550
559
  "routes": [],
551
560
  "serviceId": "developers"
561
+ },
562
+ {
563
+ "name": "topolo-developers-api",
564
+ "kind": "worker",
565
+ "apiUrl": null,
566
+ "domains": [],
567
+ "routes": [],
568
+ "serviceId": null
552
569
  }
553
570
  ]
554
571
  },
@@ -569,10 +586,16 @@ var APPLICATIONS = {
569
586
  "domains": [
570
587
  "director.topolo.app"
571
588
  ],
572
- "routes": [
573
- "director.topolo.app"
574
- ],
589
+ "routes": [],
575
590
  "serviceId": "director"
591
+ },
592
+ {
593
+ "name": "topolo-director-api",
594
+ "kind": "worker",
595
+ "apiUrl": null,
596
+ "domains": [],
597
+ "routes": [],
598
+ "serviceId": null
576
599
  }
577
600
  ]
578
601
  },
@@ -586,13 +609,21 @@ var APPLICATIONS = {
586
609
  "deployTargets": [
587
610
  {
588
611
  "name": "topolo-docs",
589
- "kind": "pages",
590
- "apiUrl": null,
612
+ "kind": "worker",
613
+ "apiUrl": "https://docs.topolo.app",
591
614
  "domains": [
592
615
  "docs.topolo.app"
593
616
  ],
594
617
  "routes": [],
595
618
  "serviceId": null
619
+ },
620
+ {
621
+ "name": "topolo-docs-api",
622
+ "kind": "worker",
623
+ "apiUrl": null,
624
+ "domains": [],
625
+ "routes": [],
626
+ "serviceId": null
596
627
  }
597
628
  ]
598
629
  },
@@ -601,7 +632,7 @@ var APPLICATIONS = {
601
632
  "appDir": "TopoloFeed",
602
633
  "name": "TopoloFeed",
603
634
  "packageName": "topolo-feed",
604
- "productionUrl": "https://feed-api.topolo.app",
635
+ "productionUrl": "https://feed.topolo.app",
605
636
  "services": [
606
637
  "feed"
607
638
  ],
@@ -613,9 +644,7 @@ var APPLICATIONS = {
613
644
  "domains": [
614
645
  "feed-analytics-api.topolo.app"
615
646
  ],
616
- "routes": [
617
- "feed-analytics-api.topolo.app"
618
- ],
647
+ "routes": [],
619
648
  "serviceId": null
620
649
  },
621
650
  {
@@ -625,10 +654,18 @@ var APPLICATIONS = {
625
654
  "domains": [
626
655
  "feed-api.topolo.app"
627
656
  ],
628
- "routes": [
629
- "feed-api.topolo.app"
630
- ],
657
+ "routes": [],
631
658
  "serviceId": "feed"
659
+ },
660
+ {
661
+ "name": "topolo-feed-web",
662
+ "kind": "worker",
663
+ "apiUrl": "https://feed.topolo.app",
664
+ "domains": [
665
+ "feed.topolo.app"
666
+ ],
667
+ "routes": [],
668
+ "serviceId": null
632
669
  }
633
670
  ]
634
671
  },
@@ -649,17 +686,23 @@ var APPLICATIONS = {
649
686
  "domains": [
650
687
  "flow.topolo.app"
651
688
  ],
652
- "routes": [
653
- "flow.topolo.app"
654
- ],
689
+ "routes": [],
655
690
  "serviceId": "flow"
691
+ },
692
+ {
693
+ "name": "topolo-flow-api",
694
+ "kind": "worker",
695
+ "apiUrl": null,
696
+ "domains": [],
697
+ "routes": [],
698
+ "serviceId": null
656
699
  }
657
700
  ]
658
701
  },
659
702
  "forecast": {
660
703
  "id": "forecast",
661
704
  "appDir": "TopoloForecast",
662
- "name": "forecast-cloud",
705
+ "name": "topolo-forecast",
663
706
  "packageName": "topolo-forecast",
664
707
  "productionUrl": "https://forecast.topolo.app",
665
708
  "services": [
@@ -669,7 +712,7 @@ var APPLICATIONS = {
669
712
  {
670
713
  "name": "topolo-forecast",
671
714
  "kind": "worker",
672
- "apiUrl": null,
715
+ "apiUrl": "https://forecast.topolo.app",
673
716
  "domains": [
674
717
  "forecast.topolo.app"
675
718
  ],
@@ -705,10 +748,16 @@ var APPLICATIONS = {
705
748
  "domains": [
706
749
  "forms.topolo.app"
707
750
  ],
708
- "routes": [
709
- "forms.topolo.app"
710
- ],
751
+ "routes": [],
711
752
  "serviceId": "forms"
753
+ },
754
+ {
755
+ "name": "topolo-forms-api",
756
+ "kind": "worker",
757
+ "apiUrl": null,
758
+ "domains": [],
759
+ "routes": [],
760
+ "serviceId": null
712
761
  }
713
762
  ]
714
763
  },
@@ -729,10 +778,16 @@ var APPLICATIONS = {
729
778
  "domains": [
730
779
  "home.topolo.app"
731
780
  ],
732
- "routes": [
733
- "home.topolo.app"
734
- ],
781
+ "routes": [],
735
782
  "serviceId": "home"
783
+ },
784
+ {
785
+ "name": "topolo-home-api",
786
+ "kind": "worker",
787
+ "apiUrl": null,
788
+ "domains": [],
789
+ "routes": [],
790
+ "serviceId": null
736
791
  }
737
792
  ]
738
793
  },
@@ -753,10 +808,16 @@ var APPLICATIONS = {
753
808
  "domains": [
754
809
  "insights.topolo.app"
755
810
  ],
756
- "routes": [
757
- "insights.topolo.app"
758
- ],
811
+ "routes": [],
759
812
  "serviceId": "insights"
813
+ },
814
+ {
815
+ "name": "topolo-insights-api",
816
+ "kind": "worker",
817
+ "apiUrl": null,
818
+ "domains": [],
819
+ "routes": [],
820
+ "serviceId": null
760
821
  }
761
822
  ]
762
823
  },
@@ -777,10 +838,16 @@ var APPLICATIONS = {
777
838
  "domains": [
778
839
  "inventory.topolo.app"
779
840
  ],
780
- "routes": [
781
- "inventory.topolo.app"
782
- ],
841
+ "routes": [],
783
842
  "serviceId": "inventory"
843
+ },
844
+ {
845
+ "name": "topolo-inventory-api",
846
+ "kind": "worker",
847
+ "apiUrl": null,
848
+ "domains": [],
849
+ "routes": [],
850
+ "serviceId": null
784
851
  }
785
852
  ]
786
853
  },
@@ -797,7 +864,7 @@ var APPLICATIONS = {
797
864
  {
798
865
  "name": "topolo-learn",
799
866
  "kind": "worker",
800
- "apiUrl": null,
867
+ "apiUrl": "https://learn.topolo.app",
801
868
  "domains": [
802
869
  "learn.topolo.app"
803
870
  ],
@@ -857,10 +924,16 @@ var APPLICATIONS = {
857
924
  "domains": [
858
925
  "mail.topolo.app"
859
926
  ],
860
- "routes": [
861
- "mail.topolo.app"
862
- ],
927
+ "routes": [],
863
928
  "serviceId": "mail"
929
+ },
930
+ {
931
+ "name": "topolo-mail-api",
932
+ "kind": "worker",
933
+ "apiUrl": null,
934
+ "domains": [],
935
+ "routes": [],
936
+ "serviceId": null
864
937
  }
865
938
  ]
866
939
  },
@@ -881,47 +954,51 @@ var APPLICATIONS = {
881
954
  "domains": [
882
955
  "mdm-api.topolo.app"
883
956
  ],
884
- "routes": [
885
- "mdm-api.topolo.app"
886
- ],
957
+ "routes": [],
887
958
  "serviceId": "mdm"
888
959
  },
889
960
  {
890
961
  "name": "topolo-mdm-console",
891
962
  "kind": "worker",
892
- "apiUrl": null,
963
+ "apiUrl": "https://mdm.topolo.app",
893
964
  "domains": [
894
965
  "mdm.topolo.app"
895
966
  ],
896
- "routes": [
897
- "mdm.topolo.app"
898
- ],
967
+ "routes": [],
899
968
  "serviceId": null
900
969
  }
901
970
  ]
902
971
  },
903
- "messaging": {
904
- "id": "messaging",
905
- "appDir": "TopoloMessaging",
906
- "name": "topolo-messaging",
907
- "packageName": "topolo-messaging",
972
+ "messages": {
973
+ "id": "messages",
974
+ "appDir": "TopoloMessages",
975
+ "name": "topolo-messages",
976
+ "packageName": "topolo-messages",
908
977
  "productionUrl": "https://messages.topolo.app",
909
978
  "services": [
910
- "messaging"
979
+ "messages"
911
980
  ],
912
981
  "deployTargets": [
913
982
  {
914
- "name": "topolo-messaging",
983
+ "name": "topolo-messages",
915
984
  "kind": "worker",
916
985
  "apiUrl": "https://messages.topolo.app",
917
986
  "domains": [
918
987
  "messages.topolo.app"
919
988
  ],
920
989
  "routes": [],
921
- "serviceId": "messaging"
990
+ "serviceId": "messages"
922
991
  },
923
992
  {
924
- "name": "topolo-messaging-dispatch",
993
+ "name": "topolo-messages-api",
994
+ "kind": "worker",
995
+ "apiUrl": null,
996
+ "domains": [],
997
+ "routes": [],
998
+ "serviceId": null
999
+ },
1000
+ {
1001
+ "name": "topolo-messages-dispatch",
925
1002
  "kind": "worker",
926
1003
  "apiUrl": null,
927
1004
  "domains": [],
@@ -991,9 +1068,22 @@ var APPLICATIONS = {
991
1068
  "appDir": "TopoloObservabilityWatch",
992
1069
  "name": "topolo-observability-watch",
993
1070
  "packageName": "topolo-observability-watch",
994
- "productionUrl": null,
995
- "services": [],
996
- "deployTargets": []
1071
+ "productionUrl": "https://observability-watch.topolo.app",
1072
+ "services": [
1073
+ "observability_watch"
1074
+ ],
1075
+ "deployTargets": [
1076
+ {
1077
+ "name": "topolo-observability-watch",
1078
+ "kind": "worker",
1079
+ "apiUrl": "https://observability-watch.topolo.app",
1080
+ "domains": [
1081
+ "observability-watch.topolo.app"
1082
+ ],
1083
+ "routes": [],
1084
+ "serviceId": "observability_watch"
1085
+ }
1086
+ ]
997
1087
  },
998
1088
  "one": {
999
1089
  "id": "one",
@@ -1007,40 +1097,37 @@ var APPLICATIONS = {
1007
1097
  "deployTargets": [
1008
1098
  {
1009
1099
  "name": "topolo-one",
1010
- "kind": "pages",
1011
- "apiUrl": null,
1012
- "domains": [
1013
- "one.topolo.app"
1014
- ],
1015
- "routes": [],
1016
- "serviceId": null
1017
- },
1018
- {
1019
- "name": "topolo-one-website",
1020
1100
  "kind": "worker",
1021
1101
  "apiUrl": null,
1022
- "domains": [
1023
- "topolo.io",
1024
- "www.topolo.io"
1025
- ],
1102
+ "domains": [],
1026
1103
  "routes": [],
1027
1104
  "serviceId": null
1028
1105
  },
1029
1106
  {
1030
- "name": "topolo-one-worker",
1107
+ "name": "topolo-one-api",
1031
1108
  "kind": "worker",
1032
1109
  "apiUrl": "https://one.topolo.app",
1033
1110
  "domains": [
1034
- "one.topolo.app",
1035
- "topolo.app",
1036
- "www.topolo.app"
1111
+ "one.topolo.app"
1037
1112
  ],
1038
1113
  "routes": [
1039
1114
  "one.topolo.app/*",
1040
- "topolo.app",
1041
1115
  "www.topolo.app/*"
1042
1116
  ],
1043
1117
  "serviceId": "one"
1118
+ },
1119
+ {
1120
+ "name": "topolo-one-website",
1121
+ "kind": "worker",
1122
+ "apiUrl": "https://topolo.io",
1123
+ "domains": [
1124
+ "topolo.io"
1125
+ ],
1126
+ "routes": [
1127
+ "topolo.io",
1128
+ "www.topolo.io"
1129
+ ],
1130
+ "serviceId": null
1044
1131
  }
1045
1132
  ]
1046
1133
  },
@@ -1061,10 +1148,16 @@ var APPLICATIONS = {
1061
1148
  "domains": [
1062
1149
  "p2p.topolo.app"
1063
1150
  ],
1064
- "routes": [
1065
- "p2p.topolo.app"
1066
- ],
1151
+ "routes": [],
1067
1152
  "serviceId": "p2p"
1153
+ },
1154
+ {
1155
+ "name": "topolo-p2p-api",
1156
+ "kind": "worker",
1157
+ "apiUrl": null,
1158
+ "domains": [],
1159
+ "routes": [],
1160
+ "serviceId": null
1068
1161
  }
1069
1162
  ]
1070
1163
  },
@@ -1087,6 +1180,14 @@ var APPLICATIONS = {
1087
1180
  ],
1088
1181
  "routes": [],
1089
1182
  "serviceId": "pay"
1183
+ },
1184
+ {
1185
+ "name": "topolo-pay-api",
1186
+ "kind": "worker",
1187
+ "apiUrl": null,
1188
+ "domains": [],
1189
+ "routes": [],
1190
+ "serviceId": null
1090
1191
  }
1091
1192
  ]
1092
1193
  },
@@ -1107,10 +1208,16 @@ var APPLICATIONS = {
1107
1208
  "domains": [
1108
1209
  "people.topolo.app"
1109
1210
  ],
1110
- "routes": [
1111
- "people.topolo.app"
1112
- ],
1211
+ "routes": [],
1113
1212
  "serviceId": "people"
1213
+ },
1214
+ {
1215
+ "name": "topolo-people-api",
1216
+ "kind": "worker",
1217
+ "apiUrl": null,
1218
+ "domains": [],
1219
+ "routes": [],
1220
+ "serviceId": null
1114
1221
  }
1115
1222
  ]
1116
1223
  },
@@ -1131,10 +1238,16 @@ var APPLICATIONS = {
1131
1238
  "domains": [
1132
1239
  "pulse.topolo.app"
1133
1240
  ],
1134
- "routes": [
1135
- "pulse.topolo.app"
1136
- ],
1241
+ "routes": [],
1137
1242
  "serviceId": "pulse"
1243
+ },
1244
+ {
1245
+ "name": "topolo-pulse-api",
1246
+ "kind": "worker",
1247
+ "apiUrl": null,
1248
+ "domains": [],
1249
+ "routes": [],
1250
+ "serviceId": null
1138
1251
  }
1139
1252
  ]
1140
1253
  },
@@ -1156,7 +1269,9 @@ var APPLICATIONS = {
1156
1269
  "domains": [
1157
1270
  "api.ol0.me"
1158
1271
  ],
1159
- "routes": [],
1272
+ "routes": [
1273
+ "api.ol0.me/*"
1274
+ ],
1160
1275
  "serviceId": "quro.api"
1161
1276
  },
1162
1277
  {
@@ -1174,7 +1289,7 @@ var APPLICATIONS = {
1174
1289
  {
1175
1290
  "name": "quro-ui",
1176
1291
  "kind": "worker",
1177
- "apiUrl": null,
1292
+ "apiUrl": "https://quro.topolo.app",
1178
1293
  "domains": [
1179
1294
  "quro.topolo.app"
1180
1295
  ],
@@ -1196,7 +1311,7 @@ var APPLICATIONS = {
1196
1311
  {
1197
1312
  "name": "topolo-roadmapper",
1198
1313
  "kind": "worker",
1199
- "apiUrl": null,
1314
+ "apiUrl": "https://roadmapper.topolo.app",
1200
1315
  "domains": [
1201
1316
  "roadmapper.topolo.app"
1202
1317
  ],
@@ -1232,10 +1347,16 @@ var APPLICATIONS = {
1232
1347
  "domains": [
1233
1348
  "sign.topolo.app"
1234
1349
  ],
1235
- "routes": [
1236
- "sign.topolo.app"
1237
- ],
1350
+ "routes": [],
1238
1351
  "serviceId": "sign"
1352
+ },
1353
+ {
1354
+ "name": "topolo-sign-api",
1355
+ "kind": "worker",
1356
+ "apiUrl": null,
1357
+ "domains": [],
1358
+ "routes": [],
1359
+ "serviceId": null
1239
1360
  }
1240
1361
  ]
1241
1362
  },
@@ -1252,7 +1373,7 @@ var APPLICATIONS = {
1252
1373
  {
1253
1374
  "name": "topolo-social-studio",
1254
1375
  "kind": "worker",
1255
- "apiUrl": null,
1376
+ "apiUrl": "https://studio.topolo.app",
1256
1377
  "domains": [
1257
1378
  "studio.topolo.app"
1258
1379
  ],
@@ -1291,8 +1412,8 @@ var APPLICATIONS = {
1291
1412
  "deployTargets": [
1292
1413
  {
1293
1414
  "name": "topolo-socialize",
1294
- "kind": "pages",
1295
- "apiUrl": null,
1415
+ "kind": "worker",
1416
+ "apiUrl": "https://socialize.topolo.app",
1296
1417
  "domains": [
1297
1418
  "socialize.topolo.app"
1298
1419
  ],
@@ -1336,10 +1457,16 @@ var APPLICATIONS = {
1336
1457
  "domains": [
1337
1458
  "spaces.topolo.app"
1338
1459
  ],
1339
- "routes": [
1340
- "spaces.topolo.app"
1341
- ],
1460
+ "routes": [],
1342
1461
  "serviceId": "spaces"
1462
+ },
1463
+ {
1464
+ "name": "topolo-spaces-api",
1465
+ "kind": "worker",
1466
+ "apiUrl": null,
1467
+ "domains": [],
1468
+ "routes": [],
1469
+ "serviceId": null
1343
1470
  }
1344
1471
  ]
1345
1472
  },
@@ -1360,10 +1487,40 @@ var APPLICATIONS = {
1360
1487
  "domains": [
1361
1488
  "status.topolo.app"
1362
1489
  ],
1490
+ "routes": [],
1491
+ "serviceId": "status"
1492
+ }
1493
+ ]
1494
+ },
1495
+ "success": {
1496
+ "id": "success",
1497
+ "appDir": "TopoloSuccess",
1498
+ "name": "topolo-success",
1499
+ "packageName": "topolo-success",
1500
+ "productionUrl": "https://success.topolo.app",
1501
+ "services": [
1502
+ "success"
1503
+ ],
1504
+ "deployTargets": [
1505
+ {
1506
+ "name": "topolo-success",
1507
+ "kind": "worker",
1508
+ "apiUrl": "https://success.topolo.app",
1509
+ "domains": [
1510
+ "success.topolo.app"
1511
+ ],
1363
1512
  "routes": [
1364
- "status.topolo.app"
1513
+ "success.topolo.app"
1365
1514
  ],
1366
- "serviceId": "status"
1515
+ "serviceId": "success"
1516
+ },
1517
+ {
1518
+ "name": "topolo-success-api",
1519
+ "kind": "worker",
1520
+ "apiUrl": null,
1521
+ "domains": [],
1522
+ "routes": [],
1523
+ "serviceId": null
1367
1524
  }
1368
1525
  ]
1369
1526
  },
@@ -1388,6 +1545,14 @@ var APPLICATIONS = {
1388
1545
  "support.topolo.app/*"
1389
1546
  ],
1390
1547
  "serviceId": "support"
1548
+ },
1549
+ {
1550
+ "name": "topolo-support-api",
1551
+ "kind": "worker",
1552
+ "apiUrl": null,
1553
+ "domains": [],
1554
+ "routes": [],
1555
+ "serviceId": null
1391
1556
  }
1392
1557
  ]
1393
1558
  },
@@ -1408,10 +1573,16 @@ var APPLICATIONS = {
1408
1573
  "domains": [
1409
1574
  "survey.topolo.app"
1410
1575
  ],
1411
- "routes": [
1412
- "survey.topolo.app"
1413
- ],
1576
+ "routes": [],
1414
1577
  "serviceId": "survey"
1578
+ },
1579
+ {
1580
+ "name": "topolo-survey-api",
1581
+ "kind": "worker",
1582
+ "apiUrl": null,
1583
+ "domains": [],
1584
+ "routes": [],
1585
+ "serviceId": null
1415
1586
  }
1416
1587
  ]
1417
1588
  },
@@ -1432,10 +1603,16 @@ var APPLICATIONS = {
1432
1603
  "domains": [
1433
1604
  "voice.topolo.app"
1434
1605
  ],
1435
- "routes": [
1436
- "voice.topolo.app"
1437
- ],
1606
+ "routes": [],
1438
1607
  "serviceId": "voice"
1608
+ },
1609
+ {
1610
+ "name": "topolo-voice-api",
1611
+ "kind": "worker",
1612
+ "apiUrl": null,
1613
+ "domains": [],
1614
+ "routes": [],
1615
+ "serviceId": null
1439
1616
  }
1440
1617
  ]
1441
1618
  },
@@ -1466,13 +1643,16 @@ var APPLICATIONS = {
1466
1643
  "domains": [
1467
1644
  "sites.topolo.app"
1468
1645
  ],
1469
- "routes": [],
1646
+ "routes": [
1647
+ "*.sites.topolo.app/*",
1648
+ "sites.topolo.app"
1649
+ ],
1470
1650
  "serviceId": null
1471
1651
  },
1472
1652
  {
1473
1653
  "name": "topolo-web-studio",
1474
1654
  "kind": "worker",
1475
- "apiUrl": null,
1655
+ "apiUrl": "https://web.topolo.app",
1476
1656
  "domains": [
1477
1657
  "web.topolo.app"
1478
1658
  ],
@@ -1560,11 +1740,13 @@ var APPLICATION_REQUIREMENTS = [
1560
1740
  evidence: [
1561
1741
  "Auth service catalog contains the service ID and slug",
1562
1742
  "Permissions, role bundles, and API-key scopes exist for the service",
1563
- "SDK, CLI, and MCP service discovery include the callable production API"
1743
+ "SDK, CLI, and MCP service discovery include the callable production API",
1744
+ "CloudControl production HTTP targets classify agent reachability with sdk_service_id or agent_callable:false"
1564
1745
  ],
1565
1746
  implementation: [
1566
- "Add production Worker api_url metadata before adding an SDK service ID",
1567
- "Expose stable service IDs through DEFAULT_SERVICE_URLS",
1747
+ "Add production Worker api_url metadata and sdk_service_id for every agent-callable API target",
1748
+ "Mark browser, documentation, and runtime-only Worker targets with agent_callable:false",
1749
+ "Expose stable service IDs through generated DEFAULT_SERVICE_URLS and live catalog discovery",
1568
1750
  "Keep typed SDK/CLI/MCP surfaces in sync when an API contract stabilizes"
1569
1751
  ]
1570
1752
  },
@@ -1668,8 +1850,10 @@ var APPLICATION_REQUIREMENTS = [
1668
1850
  function applicationRequirementScopes(application) {
1669
1851
  const scopes = /* @__PURE__ */ new Set(["all"]);
1670
1852
  const appId = application.id;
1671
- const hasBrowserSurface = Boolean(application.productionUrl) || application.deployTargets.some((target) => target.kind === "pages");
1672
- const hasApiSurface = application.services.length > 0 || application.deployTargets.some((target) => Boolean(target.apiUrl));
1853
+ const hasBrowserSurface = Boolean(application.productionUrl) || // String() keeps this guard valid after the Pages->Worker migration narrowed
1854
+ // the generated kind union to "worker" (no "pages" literal remains).
1855
+ application.deployTargets.some((target) => String(target.kind) === "pages");
1856
+ const hasApiSurface = application.services.length > 0 || application.deployTargets.some((target) => Boolean(target.serviceId));
1673
1857
  if (hasBrowserSurface) scopes.add("browser");
1674
1858
  if (hasApiSurface) scopes.add("api");
1675
1859
  if (appId === "cli" || appId === "mcp" || appId === "cloudcontrol") {
@@ -1786,7 +1970,7 @@ function evaluateRequirement(application, requirement) {
1786
1970
  evidence
1787
1971
  );
1788
1972
  case "cloudflare-deployment-contract": {
1789
- if (application.deployTargets.length === 0) {
1973
+ if (application.deployTargets.length < 1) {
1790
1974
  return finding(requirement, "missing", "No CloudControl deploy targets are registered.", evidence);
1791
1975
  }
1792
1976
  evidence.push(`deployTargets=${application.deployTargets.length}`);
@@ -1873,6 +2057,7 @@ function resolveServiceUrl(service, overrides) {
1873
2057
  var WRITE_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
1874
2058
  var POSITIVE_CACHE_TTL_MS = 5 * 60 * 1e3;
1875
2059
  var NEGATIVE_CACHE_TTL_MS = 60 * 1e3;
2060
+ var CATALOG_CACHE_TTL_MS = 60 * 1e3;
1876
2061
  var TopoloClient = class _TopoloClient {
1877
2062
  credential;
1878
2063
  agent;
@@ -1882,6 +2067,7 @@ var TopoloClient = class _TopoloClient {
1882
2067
  timeoutMs;
1883
2068
  fetchImpl;
1884
2069
  debug;
2070
+ serviceCatalogCache = null;
1885
2071
  /**
1886
2072
  * Process-wide cache for slug → service-id lookups. Keyed by
1887
2073
  * `${authBaseUrl}|${slug}` so multiple `TopoloClient` instances pointing at
@@ -1927,7 +2113,8 @@ var TopoloClient = class _TopoloClient {
1927
2113
  `Mutating ${method} requests require { confirm: true }. This guardrail prevents agents from issuing writes without an explicit human-in-the-loop acknowledgement.`
1928
2114
  );
1929
2115
  }
1930
- const baseUrl = resolveServiceUrl(opts.service, this.serviceUrls);
2116
+ const resolvedService = await this.resolveRequestService(opts.service);
2117
+ const baseUrl = resolvedService.baseUrl;
1931
2118
  const url = new URL(opts.path, baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`);
1932
2119
  if (opts.query) {
1933
2120
  for (const [k, v] of Object.entries(opts.query)) {
@@ -1937,7 +2124,7 @@ var TopoloClient = class _TopoloClient {
1937
2124
  const headers = new Headers();
1938
2125
  headers.set("Accept", "application/json");
1939
2126
  if (opts.body !== void 0) headers.set("Content-Type", "application/json");
1940
- const platformServiceId = resolveConfiguredServiceId(opts.service, this.serviceIds);
2127
+ const platformServiceId = resolveConfiguredServiceId(resolvedService.serviceKey, this.serviceIds);
1941
2128
  if (platformServiceId) headers.set("X-Service-ID", platformServiceId);
1942
2129
  applyAuthHeaders(headers, this.credential);
1943
2130
  const requestId = generateRequestId();
@@ -1950,7 +2137,7 @@ var TopoloClient = class _TopoloClient {
1950
2137
  const signal = opts.signal ? mergeSignals(opts.signal, controller.signal) : controller.signal;
1951
2138
  const urlStr = url.toString();
1952
2139
  const startedAt = Date.now();
1953
- this.emit({ phase: "request", method, service: opts.service, path: opts.path, url: urlStr, requestId });
2140
+ this.emit({ phase: "request", method, service: resolvedService.serviceKey, path: opts.path, url: urlStr, requestId });
1954
2141
  let res;
1955
2142
  try {
1956
2143
  res = await this.fetchImpl(urlStr, {
@@ -1963,7 +2150,7 @@ var TopoloClient = class _TopoloClient {
1963
2150
  this.emit({
1964
2151
  phase: "error",
1965
2152
  method,
1966
- service: opts.service,
2153
+ service: resolvedService.serviceKey,
1967
2154
  path: opts.path,
1968
2155
  url: urlStr,
1969
2156
  requestId,
@@ -1982,7 +2169,7 @@ var TopoloClient = class _TopoloClient {
1982
2169
  this.emit({
1983
2170
  phase: "error",
1984
2171
  method,
1985
- service: opts.service,
2172
+ service: resolvedService.serviceKey,
1986
2173
  path: opts.path,
1987
2174
  url: urlStr,
1988
2175
  requestId,
@@ -1997,12 +2184,12 @@ var TopoloClient = class _TopoloClient {
1997
2184
  extractRequired(parsed)
1998
2185
  );
1999
2186
  }
2000
- throw new TopoloHttpError(opts.service, opts.path, res.status, parsed);
2187
+ throw new TopoloHttpError(resolvedService.serviceKey, opts.path, res.status, parsed);
2001
2188
  }
2002
2189
  this.emit({
2003
2190
  phase: "response",
2004
2191
  method,
2005
- service: opts.service,
2192
+ service: resolvedService.serviceKey,
2006
2193
  path: opts.path,
2007
2194
  url: urlStr,
2008
2195
  requestId,
@@ -2011,6 +2198,31 @@ var TopoloClient = class _TopoloClient {
2011
2198
  });
2012
2199
  return parsed;
2013
2200
  }
2201
+ async resolveRequestService(service) {
2202
+ const serviceKey = normalizeLookupKey(service);
2203
+ if (!serviceKey) {
2204
+ throw new TopoloSdkError("unknown_service", "service is required");
2205
+ }
2206
+ const overrideUrl = resolveConfiguredServiceUrl(serviceKey, this.serviceUrls);
2207
+ if (overrideUrl) return { serviceKey, baseUrl: overrideUrl };
2208
+ const staticUrl = DEFAULT_SERVICE_URLS[serviceKey];
2209
+ if (staticUrl) return { serviceKey, baseUrl: staticUrl };
2210
+ const live = await this.resolveLiveCallableService(serviceKey);
2211
+ if (live) return live;
2212
+ throw new TopoloSdkError(
2213
+ "unknown_service",
2214
+ `Unknown Topolo service "${service}". Run service discovery first and pass a callable service id or slug.`
2215
+ );
2216
+ }
2217
+ async resolveLiveCallableService(serviceKey) {
2218
+ const catalog = await this.listServices();
2219
+ const match = catalog.find((entry) => catalogEntryMatches(entry, serviceKey));
2220
+ if (!match?.baseUrl || !isCallableCatalogEntry(match)) return null;
2221
+ return {
2222
+ serviceKey: normalizeLookupKey(match.slug || match.name || match.serviceId) || serviceKey,
2223
+ baseUrl: match.baseUrl
2224
+ };
2225
+ }
2014
2226
  /**
2015
2227
  * Resolve a service-slug → opaque service-id via caller config or TopoloAuth.
2016
2228
  *
@@ -2081,6 +2293,10 @@ var TopoloClient = class _TopoloClient {
2081
2293
  * authoritative; the snapshot is a floor, not a cap.
2082
2294
  */
2083
2295
  async listServices() {
2296
+ const now = Date.now();
2297
+ if (this.serviceCatalogCache && this.serviceCatalogCache.expiresAt > now) {
2298
+ return this.serviceCatalogCache.entries;
2299
+ }
2084
2300
  try {
2085
2301
  const res = await this.request({ service: "auth", method: "GET", path: "/api/services/catalog" });
2086
2302
  const raw = res.data?.services ?? res.services;
@@ -2101,7 +2317,13 @@ var TopoloClient = class _TopoloClient {
2101
2317
  launchable: s.launchable === true
2102
2318
  });
2103
2319
  }
2104
- if (entries.length > 0) return entries;
2320
+ if (entries.length > 0) {
2321
+ this.serviceCatalogCache = {
2322
+ entries,
2323
+ expiresAt: now + CATALOG_CACHE_TTL_MS
2324
+ };
2325
+ return entries;
2326
+ }
2105
2327
  }
2106
2328
  } catch {
2107
2329
  }
@@ -2166,8 +2388,18 @@ function resolveConfiguredServiceId(slug, serviceIds) {
2166
2388
  }
2167
2389
  return null;
2168
2390
  }
2391
+ function resolveConfiguredServiceUrl(slug, serviceUrls) {
2392
+ for (const serviceKey of serviceKeyCandidates(slug)) {
2393
+ const configured = serviceUrls?.[serviceKey];
2394
+ if (typeof configured === "string" && configured.trim()) return configured.trim();
2395
+ const envKey = `TOPOLO_SERVICE_URL_${serviceKey.replace(/\./g, "_").toUpperCase()}`;
2396
+ const envValue = typeof process !== "undefined" ? process.env?.[envKey] : void 0;
2397
+ if (typeof envValue === "string" && envValue.trim()) return envValue.trim();
2398
+ }
2399
+ return null;
2400
+ }
2169
2401
  function serviceKeyCandidates(slug) {
2170
- const normalized = slug.trim().toLowerCase();
2402
+ const normalized = normalizeLookupKey(slug);
2171
2403
  const withoutTopolo = normalized.startsWith("topolo-") ? normalized.slice("topolo-".length) : normalized;
2172
2404
  return [.../* @__PURE__ */ new Set([
2173
2405
  normalized,
@@ -2176,6 +2408,33 @@ function serviceKeyCandidates(slug) {
2176
2408
  withoutTopolo.replace(/-/g, "_")
2177
2409
  ])];
2178
2410
  }
2411
+ function normalizeLookupKey(value) {
2412
+ return String(value || "").trim().toLowerCase();
2413
+ }
2414
+ function catalogEntryMatches(entry, needle) {
2415
+ return catalogEntryLookupKeys(entry).includes(needle);
2416
+ }
2417
+ function catalogEntryLookupKeys(entry) {
2418
+ const values = [entry.serviceId, entry.slug, entry.name].filter((value) => typeof value === "string" && value.trim().length > 0);
2419
+ const keys = /* @__PURE__ */ new Set();
2420
+ for (const value of values) {
2421
+ const normalized = normalizeLookupKey(value);
2422
+ if (!normalized) continue;
2423
+ keys.add(normalized);
2424
+ keys.add(normalized.replace(/-/g, "_"));
2425
+ if (normalized.startsWith("topolo-")) {
2426
+ const withoutTopolo = normalized.slice("topolo-".length);
2427
+ keys.add(withoutTopolo);
2428
+ keys.add(withoutTopolo.replace(/-/g, "_"));
2429
+ }
2430
+ }
2431
+ return [...keys];
2432
+ }
2433
+ function isCallableCatalogEntry(entry) {
2434
+ const kind = normalizeLookupKey(entry.surfaceKind || "");
2435
+ if (kind === "api" || kind === "technical_service" || kind === "service") return true;
2436
+ return Boolean(entry.slug && Object.prototype.hasOwnProperty.call(DEFAULT_SERVICE_URLS, catalogEntryLookupKeys(entry)[0] ?? ""));
2437
+ }
2179
2438
  function describeError(body, fallback) {
2180
2439
  if (body && typeof body === "object") {
2181
2440
  const maybe = body;
@@ -3013,12 +3272,12 @@ function formatContact(c) {
3013
3272
  }
3014
3273
 
3015
3274
  // src/commands/api.ts
3016
- var KNOWN_SERVICES = Object.keys(DEFAULT_SERVICE_URLS).sort();
3275
+ var STATIC_SERVICE_IDS = new Set(Object.keys(DEFAULT_SERVICE_URLS));
3017
3276
  var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
3018
3277
  var WRITE_METHODS2 = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
3019
3278
  function registerApiCommand(program2) {
3020
3279
  program2.command("api <service> <method> <path>").description(
3021
- `Generic passthrough to a Topolo service. Known services: ${KNOWN_SERVICES.join(", ")}.`
3280
+ "Generic passthrough to a Topolo service. Run `topolo services --json` to discover callable services."
3022
3281
  ).option("-d, --data <json>", "JSON body (for POST/PUT/PATCH)").option(
3023
3282
  "-q, --query <kv...>",
3024
3283
  "Query params as key=value (repeatable), e.g. --query source=cli page=2"
@@ -3033,22 +3292,17 @@ function registerApiCommand(program2) {
3033
3292
  );
3034
3293
  }
3035
3294
  const httpMethod = upperMethod;
3036
- if (!KNOWN_SERVICES.includes(service)) {
3037
- throw new Error(
3038
- `Unknown service "${service}". Known services: ${KNOWN_SERVICES.join(", ")}`
3039
- );
3040
- }
3041
3295
  if (WRITE_METHODS2.has(httpMethod) && !opts.confirm) {
3042
3296
  throw new Error(`Mutating ${httpMethod} requires --confirm.`);
3043
3297
  }
3044
3298
  const environment = selectedEnvironment();
3045
3299
  const config = await readConfig();
3046
3300
  const profile = resolvedProfileConfig(config, environment);
3047
- assertServiceUrlForEnvironment(
3048
- service,
3049
- resolveServiceUrl(service, profile.serviceUrls),
3050
- environment
3051
- );
3301
+ const configuredUrl = resolveConfiguredServiceUrl2(service, profile.serviceUrls);
3302
+ const staticUrl = STATIC_SERVICE_IDS.has(service) ? DEFAULT_SERVICE_URLS[service] : void 0;
3303
+ if (configuredUrl || staticUrl) {
3304
+ assertServiceUrlForEnvironment(service, configuredUrl ?? staticUrl, environment);
3305
+ }
3052
3306
  let body = void 0;
3053
3307
  if (opts.data) {
3054
3308
  try {
@@ -3079,6 +3333,21 @@ function registerApiCommand(program2) {
3079
3333
  }
3080
3334
  );
3081
3335
  }
3336
+ function resolveConfiguredServiceUrl2(service, serviceUrls) {
3337
+ const normalized = service.trim().toLowerCase();
3338
+ const withoutTopolo = normalized.startsWith("topolo-") ? normalized.slice("topolo-".length) : normalized;
3339
+ const candidates = [
3340
+ normalized,
3341
+ normalized.replace(/-/g, "_"),
3342
+ withoutTopolo,
3343
+ withoutTopolo.replace(/-/g, "_")
3344
+ ];
3345
+ for (const candidate of candidates) {
3346
+ const url = serviceUrls?.[candidate];
3347
+ if (typeof url === "string" && url.trim()) return url.trim();
3348
+ }
3349
+ return void 0;
3350
+ }
3082
3351
  function parseQueryPairs(pairs) {
3083
3352
  if (!pairs || pairs.length === 0) return void 0;
3084
3353
  const out = {};
@@ -3108,6 +3377,8 @@ import {
3108
3377
  } from "fs";
3109
3378
  import path from "path";
3110
3379
  var EVIDENCE_FRESHNESS_MS = 36 * 60 * 60 * 1e3;
3380
+ var REQUIRED_AUTH_CLIENT_VERSION = "1.1.9";
3381
+ var REQUIRED_UI_KIT_VERSION = "0.4.67";
3111
3382
  function readEvidenceFile(appRoot) {
3112
3383
  const candidate = path.join(appRoot, ".topolo", "evidence.json");
3113
3384
  if (!isFile(candidate)) return null;
@@ -3269,28 +3540,53 @@ function enrichFinding(application, finding2, evidence) {
3269
3540
  evidence: ["service=auth"]
3270
3541
  });
3271
3542
  }
3543
+ const usesWorkerRuntime = authEvidence.includes("source=@topolo-io/worker-runtime") || authEvidence.includes("source=topolo-worker-runtime") || authEvidence.includes("source=createTopoloWorkerRuntime");
3272
3544
  const usesAuthMiddleware = authEvidence.includes("source=@topolo-io/auth-middleware") || authEvidence.includes("source=topolo-auth-middleware") || authEvidence.includes("source=TopoloAuthMiddleware");
3273
3545
  const usesAuthClient = authEvidence.includes("source=@topolo-io/auth-client") || authEvidence.includes("source=topolo-auth-client");
3274
3546
  const usesTopoloClient = authEvidence.includes("source=TopoloClient");
3275
3547
  const usesAuthValidateFetch = authEvidence.includes("source=auth-validate-fetch");
3276
- if (usesAuthMiddleware || usesAuthClient || usesTopoloClient || usesAuthValidateFetch) {
3548
+ if (authEvidence.includes(`dependency-version=@topolo-io/auth-client<${REQUIRED_AUTH_CLIENT_VERSION}`)) {
3549
+ return mergeFinding(finding2, {
3550
+ status: "needs_review",
3551
+ reason: `The app depends on @topolo-io/auth-client below ${REQUIRED_AUTH_CLIENT_VERSION}; public-route auth boot and session-channel fixes require the current shared client.`,
3552
+ evidence: authEvidence,
3553
+ nextActions: [
3554
+ `Update @topolo-io/auth-client to ${REQUIRED_AUTH_CLIENT_VERSION} or the workspace package that resolves to it`,
3555
+ "Regenerate the lockfile without reverting unrelated app changes",
3556
+ "Rerun this audit after the package update"
3557
+ ]
3558
+ });
3559
+ }
3560
+ if (authEvidence.includes("auth-boot=blocking-cookie-refresh") && !authEvidence.includes("auth-boot=background-cookie-refresh")) {
3561
+ return mergeFinding(finding2, {
3562
+ status: "needs_review",
3563
+ reason: "The app appears to block all browser routes on auth cookie refresh. Public landing and login routes must render immediately and restore the cookie session in the background.",
3564
+ evidence: authEvidence,
3565
+ nextActions: [
3566
+ "Use authClient.initialize({ cookieRefresh: 'background' }) or initializeSession({ cookieRefresh: 'background' }) on public landing/login routes",
3567
+ "Keep cookieRefresh: 'blocking' for protected workspace routes",
3568
+ "Subscribe to auth:session-updated so background restore updates the visible user state"
3569
+ ]
3570
+ });
3571
+ }
3572
+ if (usesWorkerRuntime || usesAuthMiddleware || usesAuthClient || usesTopoloClient || usesAuthValidateFetch) {
3277
3573
  return mergeFinding(finding2, {
3278
3574
  status: "met",
3279
- reason: "Canonical Topolo auth wiring is present (auth-middleware verifyRequest, auth-client browser module, or TopoloClient).",
3575
+ reason: "Canonical Topolo auth wiring is present (worker-runtime, auth-middleware verifyRequest, auth-client browser session controller, or TopoloClient).",
3280
3576
  evidence: authEvidence
3281
3577
  });
3282
3578
  }
3283
3579
  return mergeFinding(finding2, {
3284
3580
  status: "partial",
3285
- reason: "Shared Topolo auth dependency is declared but the canonical wiring (verifyRequest / createBrowserAuthModule / useTopoloAuth / TopoloClient) is not visible.",
3581
+ reason: "Shared Topolo auth dependency is declared but the canonical wiring (verifyRequest / createBrowserAuthSessionController / useTopoloAuth / TopoloClient) is not visible.",
3286
3582
  evidence: authEvidence
3287
3583
  });
3288
3584
  }
3289
3585
  case "service-registration-and-scopes": {
3290
- const applicationId = String(application.id);
3291
3586
  const declaredServices = application.services ?? [];
3292
3587
  if (declaredServices.length === 0) {
3293
- if (applicationId === "cli" || applicationId === "mcp" || applicationId === "cloudcontrol") {
3588
+ const applicationId = String(application.id);
3589
+ if (applicationId === "cloudcontrol") {
3294
3590
  return mergeFinding(finding2, {
3295
3591
  status: "met",
3296
3592
  reason: "This application is itself a tooling/agent surface and does not register a callable platform service.",
@@ -3373,6 +3669,18 @@ function enrichFinding(application, finding2, evidence) {
3373
3669
  });
3374
3670
  }
3375
3671
  if (shellEvidence.length === 0) return finding2;
3672
+ if (shellEvidence.includes(`dependency-version=@topolo-io/ui-kit<${REQUIRED_UI_KIT_VERSION}`)) {
3673
+ return mergeFinding(finding2, {
3674
+ status: "needs_review",
3675
+ reason: `The app depends on @topolo-io/ui-kit below ${REQUIRED_UI_KIT_VERSION}; launcher catalog cache/dedupe fixes require the current shared kit.`,
3676
+ evidence: shellEvidence,
3677
+ nextActions: [
3678
+ `Update @topolo-io/ui-kit to ${REQUIRED_UI_KIT_VERSION}`,
3679
+ "Regenerate the lockfile without reverting unrelated app changes",
3680
+ "Rerun this audit after the package update"
3681
+ ]
3682
+ });
3683
+ }
3376
3684
  if (shellEvidence.includes("forbidden=workspace-shell")) {
3377
3685
  return mergeFinding(finding2, {
3378
3686
  status: "needs_review",
@@ -3439,7 +3747,7 @@ function enrichFinding(application, finding2, evidence) {
3439
3747
  }
3440
3748
  const text = evidence.source.text;
3441
3749
  const hasOrganizationIdColumn = orgEvidence.includes("source=organization_id") || orgEvidence.includes("source=organizationId") || orgEvidence.includes("source=tenantId") || orgEvidence.includes("source=tenant_id");
3442
- const hasCredentialBoundary = orgEvidence.includes("source=organization-auth-boundary") || text.includes("verifyRequest") || text.includes("createBrowserAuthModule") || text.includes("useTopoloAuth");
3750
+ const hasCredentialBoundary = orgEvidence.includes("source=organization-auth-boundary") || text.includes("verifyRequest") || text.includes("createBrowserAuthSessionController") || text.includes("useTopoloAuth");
3443
3751
  if (hasOrganizationIdColumn && hasCredentialBoundary) {
3444
3752
  return mergeFinding(finding2, {
3445
3753
  status: "met",
@@ -3454,9 +3762,9 @@ function enrichFinding(application, finding2, evidence) {
3454
3762
  });
3455
3763
  }
3456
3764
  case "agent-and-operator-surfaces": {
3457
- const applicationId = String(application.id);
3458
3765
  const declaredServices = application.services ?? [];
3459
3766
  const text = evidence.source.text;
3767
+ const applicationId = String(application.id);
3460
3768
  const items = [];
3461
3769
  if (declaredServices.length > 0) items.push(`services=${declaredServices.join(",")}`);
3462
3770
  if (text.includes("TopoloClient")) items.push("source=TopoloClient");
@@ -3464,10 +3772,10 @@ function enrichFinding(application, finding2, evidence) {
3464
3772
  items.push("source=confirm-write-gate");
3465
3773
  }
3466
3774
  if (text.includes("@topolo/sdk")) items.push("source=@topolo/sdk");
3467
- if (applicationId === "cli" || applicationId === "mcp" || applicationId === "cloudcontrol") {
3775
+ if (applicationId === "cloudcontrol") {
3468
3776
  return mergeFinding(finding2, {
3469
3777
  status: "met",
3470
- reason: "This application is itself an agent/operator surface (CLI / MCP / CloudControl).",
3778
+ reason: "This application is itself an agent/operator surface (CloudControl).",
3471
3779
  evidence: [`tooling=${applicationId}`, ...items],
3472
3780
  nextActions: []
3473
3781
  });
@@ -3715,15 +4023,24 @@ function sharedAuthEvidence(evidence) {
3715
4023
  const text = evidence.source.text;
3716
4024
  const items = [];
3717
4025
  if (deps.has("@topolo-io/auth-client")) items.push("dependency=@topolo-io/auth-client");
4026
+ if (packageDependencyVersions(evidence, "@topolo-io/auth-client").some((version) => isStaleDependencyVersion(version, REQUIRED_AUTH_CLIENT_VERSION))) {
4027
+ items.push(`dependency-version=@topolo-io/auth-client<${REQUIRED_AUTH_CLIENT_VERSION}`);
4028
+ }
3718
4029
  if (deps.has("@topolo-io/auth-middleware")) items.push("dependency=@topolo-io/auth-middleware");
4030
+ if (deps.has("@topolo-io/worker-runtime")) items.push("dependency=@topolo-io/worker-runtime");
3719
4031
  if (deps.has("@topolo/sdk")) items.push("dependency=@topolo/sdk");
3720
4032
  if (text.includes("@topolo-io/auth-client")) items.push("source=@topolo-io/auth-client");
3721
4033
  if (text.includes("@topolo-io/auth-middleware")) items.push("source=@topolo-io/auth-middleware");
4034
+ if (text.includes("@topolo-io/worker-runtime")) items.push("source=@topolo-io/worker-runtime");
4035
+ if (text.includes("topolo-worker-runtime")) items.push("source=topolo-worker-runtime");
4036
+ if (text.includes("createTopoloWorkerRuntime")) items.push("source=createTopoloWorkerRuntime");
3722
4037
  if (text.includes("topolo-auth-client")) items.push("source=topolo-auth-client");
3723
4038
  if (text.includes("topolo-auth-middleware")) items.push("source=topolo-auth-middleware");
3724
4039
  if (text.includes("TopoloAuthMiddleware")) items.push("source=TopoloAuthMiddleware");
3725
4040
  if (text.includes("TopoloClient")) items.push("source=TopoloClient");
3726
4041
  if (text.includes("TopoloOAuth")) items.push("source=TopoloOAuth");
4042
+ if (/\.initialize(?:Session)?\(\s*\)/.test(text)) items.push("auth-boot=blocking-cookie-refresh");
4043
+ if (/cookieRefresh:\s*['"]background['"]/.test(text)) items.push("auth-boot=background-cookie-refresh");
3727
4044
  if (/Authorization.*Bearer/i.test(text) && (text.includes("/validate") || text.includes("TOPOLO_AUTH_URL") || text.includes("AUTH_BASE_URL") || text.includes("auth.topolo.app"))) {
3728
4045
  items.push("source=auth-validate-fetch");
3729
4046
  }
@@ -3745,6 +4062,9 @@ function sharedShellEvidence(evidence) {
3745
4062
  const text = evidence.source.text;
3746
4063
  const items = [];
3747
4064
  if (deps.has("@topolo-io/ui-kit")) items.push("dependency=@topolo-io/ui-kit");
4065
+ if (packageDependencyVersions(evidence, "@topolo-io/ui-kit").some((version) => isStaleDependencyVersion(version, REQUIRED_UI_KIT_VERSION))) {
4066
+ items.push(`dependency-version=@topolo-io/ui-kit<${REQUIRED_UI_KIT_VERSION}`);
4067
+ }
3748
4068
  if (text.includes("@topolo-io/ui-kit")) items.push("source=@topolo-io/ui-kit");
3749
4069
  if (text.includes("TopoloAppShell")) items.push("source=TopoloAppShell");
3750
4070
  if (text.includes(["Topolo", "Workspace", "Shell"].join(""))) items.push("forbidden=workspace-shell");
@@ -3851,6 +4171,36 @@ function dependencyNames(evidence) {
3851
4171
  ])
3852
4172
  );
3853
4173
  }
4174
+ function packageDependencyVersions(evidence, packageName) {
4175
+ return unique(
4176
+ evidence.packageJsons.flatMap((record) => {
4177
+ const specs = [
4178
+ record.packageJson.dependencies?.[packageName],
4179
+ record.packageJson.devDependencies?.[packageName],
4180
+ record.packageJson.peerDependencies?.[packageName]
4181
+ ];
4182
+ return specs.filter((spec) => typeof spec === "string");
4183
+ })
4184
+ );
4185
+ }
4186
+ function isStaleDependencyVersion(spec, minimumVersion) {
4187
+ if (spec.startsWith("workspace:")) return false;
4188
+ const current = parseDependencyVersion(spec);
4189
+ const minimum = parseDependencyVersion(minimumVersion);
4190
+ if (!current || !minimum) return false;
4191
+ const currentParts = current;
4192
+ const minimumParts = minimum;
4193
+ for (const index of [0, 1, 2]) {
4194
+ if (currentParts[index] < minimumParts[index]) return true;
4195
+ if (currentParts[index] > minimumParts[index]) return false;
4196
+ }
4197
+ return false;
4198
+ }
4199
+ function parseDependencyVersion(spec) {
4200
+ const match = spec.match(/(\d+)\.(\d+)\.(\d+)/);
4201
+ if (!match) return null;
4202
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
4203
+ }
3854
4204
  function matchingSystems(repoRoot, application) {
3855
4205
  const docsRoot = topoloDocsRoot(repoRoot);
3856
4206
  const systemsRoot = path.join(docsRoot, "src", "content", "systems");
@@ -4204,6 +4554,8 @@ function scaffoldFiles(options) {
4204
4554
  const { application, target } = options;
4205
4555
  const slug = toSlug(application.id);
4206
4556
  const base = target === "platform" ? firstPartyAppPath(application.appDir, options.payload.audience) : application.appDir;
4557
+ const browserBase = target === "platform" ? `${base}/apps/web` : base;
4558
+ const apiBase = target === "platform" ? `${base}/apps/api` : base;
4207
4559
  const files = [
4208
4560
  {
4209
4561
  path: `${base}/package.json`,
@@ -4250,10 +4602,10 @@ function scaffoldFiles(options) {
4250
4602
  "uses strict mode",
4251
4603
  "includes browser DOM types for React and Worker types for API-backed apps"
4252
4604
  ],
4253
- content: tsconfigJson(options.api)
4605
+ content: tsconfigJson(options.api, target)
4254
4606
  },
4255
4607
  {
4256
- path: `${base}/vite.config.ts`,
4608
+ path: `${browserBase}/vite.config.ts`,
4257
4609
  purpose: "Vite browser build configuration.",
4258
4610
  requiredSignals: [
4259
4611
  "builds the authenticated browser surface",
@@ -4262,7 +4614,7 @@ function scaffoldFiles(options) {
4262
4614
  content: viteConfig(target)
4263
4615
  },
4264
4616
  {
4265
- path: `${base}/index.html`,
4617
+ path: `${browserBase}/index.html`,
4266
4618
  purpose: "Browser entry document.",
4267
4619
  requiredSignals: [
4268
4620
  "mounts the React app at #root",
@@ -4271,7 +4623,7 @@ function scaffoldFiles(options) {
4271
4623
  content: indexHtml(application.name)
4272
4624
  },
4273
4625
  {
4274
- path: `${base}/src/main.tsx`,
4626
+ path: `${browserBase}/src/main.tsx`,
4275
4627
  purpose: "React app entrypoint.",
4276
4628
  requiredSignals: [
4277
4629
  "renders the generated app component",
@@ -4280,7 +4632,7 @@ function scaffoldFiles(options) {
4280
4632
  content: mainTsx()
4281
4633
  },
4282
4634
  {
4283
- path: `${base}/src/env.d.ts`,
4635
+ path: `${browserBase}/src/env.d.ts`,
4284
4636
  purpose: "Vite environment variable typing.",
4285
4637
  requiredSignals: [
4286
4638
  "loads vite/client import.meta.env types",
@@ -4300,7 +4652,7 @@ function scaffoldFiles(options) {
4300
4652
  }
4301
4653
  ] : [],
4302
4654
  {
4303
- path: `${base}/src/styles.css`,
4655
+ path: `${browserBase}/src/styles.css`,
4304
4656
  purpose: "Minimal app-local styles outside the shared Topolo shell.",
4305
4657
  requiredSignals: [
4306
4658
  "does not fork the shared shell chrome",
@@ -4311,6 +4663,19 @@ function scaffoldFiles(options) {
4311
4663
  ];
4312
4664
  if (target === "platform") {
4313
4665
  files.push(
4666
+ {
4667
+ path: `${base}/topolo.app.json`,
4668
+ purpose: "Canonical app manifest that drives generated Wrangler and CloudControl metadata.",
4669
+ requiredSignals: [
4670
+ "declares web/api targets with canonical names and app-local target directories",
4671
+ "keeps Cloudflare account, domain, service id, and bindings in one manifest source of truth"
4672
+ ],
4673
+ content: topoloAppManifestJson(application, {
4674
+ api: options.api,
4675
+ resources: options.payload.resources,
4676
+ profile: options.payload.profile
4677
+ })
4678
+ },
4314
4679
  {
4315
4680
  path: `${base}/topolo.cloudcontrol.json`,
4316
4681
  purpose: "CloudControl source of truth for deploy targets, routes, bindings, and CI metadata.",
@@ -4436,7 +4801,7 @@ function scaffoldFiles(options) {
4436
4801
  if (options.browser) {
4437
4802
  if (target === "platform") {
4438
4803
  files.push({
4439
- path: `${base}/src/service-identity.ts`,
4804
+ path: `${browserBase}/src/service-identity.ts`,
4440
4805
  purpose: "Central service identity resolver shared by browser and Worker runtime surfaces.",
4441
4806
  requiredSignals: [
4442
4807
  "exports the generated app id, display name, and stable Auth service slug",
@@ -4446,7 +4811,7 @@ function scaffoldFiles(options) {
4446
4811
  });
4447
4812
  }
4448
4813
  files.push({
4449
- path: `${base}/src/App.tsx`,
4814
+ path: `${browserBase}/src/App.tsx`,
4450
4815
  purpose: "Authenticated browser surface.",
4451
4816
  requiredSignals: [
4452
4817
  "imports shared TopoloAppShell from @topolo-io/ui-kit",
@@ -4463,16 +4828,16 @@ function scaffoldFiles(options) {
4463
4828
  if (options.api) {
4464
4829
  files.push(
4465
4830
  {
4466
- path: `${base}/wrangler.toml`,
4831
+ path: `${apiBase}/wrangler.toml`,
4467
4832
  purpose: "Cloudflare Worker runtime configuration.",
4468
4833
  requiredSignals: [
4469
4834
  "bindings match topolo.cloudcontrol.json or topolo.app.json",
4470
4835
  "secrets are referenced by name only"
4471
4836
  ],
4472
- content: wranglerToml(application, target, options.payload.resources, options.payload.profile)
4837
+ content: target === "platform" ? platformApiWranglerToml(application, options.payload.resources, options.payload.profile) : wranglerToml(application, target, options.payload.resources, options.payload.profile)
4473
4838
  },
4474
4839
  {
4475
- path: `${base}/src/worker.ts`,
4840
+ path: `${apiBase}/src/worker.ts`,
4476
4841
  purpose: "Protected service boundary.",
4477
4842
  requiredSignals: [
4478
4843
  "uses @topolo-io/worker-runtime for Worker auth, gatewaying, headers, and request helpers",
@@ -4487,6 +4852,37 @@ function scaffoldFiles(options) {
4487
4852
  })
4488
4853
  }
4489
4854
  );
4855
+ if (target === "platform") {
4856
+ files.push(
4857
+ {
4858
+ path: `${apiBase}/src/service-identity.ts`,
4859
+ purpose: "Runtime service identity resolver for the API Worker.",
4860
+ requiredSignals: [
4861
+ "resolves the concrete Auth service id from Worker vars",
4862
+ "keeps the API Worker independent from browser-only import.meta usage"
4863
+ ],
4864
+ content: apiServiceIdentityTs(application)
4865
+ },
4866
+ {
4867
+ path: `${browserBase}/src/worker.ts`,
4868
+ purpose: "Browser Worker that serves assets and forwards API paths to the API Worker binding.",
4869
+ requiredSignals: [
4870
+ "keeps the frontend Worker thin",
4871
+ "routes /api/* to the API service binding instead of owning backend routes locally"
4872
+ ],
4873
+ content: webWorkerTs(application)
4874
+ },
4875
+ {
4876
+ path: `${browserBase}/wrangler.toml`,
4877
+ purpose: "Cloudflare Worker static-assets configuration for the browser surface.",
4878
+ requiredSignals: [
4879
+ "serves Vite assets through a Worker with assets",
4880
+ "binds the frontend Worker to the API Worker for /api/* requests"
4881
+ ],
4882
+ content: platformWebWranglerToml(application)
4883
+ }
4884
+ );
4885
+ }
4490
4886
  }
4491
4887
  return files;
4492
4888
  }
@@ -4516,20 +4912,22 @@ function topoloConformanceWorkflow(options) {
4516
4912
  const docsCheckoutStep = options.appDir === "TopoloDocs" ? [] : [
4517
4913
  "",
4518
4914
  " - name: Checkout TopoloDocs",
4915
+ ` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
4519
4916
  " uses: actions/checkout@v4",
4520
4917
  " with:",
4521
- " repository: Topolo-io/docs",
4918
+ " repository: Topolo-io/TopoloDocs",
4522
4919
  " path: Apps/system/TopoloDocs",
4523
- " token: ${{ secrets.TOPOLO_CI_REPO_TOKEN || github.token }}"
4920
+ " token: ${{ steps.app-token.outputs.token || secrets.TOPOLO_CI_REPO_TOKEN }}"
4524
4921
  ];
4525
4922
  const cliCheckoutStep = options.appDir === "TopoloCLI" || options.appDir === "TopoloCli" ? [] : [
4526
4923
  "",
4527
4924
  " - name: Checkout Topolo CLI",
4925
+ ` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
4528
4926
  " uses: actions/checkout@v4",
4529
4927
  " with:",
4530
4928
  " repository: Topolo-io/TopoloCli",
4531
4929
  " path: Apps/system/TopoloCLI",
4532
- " token: ${{ secrets.TOPOLO_CI_REPO_TOKEN || github.token }}"
4930
+ " token: ${{ steps.app-token.outputs.token || secrets.TOPOLO_CI_REPO_TOKEN }}"
4533
4931
  ];
4534
4932
  return [
4535
4933
  "name: Topolo App Conformance",
@@ -4547,44 +4945,66 @@ function topoloConformanceWorkflow(options) {
4547
4945
  "jobs:",
4548
4946
  " conformance:",
4549
4947
  " runs-on: ubuntu-latest",
4948
+ " env:",
4949
+ ` HAS_TOPOLO_PACKAGES_APP: \${{ secrets.TOPOLO_PACKAGES_APP_ID != '' && secrets.TOPOLO_PACKAGES_APP_PRIVATE_KEY != '' }}`,
4950
+ ` HAS_TOPOLO_CI_REPO_TOKEN: \${{ secrets.TOPOLO_CI_REPO_TOKEN != '' }}`,
4550
4951
  " steps:",
4551
4952
  " - name: Checkout application",
4552
4953
  " uses: actions/checkout@v4",
4553
4954
  " with:",
4554
4955
  ` path: ${appPath}`,
4956
+ "",
4957
+ " - name: Mint Topolo cross-repo token",
4958
+ " id: app-token",
4959
+ ` if: env.HAS_TOPOLO_PACKAGES_APP == 'true'`,
4960
+ " uses: actions/create-github-app-token@v1",
4961
+ " with:",
4962
+ ` app-id: \${{ secrets.TOPOLO_PACKAGES_APP_ID }}`,
4963
+ ` private-key: \${{ secrets.TOPOLO_PACKAGES_APP_PRIVATE_KEY }}`,
4964
+ " owner: Topolo-io",
4555
4965
  ...docsCheckoutStep,
4556
4966
  "",
4557
4967
  " - name: Checkout Topolo SDK",
4968
+ ` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
4558
4969
  " uses: actions/checkout@v4",
4559
4970
  " with:",
4560
4971
  " repository: Topolo-io/topolo-sdk",
4561
4972
  " path: Apps/packages/topolo-sdk",
4562
- " token: ${{ secrets.TOPOLO_CI_REPO_TOKEN || github.token }}",
4973
+ " token: ${{ steps.app-token.outputs.token || secrets.TOPOLO_CI_REPO_TOKEN }}",
4563
4974
  ...cliCheckoutStep,
4564
4975
  "",
4976
+ " - name: Skip conformance without cross-repo token",
4977
+ ` if: steps.app-token.outputs.token == '' && env.HAS_TOPOLO_CI_REPO_TOKEN != 'true'`,
4978
+ " run: |",
4979
+ ' echo "::warning::Skipping Topolo app conformance because no Topolo cross-repo token is available to this workflow context."',
4980
+ "",
4565
4981
  " - name: Setup Node.js",
4982
+ ` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
4566
4983
  " uses: actions/setup-node@v4",
4567
4984
  " with:",
4568
4985
  " node-version: 20",
4569
4986
  "",
4570
4987
  " - name: Build Topolo SDK",
4988
+ ` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
4571
4989
  " working-directory: Apps/packages/topolo-sdk",
4572
4990
  " run: |",
4573
4991
  " npm install --no-audit --no-fund",
4574
4992
  " npm run build",
4575
4993
  "",
4576
4994
  " - name: Build Topolo CLI",
4995
+ ` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
4577
4996
  " working-directory: Apps/system/TopoloCLI",
4578
4997
  " run: |",
4579
4998
  " npm install --no-audit --no-fund",
4580
4999
  " npm run build",
4581
5000
  "",
4582
5001
  " - name: Run Topolo app conformance audit",
5002
+ ` if: steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true'`,
4583
5003
  " run: |",
4584
5004
  ` node Apps/system/TopoloCLI/dist/index.js apps audit ${options.id} --local --repo-root "$GITHUB_WORKSPACE" --fail-on needs_review --json`,
4585
5005
  "",
4586
5006
  " - name: Record green CI evidence",
4587
- ` if: \${{ success() }}`,
5007
+ ` if: \${{ success() && (steps.app-token.outputs.token != '' || env.HAS_TOPOLO_CI_REPO_TOKEN == 'true') }}`,
4588
5008
  ` working-directory: ${appPath}`,
4589
5009
  " run: |",
4590
5010
  ` node "$GITHUB_WORKSPACE/Apps/system/TopoloCLI/dist/index.js" apps record-evidence ${options.id} --build --typecheck --test --commit "$GITHUB_SHA"`
@@ -4598,9 +5018,9 @@ function packageJson(application, target, options) {
4598
5018
  vite: "^5.4.0"
4599
5019
  };
4600
5020
  const scripts = {
4601
- dev: "vite --host 0.0.0.0",
4602
- build: "vite build",
4603
- preview: "vite preview --host 0.0.0.0",
5021
+ dev: target === "platform" ? "vite --config apps/web/vite.config.ts --host 0.0.0.0" : "vite --host 0.0.0.0",
5022
+ build: target === "platform" ? "vite build --config apps/web/vite.config.ts" : "vite build",
5023
+ preview: target === "platform" ? "vite preview --config apps/web/vite.config.ts --host 0.0.0.0" : "vite preview --host 0.0.0.0",
4604
5024
  typecheck: "tsc --noEmit"
4605
5025
  };
4606
5026
  if (options.browser) {
@@ -4612,27 +5032,30 @@ function packageJson(application, target, options) {
4612
5032
  if (target === "platform") {
4613
5033
  dependencies["@topolo-io/auth-client"] = topoloPackageVersion("@topolo-io/auth-client", target);
4614
5034
  dependencies["@topolo-io/design-tokens"] = topoloPackageVersion("@topolo-io/design-tokens", target);
5035
+ dependencies["@topolo-io/onboarding"] = topoloPackageVersion("@topolo-io/onboarding", target);
5036
+ dependencies["@topolo-io/tour"] = topoloPackageVersion("@topolo-io/tour", target);
4615
5037
  dependencies["@topolo-io/ui-kit"] = topoloPackageVersion("@topolo-io/ui-kit", target);
4616
5038
  } else {
4617
5039
  dependencies["@topolo/login"] = topoloPackageVersion("@topolo/login", target);
4618
5040
  }
4619
5041
  }
4620
5042
  if (options.api) {
5043
+ devDependencies["@cloudflare/workers-types"] = "^4.20260413.1";
4621
5044
  if (target === "platform") {
4622
5045
  dependencies["@topolo-io/worker-runtime"] = topoloPackageVersion("@topolo-io/worker-runtime", target);
4623
- dependencies["@topolo-io/widget-server"] = topoloPackageVersion("@topolo-io/widget-server", target);
4624
5046
  dependencies["@topolo/sdk"] = topoloPackageVersion("@topolo/sdk", target);
5047
+ dependencies.hono = "^4";
4625
5048
  scripts.deploy = `node -e 'console.error("\\nX Bare deploy is disabled. Use deploy:staging or deploy:production.\\n"); process.exit(1)'`;
4626
- scripts["deploy:staging"] = "DEPLOY_ENVIRONMENT=staging VITE_TOPOLO_AUTH_URL=https://auth.stg.topolo.us npm run build && wrangler deploy --env staging";
4627
- scripts["deploy:production"] = "DEPLOY_ENVIRONMENT=production npm run build && wrangler deploy";
5049
+ scripts["deploy:staging"] = "DEPLOY_ENVIRONMENT=staging VITE_TOPOLO_AUTH_URL=https://auth.stg.topolo.us npm run build && wrangler deploy --config apps/api/wrangler.toml --env staging && wrangler deploy --config apps/web/wrangler.toml --env staging";
5050
+ scripts["deploy:production"] = "DEPLOY_ENVIRONMENT=production VITE_TOPOLO_AUTH_URL=https://auth.topolo.app npm run build && wrangler deploy --config apps/api/wrangler.toml --env production && wrangler deploy --config apps/web/wrangler.toml --env production";
4628
5051
  } else {
4629
- dependencies["@topolo-io/widget-server"] = topoloPackageVersion("@topolo-io/widget-server", target);
5052
+ dependencies["@topolo/sdk"] = topoloPackageVersion("@topolo/sdk", target);
4630
5053
  scripts.deploy = "npm run build && wrangler deploy";
4631
5054
  }
4632
5055
  devDependencies.wrangler = "^4.40.2";
4633
5056
  }
4634
5057
  if (target === "platform" && hasScaffoldResources(options.resources)) {
4635
- scripts["check:deploy-config"] = "wrangler deploy --dry-run --outdir .wrangler/build";
5058
+ scripts["check:deploy-config"] = "wrangler deploy --config apps/api/wrangler.toml --env staging --dry-run --outdir .wrangler/api-build && wrangler deploy --config apps/web/wrangler.toml --env staging --dry-run --outdir .wrangler/web-build";
4636
5059
  }
4637
5060
  return `${json({
4638
5061
  name: application.packageName,
@@ -4649,7 +5072,7 @@ function topoloPackageVersion(packageName, target) {
4649
5072
  if (target === "developer") {
4650
5073
  if (packageName === "@topolo/login") return "^0.1.0";
4651
5074
  if (packageName === "@topolo-io/auth-client") return "^1.0.0";
4652
- if (packageName === "@topolo-io/widget-server") return "^0.1.1";
5075
+ if (packageName === "@topolo/sdk") return "^0.4.1";
4653
5076
  return "^0.1.0";
4654
5077
  }
4655
5078
  const platformPackageFolders = {
@@ -4659,8 +5082,9 @@ function topoloPackageVersion(packageName, target) {
4659
5082
  "@topolo-io/auth-middleware": "topolo-auth-middleware",
4660
5083
  "@topolo-io/worker-runtime": "topolo-worker-runtime",
4661
5084
  "@topolo-io/design-tokens": "topolo-design-tokens",
4662
- "@topolo-io/ui-kit": "topolo-ui-kit",
4663
- "@topolo-io/widget-server": "topolo-widget-server"
5085
+ "@topolo-io/onboarding": "topolo-onboarding",
5086
+ "@topolo-io/tour": "topolo-tour",
5087
+ "@topolo-io/ui-kit": "topolo-ui-kit"
4664
5088
  };
4665
5089
  const folder = platformPackageFolders[packageName];
4666
5090
  if (!folder) {
@@ -4745,7 +5169,7 @@ function readme(application, target, options) {
4745
5169
  "- `vite.config.ts` includes the standard React plugin plus React runtime dedupe so linked Topolo packages resolve one browser React runtime."
4746
5170
  );
4747
5171
  if (options.api) {
4748
- lines.push("- `@topolo-io/worker-runtime` owns Worker auth validation, `/api/auth/*` gatewaying, security headers, request ids, and BugFix reporting.");
5172
+ lines.push("- `@topolo-io/worker-runtime` owns Worker auth validation, `/api/auth/*` gatewaying, security headers, request ids, Nexus API-call usage monitoring, and BugFix reporting.");
4749
5173
  }
4750
5174
  if (hasScaffoldResources(options.resources)) {
4751
5175
  lines.push(
@@ -4789,7 +5213,8 @@ function readme(application, target, options) {
4789
5213
  return `${lines.join("\n")}
4790
5214
  `;
4791
5215
  }
4792
- function tsconfigJson(api) {
5216
+ function tsconfigJson(api, target) {
5217
+ const include = target === "platform" ? ["apps/web/src", ...api ? ["apps/api/src"] : []] : ["src"];
4793
5218
  return `${json({
4794
5219
  compilerOptions: {
4795
5220
  target: "ES2022",
@@ -4807,9 +5232,9 @@ function tsconfigJson(api) {
4807
5232
  isolatedModules: true,
4808
5233
  noEmit: true,
4809
5234
  jsx: "react-jsx",
4810
- types: ["vite/client"]
5235
+ types: api ? ["vite/client", "@cloudflare/workers-types"] : ["vite/client"]
4811
5236
  },
4812
- include: ["src"],
5237
+ include,
4813
5238
  references: []
4814
5239
  })}
4815
5240
  `;
@@ -4907,21 +5332,47 @@ function serviceIdentityTs(application) {
4907
5332
  " return cachedBrowserServiceId;",
4908
5333
  "}",
4909
5334
  "",
4910
- "async function fetchServiceIdentity(): Promise<Response> {",
4911
- " const response = await fetch('/api/service-identity', {",
4912
- " headers: { Accept: 'application/json' },",
4913
- " );",
4914
- " if (!response.ok) {",
4915
- " throw new Error(`Unable to resolve ${SCAFFOLD_SERVICE_SLUG} service identity`);",
4916
- " }",
4917
- " return response;",
5335
+ "async function fetchServiceIdentity(): Promise<Response> {",
5336
+ " const response = await fetch('/api/service-identity', {",
5337
+ " headers: { Accept: 'application/json' },",
5338
+ " );",
5339
+ " if (!response.ok) {",
5340
+ " throw new Error(`Unable to resolve ${SCAFFOLD_SERVICE_SLUG} service identity`);",
5341
+ " }",
5342
+ " return response;",
5343
+ "}",
5344
+ "",
5345
+ "function requireServiceId(value: unknown): string {",
5346
+ " const serviceId = typeof value === 'string' ? value.trim() : '';",
5347
+ " if (!serviceId) {",
5348
+ " throw new Error(`SERVICE_ID is required for ${SCAFFOLD_SERVICE_SLUG}`);",
5349
+ " }",
5350
+ " return serviceId;",
5351
+ "}",
5352
+ "",
5353
+ "export function resolveRuntimeServiceId(env: ServiceIdentityEnv): Promise<string> {",
5354
+ " return Promise.resolve(requireServiceId(env.SERVICE_ID));",
5355
+ "}",
5356
+ ""
5357
+ ].join("\n");
5358
+ }
5359
+ function apiServiceIdentityTs(application) {
5360
+ const appName = toTitle(application.name);
5361
+ const serviceSlug = application.serviceSlug ?? `topolo-${toSlug(application.id)}`;
5362
+ return [
5363
+ `export const SCAFFOLD_APP_ID = '${application.id}';`,
5364
+ `export const SCAFFOLD_APP_NAME = '${escapeSingleQuoted(appName)}';`,
5365
+ `export const SCAFFOLD_SERVICE_SLUG = '${escapeSingleQuoted(serviceSlug)}';`,
5366
+ "",
5367
+ "interface ServiceIdentityEnv {",
5368
+ " SERVICE_ID?: string;",
4918
5369
  "}",
4919
5370
  "",
4920
5371
  "function requireServiceId(value: unknown): string {",
4921
5372
  " const serviceId = typeof value === 'string' ? value.trim() : '';",
4922
5373
  " if (!serviceId) {",
4923
5374
  " throw new Error(`SERVICE_ID is required for ${SCAFFOLD_SERVICE_SLUG}`);",
4924
- " }",
5375
+ " }",
4925
5376
  " return serviceId;",
4926
5377
  "}",
4927
5378
  "",
@@ -4998,10 +5449,11 @@ function viteConfig(target) {
4998
5449
  " process.env.VITE_TOPOLO_AUTH_URL === 'https://auth.stg.topolo.us';",
4999
5450
  "",
5000
5451
  " return {",
5452
+ " root: fileURLToPath(new URL('.', import.meta.url)),",
5001
5453
  " plugins: [react(), ...(isStagingBuild ? [stagingUrlRewrite()] : [])],",
5002
5454
  " resolve: {",
5003
5455
  " alias: {",
5004
- " '@topolo-io/auth-client': fileURLToPath(new URL('./node_modules/@topolo-io/auth-client/dist/index.js', import.meta.url)),",
5456
+ " '@topolo-io/auth-client': fileURLToPath(new URL('../../node_modules/@topolo-io/auth-client/dist/index.js', import.meta.url)),",
5005
5457
  " },",
5006
5458
  " dedupe: ['react', 'react-dom', 'react/jsx-runtime', 'react/jsx-dev-runtime'],",
5007
5459
  " },",
@@ -5033,6 +5485,8 @@ function mainTsx() {
5033
5485
  "import React from 'react';",
5034
5486
  "import { createRoot } from 'react-dom/client';",
5035
5487
  "import '@topolo-io/design-tokens/tokens.css';",
5488
+ "import '@topolo-io/tour/styles.css';",
5489
+ "import '@topolo-io/onboarding/styles.css';",
5036
5490
  "import App from './App';",
5037
5491
  "import './styles.css';",
5038
5492
  "",
@@ -5135,16 +5589,19 @@ function platformAppTsx(application, options) {
5135
5589
  const baselineHeading = isPersonal ? "Shared personal baseline" : "Shared workspace baseline";
5136
5590
  const baselineDescription = isPersonal ? "This scaffold starts on the shared landing and login surfaces, completes sign-in on the shared callback path, and renders the signed personal app inside the shared Topolo shell." : "This scaffold starts on the shared landing and login surfaces, completes sign-in on the shared callback path, and renders the signed workspace inside the shared Topolo shell.";
5137
5591
  return [
5138
- "import { Component, useEffect, useMemo, useState, type ErrorInfo, type ReactNode } from 'react';",
5592
+ "import { Component, useCallback, useEffect, useMemo, useState, type ErrorInfo, type ReactNode } from 'react';",
5139
5593
  "import { createBrowserAuthSessionController, type AuthUser } from '@topolo-io/auth-client';",
5140
5594
  "import { LandingPage, LoginPage, TopoloAppShell, TopoloAuthFlowScreen, TopoloLoadingScreen, setupBugReporterErrorCapture, type TopoloAppShellUser } from '@topolo-io/ui-kit';",
5595
+ "import { GettingStarted, type OnboardingGuideStep } from '@topolo-io/onboarding';",
5596
+ "import { IntroTour } from '@topolo-io/tour';",
5141
5597
  "import { SCAFFOLD_APP_ID, SCAFFOLD_APP_NAME, SCAFFOLD_SERVICE_SLUG, getBrowserAuthBaseUrl, resolveAppServiceId } from './service-identity';",
5142
5598
  "",
5143
5599
  "// Wire global error/console capture so BugReporter (enabled in TopoloAppShell below)",
5144
5600
  "// can attach the last few errors to any submitted bug report.",
5145
5601
  "setupBugReporterErrorCapture();",
5146
5602
  "",
5147
- "type NavItem = 'home';",
5603
+ "type NavItem = 'home' | 'getting-started';",
5604
+ "type WorkspaceView = 'workspace' | 'getting-started';",
5148
5605
  "type AppRoute = { kind: 'root' } | { kind: 'login' } | { kind: 'callback' };",
5149
5606
  "type BrowserAuthModule = ReturnType<typeof createBrowserAuthSessionController>;",
5150
5607
  "",
@@ -5221,7 +5678,17 @@ function platformAppTsx(application, options) {
5221
5678
  " const [loading, setLoading] = useState(true);",
5222
5679
  " const [authError, setAuthError] = useState('');",
5223
5680
  " const [loginAttempted, setLoginAttempted] = useState(false);",
5681
+ " const [view, setView] = useState<WorkspaceView>('workspace');",
5224
5682
  " const auth = useMemo(() => serviceId ? getAuthModule(serviceId) : null, [serviceId]);",
5683
+ " const openWorkspace = useCallback(() => setView('workspace'), []);",
5684
+ " const openGettingStarted = useCallback(() => {",
5685
+ " setView('getting-started');",
5686
+ " if (typeof window !== 'undefined') window.scrollTo({ top: 0, behavior: 'smooth' });",
5687
+ " }, []);",
5688
+ " const handleNavSelect = useCallback((id: NavItem) => {",
5689
+ " if (id === 'getting-started') openGettingStarted();",
5690
+ " else openWorkspace();",
5691
+ " }, [openGettingStarted, openWorkspace]);",
5225
5692
  "",
5226
5693
  " useEffect(() => {",
5227
5694
  " let active = true;",
@@ -5244,7 +5711,9 @@ function platformAppTsx(application, options) {
5244
5711
  " if (!auth) return;",
5245
5712
  " let active = true;",
5246
5713
  " const syncAuth = async () => {",
5247
- " setLoading(true);",
5714
+ " const publicRoute = route.kind === 'root' || route.kind === 'login';",
5715
+ " if (publicRoute) setLoading(false);",
5716
+ " else setLoading(true);",
5248
5717
  " setAuthError('');",
5249
5718
  "",
5250
5719
  " if (route.kind === 'callback') {",
@@ -5260,7 +5729,9 @@ function platformAppTsx(application, options) {
5260
5729
  " return;",
5261
5730
  " }",
5262
5731
  "",
5263
- " const nextUser = await auth.authClient.initialize();",
5732
+ " const nextUser = await auth.authClient.initialize({",
5733
+ " cookieRefresh: publicRoute ? 'background' : 'blocking',",
5734
+ " });",
5264
5735
  " if (!active) return;",
5265
5736
  " setUser(nextUser);",
5266
5737
  " setLoading(false);",
@@ -5277,6 +5748,16 @@ function platformAppTsx(application, options) {
5277
5748
  " }, [auth, route.kind]);",
5278
5749
  "",
5279
5750
  " useEffect(() => {",
5751
+ " if (!auth) return;",
5752
+ " const handleSessionUpdated = () => {",
5753
+ " const nextUser = auth.authClient.getHydratedCurrentUser() || auth.authClient.getTokenIdentity();",
5754
+ " if (nextUser) setUser(nextUser);",
5755
+ " };",
5756
+ " window.addEventListener('auth:session-updated', handleSessionUpdated);",
5757
+ " return () => window.removeEventListener('auth:session-updated', handleSessionUpdated);",
5758
+ " }, [auth]);",
5759
+ "",
5760
+ " useEffect(() => {",
5280
5761
  " if (!loading && user && route.kind === 'login') {",
5281
5762
  " window.location.replace('/');",
5282
5763
  " }",
@@ -5406,11 +5887,11 @@ function platformAppTsx(application, options) {
5406
5887
  " orgId={user.activeContextType === 'organization' ? user.activeContextId ?? user.orgId ?? null : null}",
5407
5888
  " activeContextType={user.activeContextType}",
5408
5889
  " activeContextId={user.activeContextId}",
5409
- ' activeNavItemId="home"',
5410
- ` navItems={[{ id: 'home', label: '${escapeSingleQuoted(workspaceTitle)}' }]}`,
5890
+ " activeNavItemId={view === 'getting-started' ? 'getting-started' : 'home'}",
5891
+ ` navItems={[{ id: 'home', label: '${escapeSingleQuoted(workspaceTitle)}' }, { id: 'getting-started', label: 'Getting started' }]}`,
5411
5892
  ' navigationMode="topbar"',
5412
- " onNavItemSelect={() => undefined}",
5413
- ` title="${escapeDoubleQuoted(workspaceTitle)}"`,
5893
+ " onNavItemSelect={handleNavSelect}",
5894
+ ` title={view === 'getting-started' ? 'Getting started' : '${escapeSingleQuoted(workspaceTitle)}'}`,
5414
5895
  " authUrl={AUTH_URL}",
5415
5896
  " getAccessToken={getAccessToken}",
5416
5897
  " onSignOut={async () => {",
@@ -5422,18 +5903,94 @@ function platformAppTsx(application, options) {
5422
5903
  " enableCommandPalette",
5423
5904
  " enableBugReporter",
5424
5905
  " >",
5425
- ' <div className="topolo-starter-workspace">',
5426
- ' <section className="topolo-starter-card">',
5427
- ` <h2>${baselineHeading}</h2>`,
5428
- " <p>",
5429
- ` ${baselineDescription}`,
5430
- " </p>",
5431
- " </section>",
5432
- " </div>",
5906
+ " {view === 'getting-started' ? (",
5907
+ " <ScaffoldGettingStarted",
5908
+ " serviceId={activeServiceId}",
5909
+ " organizationId={user.organization?.id ?? user.orgId ?? ''}",
5910
+ " role={user.role}",
5911
+ " onOpenWorkspace={openWorkspace}",
5912
+ " />",
5913
+ " ) : (",
5914
+ ' <div className="topolo-starter-workspace">',
5915
+ ' <section className="topolo-starter-card" data-tour="workspace-card">',
5916
+ ` <h2>${baselineHeading}</h2>`,
5917
+ " <p>",
5918
+ ` ${baselineDescription}`,
5919
+ " </p>",
5920
+ " </section>",
5921
+ " </div>",
5922
+ " )}",
5923
+ " <IntroTour",
5924
+ " appName={APP_NAME}",
5925
+ " role={user.role}",
5926
+ " onOpenGettingStarted={openGettingStarted}",
5927
+ " extraSteps={[",
5928
+ " {",
5929
+ " id: 'workspace-card',",
5930
+ ` target: '[data-tour="workspace-card"]',`,
5931
+ " title: 'Your workspace',",
5932
+ " content: 'Everything you build renders here, inside the shared Topolo shell.',",
5933
+ " placement: 'bottom' as const,",
5934
+ " beforeStep: () => openWorkspace(),",
5935
+ " },",
5936
+ " ]}",
5937
+ " />",
5433
5938
  " </TopoloAppShell>",
5434
5939
  " );",
5435
5940
  "}",
5436
5941
  "",
5942
+ "function ScaffoldGettingStarted(props: {",
5943
+ " serviceId: string;",
5944
+ " organizationId: string;",
5945
+ " role?: string;",
5946
+ " onOpenWorkspace: () => void;",
5947
+ "}) {",
5948
+ " const { serviceId, organizationId, role, onOpenWorkspace } = props;",
5949
+ " const steps: OnboardingGuideStep[] = [",
5950
+ " {",
5951
+ " id: 'explore-workspace',",
5952
+ " title: 'Explore your workspace',",
5953
+ " description:",
5954
+ " 'Your app renders inside the shared Topolo shell \u2014 launcher, command palette, and account menu are already wired in.',",
5955
+ " actionLabel: 'Open workspace',",
5956
+ " onAction: onOpenWorkspace,",
5957
+ " },",
5958
+ " {",
5959
+ " id: 'discover-platform',",
5960
+ " title: 'Move between your Topolo apps',",
5961
+ " description:",
5962
+ " 'Use the launcher to switch between your Topolo apps, and the command palette (Cmd/Ctrl-K) to jump anywhere fast.',",
5963
+ " optional: true,",
5964
+ " },",
5965
+ " {",
5966
+ " id: 'build-feature',",
5967
+ " title: 'Build your first feature',",
5968
+ " description:",
5969
+ " 'Replace the starter card with your product surface, and gate write actions with service scopes before you ship.',",
5970
+ " },",
5971
+ " ];",
5972
+ " return (",
5973
+ ' <div className="topolo-starter-workspace">',
5974
+ " <GettingStarted",
5975
+ " authApiBaseUrl={`${AUTH_URL}/api`}",
5976
+ " organizationId={organizationId}",
5977
+ " serviceId={serviceId}",
5978
+ " getAccessToken={() => getAccessToken()}",
5979
+ " role={role || undefined}",
5980
+ " steps={steps}",
5981
+ " eyebrow={`Welcome to ${APP_NAME}`}",
5982
+ " title={`Get started with ${APP_NAME}`}",
5983
+ ' subtitle="A short checklist to orient you in your new Topolo workspace."',
5984
+ ' completeLabel="Mark done"',
5985
+ ' dismissLabel="Skip for now"',
5986
+ " completedContent={`You're all set \u2014 ${APP_NAME} is ready to build on.`}",
5987
+ " completeAction={{ label: 'Go to workspace', onClick: onOpenWorkspace }}",
5988
+ " onDismiss={onOpenWorkspace}",
5989
+ " />",
5990
+ " </div>",
5991
+ " );",
5992
+ "}",
5993
+ "",
5437
5994
  "function getRoute(): AppRoute {",
5438
5995
  " const pathname = window.location.pathname.replace(/\\/+$/, '') || '/';",
5439
5996
  " if (pathname === '/login') return { kind: 'login' };",
@@ -5586,6 +6143,208 @@ function developerAppTsx(application) {
5586
6143
  ""
5587
6144
  ].join("\n");
5588
6145
  }
6146
+ function webWorkerTs(application) {
6147
+ const serviceName = toTitle(application.name);
6148
+ return [
6149
+ "interface Env {",
6150
+ " API: { fetch(request: Request): Promise<Response> };",
6151
+ "}",
6152
+ "",
6153
+ `const SERVICE_NAME = '${escapeSingleQuoted(serviceName)}';`,
6154
+ "",
6155
+ "export default {",
6156
+ " async fetch(request: Request, env: Env): Promise<Response> {",
6157
+ " const url = new URL(request.url);",
6158
+ " if (url.pathname.startsWith('/api/')) {",
6159
+ " return env.API.fetch(request);",
6160
+ " }",
6161
+ " return Response.json({ ok: true, service: SERVICE_NAME }, { status: 200 });",
6162
+ " },",
6163
+ "};",
6164
+ ""
6165
+ ].join("\n");
6166
+ }
6167
+ function platformWebWranglerToml(application) {
6168
+ const slug = toSlug(application.id);
6169
+ const host = application.productionUrl ? new URL(application.productionUrl).host : `${slug}.topolo.app`;
6170
+ return [
6171
+ `name = "topolo-${slug}"`,
6172
+ 'main = "src/worker.ts"',
6173
+ `compatibility_date = "${CLOUDFLARE_COMPATIBILITY_DATE}"`,
6174
+ 'compatibility_flags = ["nodejs_compat"]',
6175
+ `account_id = "${CLOUDFLARE_ACCOUNT_ID}"`,
6176
+ "workers_dev = false",
6177
+ "",
6178
+ "[assets]",
6179
+ 'directory = "./dist"',
6180
+ 'binding = "ASSETS"',
6181
+ 'not_found_handling = "single-page-application"',
6182
+ 'run_worker_first = ["/api/*"]',
6183
+ "",
6184
+ "[observability]",
6185
+ "enabled = true",
6186
+ "",
6187
+ "[[routes]]",
6188
+ `pattern = "${host}"`,
6189
+ "custom_domain = true",
6190
+ "",
6191
+ "[vars]",
6192
+ `SERVICE_ID = "${application.serviceId ?? ""}"`,
6193
+ `VITE_TOPOLO_SERVICE_ID = "${application.serviceId ?? ""}"`,
6194
+ "",
6195
+ "[[services]]",
6196
+ 'binding = "API"',
6197
+ `service = "topolo-${slug}-api"`,
6198
+ "",
6199
+ "[env.staging]",
6200
+ `name = "topolo-${slug}-staging"`,
6201
+ `account_id = "${STAGING_CLOUDFLARE_ACCOUNT_ID}"`,
6202
+ "workers_dev = true",
6203
+ "",
6204
+ "[env.staging.vars]",
6205
+ `SERVICE_ID = "${application.serviceId ?? ""}"`,
6206
+ `VITE_TOPOLO_SERVICE_ID = "${application.serviceId ?? ""}"`,
6207
+ "",
6208
+ "[[env.staging.routes]]",
6209
+ `pattern = "${slug}.stg.topolo.us"`,
6210
+ "custom_domain = true",
6211
+ "",
6212
+ "[env.staging.assets]",
6213
+ 'directory = "./dist"',
6214
+ 'binding = "ASSETS"',
6215
+ 'not_found_handling = "single-page-application"',
6216
+ 'run_worker_first = ["/api/*"]',
6217
+ "",
6218
+ "[[env.staging.services]]",
6219
+ 'binding = "API"',
6220
+ `service = "topolo-${slug}-api-staging"`,
6221
+ "",
6222
+ "[env.production]",
6223
+ `name = "topolo-${slug}-production"`,
6224
+ `account_id = "${CLOUDFLARE_ACCOUNT_ID}"`,
6225
+ "workers_dev = false",
6226
+ "",
6227
+ "[env.production.vars]",
6228
+ `SERVICE_ID = "${application.serviceId ?? ""}"`,
6229
+ `VITE_TOPOLO_SERVICE_ID = "${application.serviceId ?? ""}"`,
6230
+ "",
6231
+ "[[env.production.routes]]",
6232
+ `pattern = "${host}"`,
6233
+ "custom_domain = true",
6234
+ "",
6235
+ "[env.production.assets]",
6236
+ 'directory = "./dist"',
6237
+ 'binding = "ASSETS"',
6238
+ 'not_found_handling = "single-page-application"',
6239
+ 'run_worker_first = ["/api/*"]',
6240
+ "",
6241
+ "[[env.production.services]]",
6242
+ 'binding = "API"',
6243
+ `service = "topolo-${slug}-api-production"`,
6244
+ ""
6245
+ ].join("\n");
6246
+ }
6247
+ function platformApiWranglerToml(application, resources, profile) {
6248
+ const slug = toSlug(application.id);
6249
+ const resource = resourceConfig(application, profile);
6250
+ const lines = [
6251
+ `name = "topolo-${slug}-api"`,
6252
+ 'main = "src/worker.ts"',
6253
+ `compatibility_date = "${CLOUDFLARE_COMPATIBILITY_DATE}"`,
6254
+ 'compatibility_flags = ["nodejs_compat"]',
6255
+ `account_id = "${CLOUDFLARE_ACCOUNT_ID}"`,
6256
+ "workers_dev = true",
6257
+ "",
6258
+ "[observability]",
6259
+ "enabled = true",
6260
+ "",
6261
+ "[vars]",
6262
+ 'AUTH_BASE_URL = "https://auth.topolo.app"',
6263
+ 'NEXUS_GATEWAY_URL = "https://nexus-api.topolo.app"',
6264
+ `NEXUS_APP_ID = "${slug}"`,
6265
+ `SERVICE_ID = "${application.serviceId ?? ""}"`,
6266
+ `VITE_TOPOLO_SERVICE_ID = "${application.serviceId ?? ""}"`,
6267
+ ""
6268
+ ];
6269
+ appendResourceWranglerBlocks(lines, resources, resource, "production");
6270
+ lines.push(
6271
+ "[env.staging]",
6272
+ `name = "topolo-${slug}-api-staging"`,
6273
+ `account_id = "${STAGING_CLOUDFLARE_ACCOUNT_ID}"`,
6274
+ "workers_dev = true",
6275
+ "",
6276
+ "[env.staging.vars]",
6277
+ 'AUTH_BASE_URL = "https://auth.stg.topolo.us"',
6278
+ 'NEXUS_GATEWAY_URL = "https://nexus-api.stg.topolo.us"',
6279
+ `NEXUS_APP_ID = "${slug}"`,
6280
+ `SERVICE_ID = "${application.serviceId ?? ""}"`,
6281
+ `VITE_TOPOLO_SERVICE_ID = "${application.serviceId ?? ""}"`,
6282
+ ""
6283
+ );
6284
+ appendResourceWranglerBlocks(lines, resources, resource, "staging");
6285
+ lines.push(
6286
+ "[env.production]",
6287
+ `name = "topolo-${slug}-api-production"`,
6288
+ `account_id = "${CLOUDFLARE_ACCOUNT_ID}"`,
6289
+ "workers_dev = false",
6290
+ "",
6291
+ "[env.production.vars]",
6292
+ 'AUTH_BASE_URL = "https://auth.topolo.app"',
6293
+ 'NEXUS_GATEWAY_URL = "https://nexus-api.topolo.app"',
6294
+ `NEXUS_APP_ID = "${slug}"`,
6295
+ `SERVICE_ID = "${application.serviceId ?? ""}"`,
6296
+ `VITE_TOPOLO_SERVICE_ID = "${application.serviceId ?? ""}"`,
6297
+ ""
6298
+ );
6299
+ appendResourceWranglerBlocks(lines, resources, resource, "production-env");
6300
+ return lines.join("\n");
6301
+ }
6302
+ function appendResourceWranglerBlocks(lines, resources, resource, environment) {
6303
+ const prefix = environment === "staging" ? "env.staging." : environment === "production-env" ? "env.production." : "";
6304
+ const envName = environment === "staging" ? "Staging" : "Production";
6305
+ if (resources.d1) {
6306
+ lines.push(
6307
+ `[[${prefix}d1_databases]]`,
6308
+ `binding = "${resource.d1Binding}"`,
6309
+ `database_name = "${environment === "staging" ? resource.d1StagingName : resource.d1ProductionName}"`,
6310
+ 'database_id = "provision-after-scaffold"',
6311
+ 'migrations_dir = "migrations"',
6312
+ ""
6313
+ );
6314
+ }
6315
+ if (resources.r2) {
6316
+ lines.push(
6317
+ `[[${prefix}r2_buckets]]`,
6318
+ `binding = "${resource.r2Binding}"`,
6319
+ `bucket_name = "${environment === "staging" ? resource.r2StagingName : resource.r2ProductionName}"`,
6320
+ ""
6321
+ );
6322
+ }
6323
+ if (resources.queue) {
6324
+ lines.push(
6325
+ `[[${prefix}queues.producers]]`,
6326
+ `binding = "${resource.queueBinding}"`,
6327
+ `queue = "${environment === "staging" ? resource.queueStagingName : resource.queueProductionName}"`,
6328
+ "",
6329
+ `[[${prefix}queues.consumers]]`,
6330
+ `queue = "${environment === "staging" ? resource.queueStagingName : resource.queueProductionName}"`,
6331
+ ""
6332
+ );
6333
+ }
6334
+ if (resources.workflow) {
6335
+ lines.push(
6336
+ `[[${prefix}workflows]]`,
6337
+ `binding = "${resource.workflowBinding}"`,
6338
+ `name = "${environment === "staging" ? resource.workflowStagingName : resource.workflowProductionName}"`,
6339
+ `class_name = "${resource.workflowClassName}"`,
6340
+ ""
6341
+ );
6342
+ }
6343
+ if (resources.containerProcessing) {
6344
+ lines.push(`# ${envName} container metadata is declared in topolo.app.json and topolo.cloudcontrol.json.`);
6345
+ lines.push("");
6346
+ }
6347
+ }
5589
6348
  function wranglerToml(application, target, resources, profile) {
5590
6349
  const slug = toSlug(application.id);
5591
6350
  const workerName = target === "platform" ? `topolo-${slug}` : application.packageName;
@@ -5627,6 +6386,7 @@ function wranglerToml(application, target, resources, profile) {
5627
6386
  "",
5628
6387
  "[vars]",
5629
6388
  'AUTH_BASE_URL = "https://auth.topolo.app"',
6389
+ 'NEXUS_GATEWAY_URL = "https://nexus-api.topolo.app"',
5630
6390
  runtimeIdentityVar,
5631
6391
  ""
5632
6392
  );
@@ -5681,6 +6441,7 @@ function wranglerToml(application, target, resources, profile) {
5681
6441
  "",
5682
6442
  "[env.staging.vars]",
5683
6443
  'AUTH_BASE_URL = "https://auth.stg.topolo.us"',
6444
+ 'NEXUS_GATEWAY_URL = "https://nexus-api.stg.topolo.us"',
5684
6445
  runtimeIdentityVar,
5685
6446
  ""
5686
6447
  );
@@ -5735,8 +6496,8 @@ function platformWorkerTs(application, options) {
5735
6496
  const resource = resourceConfig(application, options.profile);
5736
6497
  const envBindings = [
5737
6498
  " AUTH_BASE_URL?: string;",
6499
+ " NEXUS_GATEWAY_URL?: string;",
5738
6500
  " SERVICE_ID?: string;",
5739
- " TOPOLO_SERVICE_API_KEY?: string;",
5740
6501
  ...options.resources.d1 ? [` ${resource.d1Binding}?: unknown;`] : [],
5741
6502
  ...options.resources.r2 ? [` ${resource.r2Binding}?: unknown;`] : [],
5742
6503
  ...options.resources.queue ? [` ${resource.queueBinding}?: { send(message: unknown): Promise<void> };`] : [],
@@ -5769,8 +6530,9 @@ function platformWorkerTs(application, options) {
5769
6530
  " }, { status: 200 });"
5770
6531
  ];
5771
6532
  return [
5772
- "import { createWidgetHandler } from '@topolo-io/widget-server';",
5773
- "import { createTopoloWorkerRuntime } from '@topolo-io/worker-runtime';",
6533
+ "import { Hono } from 'hono';",
6534
+ "import { createTopoloWidgetResponse, type TopoloWidgetDraft } from '@topolo/sdk';",
6535
+ "import { createTopoloWorkerRuntime, type TopoloRuntimeContext } from '@topolo-io/worker-runtime';",
5774
6536
  "import { SCAFFOLD_APP_ID, SCAFFOLD_APP_NAME, SCAFFOLD_SERVICE_SLUG, resolveRuntimeServiceId } from './service-identity';",
5775
6537
  ...options.resources.workflow ? ["import { WorkflowEntrypoint, type WorkflowEvent, type WorkflowStep } from 'cloudflare:workers';"] : [],
5776
6538
  "",
@@ -5778,18 +6540,8 @@ function platformWorkerTs(application, options) {
5778
6540
  ...envBindings,
5779
6541
  "}",
5780
6542
  "",
5781
- "// Required by the platform `native-dashboard-widget` requirement \u2014",
5782
- "// TopoloOne renders this in the user's live workspace whenever the app",
5783
- "// is installed. Replace the quick-actions placeholder with org-scoped",
5784
- "// stats once your data model is ready: ctx.user / ctx.orgId / ctx.orgSlug",
5785
- "// / ctx.isPlatformAdmin / ctx.contextType / ctx.householdId are all",
5786
- "// pre-resolved. Builder exceptions degrade to empty widgets, not 500s.",
5787
- "const widgetHandler = createWidgetHandler<Env>({",
5788
- " serviceId: SCAFFOLD_SERVICE_SLUG,",
5789
- ` serviceName: '${escapeSingleQuoted(serviceName)}',`,
5790
- " authBaseUrl: (env) => env.AUTH_BASE_URL,",
5791
- " serviceApiKey: (env) => env.TOPOLO_SERVICE_API_KEY,",
5792
- " build: () => [",
6543
+ "function buildWidgets(): TopoloWidgetDraft[] {",
6544
+ " return [",
5793
6545
  " {",
5794
6546
  " type: 'quick-actions',",
5795
6547
  " actions: [",
@@ -5797,18 +6549,25 @@ function platformWorkerTs(application, options) {
5797
6549
  " { id: 'view-health', label: 'View health', link: '/api/health' },",
5798
6550
  " ],",
5799
6551
  " },",
5800
- " ],",
5801
- "});",
6552
+ " ];",
6553
+ "}",
5802
6554
  "",
5803
6555
  "const runtime = createTopoloWorkerRuntime<Env>({",
5804
6556
  " appName: SCAFFOLD_APP_NAME,",
5805
6557
  " serviceSlug: SCAFFOLD_SERVICE_SLUG,",
5806
6558
  " resolveServiceId: (env) => resolveRuntimeServiceId(env),",
5807
6559
  " authBaseUrl: (env) => env.AUTH_BASE_URL,",
5808
- " serviceApiKey: (env) => env.TOPOLO_SERVICE_API_KEY,",
6560
+ " usageMonitoring: {",
6561
+ " appId: SCAFFOLD_APP_ID,",
6562
+ " nexusGatewayUrl: (env) => env.NEXUS_GATEWAY_URL,",
6563
+ " },",
5809
6564
  "});",
5810
6565
  "",
5811
- "const handleFetch = runtime.worker(async (request, env, _ctx, rt): Promise<Response> => {",
6566
+ "async function handleRequest(",
6567
+ " _request: Request,",
6568
+ " env: Env,",
6569
+ " rt: TopoloRuntimeContext<Env>,",
6570
+ "): Promise<Response> {",
5812
6571
  " const url = rt.url;",
5813
6572
  "",
5814
6573
  " let serviceId: string;",
@@ -5831,9 +6590,15 @@ function platformWorkerTs(application, options) {
5831
6590
  " }, { status: 200 });",
5832
6591
  " }",
5833
6592
  "",
5834
- " // /api/widget delegates fully to @topolo-io/widget-server \u2014 it owns",
5835
- " // auth, context parsing, response wrapping, and error degradation.",
5836
- " if (url.pathname === '/api/widget') return widgetHandler(request, env);",
6593
+ " if (url.pathname === '/api/widget') {",
6594
+ " const auth = await rt.verifyAuth({ serviceId });",
6595
+ " if (!auth.ok) return rt.authError(auth);",
6596
+ " return rt.json(createTopoloWidgetResponse({",
6597
+ " serviceId,",
6598
+ " serviceName: SCAFFOLD_APP_NAME,",
6599
+ " widgets: buildWidgets(),",
6600
+ " }), { status: 200, headers: { 'Cache-Control': 'public, max-age=15' } });",
6601
+ " }",
5837
6602
  "",
5838
6603
  " const auth = await rt.verifyAuth({ serviceId });",
5839
6604
  "",
@@ -5846,10 +6611,21 @@ function platformWorkerTs(application, options) {
5846
6611
  " }",
5847
6612
  "",
5848
6613
  " return rt.notFound();",
6614
+ "}",
6615
+ "",
6616
+ "const api = new Hono<{ Bindings: Env & { __rt?: TopoloRuntimeContext<Env> } }>();",
6617
+ "api.all('*', (c) => {",
6618
+ " const rt = c.env.__rt;",
6619
+ " if (!rt) return Response.json({ error: 'runtime_context_unavailable' }, { status: 500 });",
6620
+ " return handleRequest(c.req.raw, c.env, rt);",
5849
6621
  "});",
5850
6622
  "",
5851
6623
  "export default {",
5852
- " fetch: handleFetch,",
6624
+ " fetch: runtime.worker((request, env, ctx, rt) => {",
6625
+ " const bindings = env as Env & { __rt?: TopoloRuntimeContext<Env> };",
6626
+ " bindings.__rt = rt;",
6627
+ " return api.fetch(request, bindings, ctx as ExecutionContext);",
6628
+ " }),",
5853
6629
  "};",
5854
6630
  "",
5855
6631
  "function errorMessage(error: unknown): string {",
@@ -5864,7 +6640,7 @@ function developerWorkerTs(application) {
5864
6640
  const serviceId = application.serviceId ?? application.id;
5865
6641
  const serviceName = application.name || "Topolo App";
5866
6642
  return [
5867
- "import { createWidgetHandler } from '@topolo-io/widget-server';",
6643
+ "import { createTopoloWidgetResponse, type TopoloWidgetDraft } from '@topolo/sdk';",
5868
6644
  "",
5869
6645
  "export interface Env {",
5870
6646
  " AUTH_BASE_URL?: string;",
@@ -5875,17 +6651,8 @@ function developerWorkerTs(application) {
5875
6651
  `const DEFAULT_SERVICE_ID = '${serviceId}';`,
5876
6652
  `const SERVICE_NAME = '${escapeSingleQuoted(serviceName)}';`,
5877
6653
  "",
5878
- "// Required by the platform `native-dashboard-widget` requirement \u2014",
5879
- "// TopoloOne renders this in the user's live workspace whenever the app",
5880
- "// is installed. Replace the quick-actions placeholder with org-scoped",
5881
- "// stats once your data model is ready: ctx.user / ctx.orgId / ctx.orgSlug",
5882
- "// / ctx.isPlatformAdmin / ctx.contextType / ctx.householdId are all",
5883
- "// pre-resolved. Builder exceptions degrade to empty widgets, not 500s.",
5884
- "const widgetHandler = createWidgetHandler<Env>({",
5885
- " serviceId: DEFAULT_SERVICE_ID,",
5886
- " serviceName: SERVICE_NAME,",
5887
- " authBaseUrl: (env) => env.AUTH_BASE_URL,",
5888
- " build: () => [",
6654
+ "function buildWidgets(): TopoloWidgetDraft[] {",
6655
+ " return [",
5889
6656
  " {",
5890
6657
  " type: 'quick-actions',",
5891
6658
  " actions: [",
@@ -5893,8 +6660,8 @@ function developerWorkerTs(application) {
5893
6660
  " { id: 'view-health', label: 'View health', link: '/api/health' },",
5894
6661
  " ],",
5895
6662
  " },",
5896
- " ],",
5897
- "});",
6663
+ " ];",
6664
+ "}",
5898
6665
  "",
5899
6666
  "export default {",
5900
6667
  " async fetch(request: Request, env: Env): Promise<Response> {",
@@ -5904,8 +6671,15 @@ function developerWorkerTs(application) {
5904
6671
  " return json({ ok: true, service: env.SERVICE_ID ?? DEFAULT_SERVICE_ID }, { status: 200 }, request);",
5905
6672
  " }",
5906
6673
  "",
5907
- " // /api/widget delegates fully to @topolo-io/widget-server.",
5908
- " if (url.pathname === '/api/widget') return widgetHandler(request, env);",
6674
+ " if (url.pathname === '/api/widget') {",
6675
+ " const auth = await verifyTopoloRequest(request, env);",
6676
+ " if (!auth.ok) return json({ error: auth.error }, { status: auth.status }, request);",
6677
+ " return json(createTopoloWidgetResponse({",
6678
+ " serviceId: env.SERVICE_ID ?? DEFAULT_SERVICE_ID,",
6679
+ " serviceName: SERVICE_NAME,",
6680
+ " widgets: buildWidgets(),",
6681
+ " }), { status: 200, headers: { 'Cache-Control': 'public, max-age=15' } }, request);",
6682
+ " }",
5909
6683
  "",
5910
6684
  " const auth = await verifyTopoloRequest(request, env);",
5911
6685
  " if (!auth.ok) {",
@@ -5961,6 +6735,126 @@ function developerWorkerTs(application) {
5961
6735
  ""
5962
6736
  ].join("\n");
5963
6737
  }
6738
+ function topoloAppManifestJson(application, options) {
6739
+ const slug = toSlug(application.id);
6740
+ const resource = resourceConfig(application, options.profile);
6741
+ const targets = {};
6742
+ if (options.api) {
6743
+ const bindings = {};
6744
+ if (options.resources.d1) {
6745
+ bindings.d1 = [
6746
+ {
6747
+ binding: resource.d1Binding,
6748
+ staging: {
6749
+ databaseName: resource.d1StagingName,
6750
+ databaseId: "provision-after-scaffold"
6751
+ },
6752
+ production: {
6753
+ databaseName: resource.d1ProductionName,
6754
+ databaseId: "provision-after-scaffold"
6755
+ },
6756
+ migrationsDir: "migrations"
6757
+ }
6758
+ ];
6759
+ }
6760
+ if (options.resources.r2) {
6761
+ bindings.r2 = [
6762
+ {
6763
+ binding: resource.r2Binding,
6764
+ staging: { bucketName: resource.r2StagingName },
6765
+ production: { bucketName: resource.r2ProductionName }
6766
+ }
6767
+ ];
6768
+ }
6769
+ if (options.resources.queue) {
6770
+ bindings.queues = [
6771
+ {
6772
+ binding: resource.queueBinding,
6773
+ staging: { queueName: resource.queueStagingName },
6774
+ production: { queueName: resource.queueProductionName },
6775
+ consumer: true
6776
+ }
6777
+ ];
6778
+ }
6779
+ if (options.resources.workflow) {
6780
+ bindings.workflows = [
6781
+ {
6782
+ binding: resource.workflowBinding,
6783
+ className: resource.workflowClassName,
6784
+ staging: { name: resource.workflowStagingName },
6785
+ production: { name: resource.workflowProductionName }
6786
+ }
6787
+ ];
6788
+ }
6789
+ targets.api = {
6790
+ kind: "worker",
6791
+ dir: "apps/api",
6792
+ wranglerName: `topolo-${slug}-api`,
6793
+ main: "src/worker.ts",
6794
+ compatibilityFlags: ["nodejs_compat"],
6795
+ healthcheckPath: "/api/health",
6796
+ vars: {
6797
+ AUTH_BASE_URL: {
6798
+ staging: "https://auth.stg.topolo.us",
6799
+ production: "https://auth.topolo.app"
6800
+ }
6801
+ },
6802
+ ...Object.keys(bindings).length ? { bindings } : {}
6803
+ };
6804
+ }
6805
+ targets.web = {
6806
+ kind: "worker",
6807
+ dir: "apps/web",
6808
+ wranglerName: `topolo-${slug}`,
6809
+ subdomain: slug,
6810
+ main: "src/worker.ts",
6811
+ workersDev: false,
6812
+ assets: {
6813
+ directory: "./dist",
6814
+ binding: "ASSETS",
6815
+ notFoundHandling: "single-page-application",
6816
+ runWorkerFirst: ["/api/*"]
6817
+ },
6818
+ compatibilityFlags: ["nodejs_compat"],
6819
+ buildCommand: {
6820
+ staging: "cd ../.. && DEPLOY_ENVIRONMENT=staging VITE_TOPOLO_AUTH_URL=https://auth.stg.topolo.us npm run build",
6821
+ production: "cd ../.. && DEPLOY_ENVIRONMENT=production VITE_TOPOLO_AUTH_URL=https://auth.topolo.app npm run build"
6822
+ },
6823
+ healthcheckPath: options.api ? "/api/health" : "/",
6824
+ ...options.api ? {
6825
+ bindings: {
6826
+ services: [
6827
+ {
6828
+ binding: "API",
6829
+ service: {
6830
+ staging: `topolo-${slug}-api-staging`,
6831
+ production: `topolo-${slug}-api-production`
6832
+ }
6833
+ }
6834
+ ]
6835
+ }
6836
+ } : {}
6837
+ };
6838
+ return `${json({
6839
+ $schema: "./../../topolo-platform/tooling/app-manifest-schema.json",
6840
+ name: `topolo-${slug}`,
6841
+ serviceId: application.serviceId,
6842
+ primaryTarget: "web",
6843
+ compatibilityDate: CLOUDFLARE_COMPATIBILITY_DATE,
6844
+ environments: {
6845
+ staging: {
6846
+ accountId: STAGING_CLOUDFLARE_ACCOUNT_ID,
6847
+ domain: "stg.topolo.us"
6848
+ },
6849
+ production: {
6850
+ accountId: CLOUDFLARE_ACCOUNT_ID,
6851
+ domain: "topolo.app"
6852
+ }
6853
+ },
6854
+ targets
6855
+ })}
6856
+ `;
6857
+ }
5964
6858
  function cloudcontrolJson(application, options) {
5965
6859
  const slug = toSlug(application.id);
5966
6860
  const appPath = firstPartyAppPath(application.appDir, options.audience);
@@ -6069,16 +6963,16 @@ function cloudcontrolJson(application, options) {
6069
6963
  {
6070
6964
  kind: "worker",
6071
6965
  environment: "production",
6072
- target_name: `topolo-${slug}`,
6966
+ target_name: `topolo-${slug}-api-production`,
6073
6967
  cloudflare_account_id: CLOUDFLARE_ACCOUNT_ID,
6074
- config_path: "wrangler.toml",
6075
- build_command: "npm run build",
6076
- output_dir: "dist",
6968
+ config_path: "apps/api/wrangler.toml",
6969
+ workdir: "apps/api",
6970
+ build_command: null,
6971
+ output_dir: null,
6077
6972
  production_branch: "main",
6078
6973
  healthcheck_path: "/api/health",
6079
- domains: [host],
6080
- routes: [host],
6081
- api_url: application.productionUrl,
6974
+ domains: [],
6975
+ routes: [],
6082
6976
  service_slug: serviceSlug
6083
6977
  },
6084
6978
  {
@@ -6086,36 +6980,73 @@ function cloudcontrolJson(application, options) {
6086
6980
  environment: "staging",
6087
6981
  target_name: `topolo-${slug}-staging`,
6088
6982
  cloudflare_account_id: STAGING_CLOUDFLARE_ACCOUNT_ID,
6089
- config_path: "wrangler.toml",
6090
- build_command: "VITE_TOPOLO_AUTH_URL=https://auth.stg.topolo.us npm run build",
6091
- output_dir: "dist",
6092
- production_branch: "main",
6983
+ config_path: "apps/web/wrangler.toml",
6984
+ workdir: "apps/web",
6985
+ build_command: "DEPLOY_ENVIRONMENT=staging VITE_TOPOLO_AUTH_URL=https://auth.stg.topolo.us npm run build",
6986
+ output_dir: null,
6987
+ production_branch: "staging",
6093
6988
  healthcheck_path: "/api/health",
6094
6989
  domains: [stagingHost],
6095
6990
  routes: [stagingHost],
6096
6991
  api_url: `https://${stagingHost}`,
6992
+ service_slug: serviceSlug,
6993
+ sdk_service_id: application.id,
6994
+ agent_callable: true
6995
+ },
6996
+ {
6997
+ kind: "worker",
6998
+ environment: "staging",
6999
+ target_name: `topolo-${slug}-api-staging`,
7000
+ cloudflare_account_id: STAGING_CLOUDFLARE_ACCOUNT_ID,
7001
+ config_path: "apps/api/wrangler.toml",
7002
+ workdir: "apps/api",
7003
+ build_command: null,
7004
+ output_dir: null,
7005
+ production_branch: "staging",
7006
+ healthcheck_path: "/api/health",
7007
+ domains: [],
7008
+ routes: [],
6097
7009
  service_slug: serviceSlug
7010
+ },
7011
+ {
7012
+ kind: "worker",
7013
+ environment: "production",
7014
+ target_name: `topolo-${slug}-production`,
7015
+ cloudflare_account_id: CLOUDFLARE_ACCOUNT_ID,
7016
+ config_path: "apps/web/wrangler.toml",
7017
+ workdir: "apps/web",
7018
+ build_command: "DEPLOY_ENVIRONMENT=production VITE_TOPOLO_AUTH_URL=https://auth.topolo.app npm run build",
7019
+ output_dir: null,
7020
+ production_branch: "main",
7021
+ healthcheck_path: "/api/health",
7022
+ domains: [host],
7023
+ routes: [host],
7024
+ api_url: application.productionUrl,
7025
+ service_slug: serviceSlug,
7026
+ sdk_service_id: application.id,
7027
+ agent_callable: true
6098
7028
  }
6099
7029
  ] : [
6100
7030
  {
6101
- kind: "pages",
7031
+ kind: "worker",
6102
7032
  environment: "production",
6103
7033
  target_name: `topolo-${slug}`,
6104
7034
  cloudflare_account_id: CLOUDFLARE_ACCOUNT_ID,
6105
- config_path: null,
7035
+ config_path: "wrangler.toml",
6106
7036
  build_command: "npm run build",
6107
7037
  output_dir: "dist",
6108
7038
  production_branch: "main",
6109
- healthcheck_path: null,
7039
+ healthcheck_path: "/",
6110
7040
  domains: [host],
6111
7041
  routes: [host],
6112
- api_url: null
7042
+ api_url: application.productionUrl,
7043
+ agent_callable: false
6113
7044
  }
6114
7045
  ];
6115
7046
  return `${json({
6116
7047
  name: `topolo-${slug}`,
6117
7048
  description: `Canonical documentation for ${toTitle(application.name)} lives in Apps/system/TopoloDocs.`,
6118
- deploy_method: options.api ? "wrangler" : "cloudflare_pages",
7049
+ deploy_method: "wrangler",
6119
7050
  ci_provider: "github_actions",
6120
7051
  repo_identity: {
6121
7052
  provider: "github",
@@ -6195,7 +7126,7 @@ function systemJson(application, options) {
6195
7126
  visibility: "internal",
6196
7127
  api_contract: {
6197
7128
  type: options.api ? "curated" : "none",
6198
- source: options.api ? `${appPath}/src/worker.ts` : `${appPath}/src/App.tsx`,
7129
+ source: options.api ? `${appPath}/apps/api/src/worker.ts` : `${appPath}/apps/web/src/App.tsx`,
6199
7130
  notes: apiNotes
6200
7131
  },
6201
7132
  primary_hosts: application.productionUrl ? [application.productionUrl] : [],
@@ -6284,17 +7215,19 @@ function internalDocs(application, options) {
6284
7215
  "",
6285
7216
  "## Architecture",
6286
7217
  "",
6287
- "The generated browser surface uses `@topolo-io/ui-kit` for the shared landing page, shared login page, auth callback state, and shared signed shell. `vite.config.ts` includes the standard React plugin plus React runtime dedupe so linked Topolo packages resolve a single browser React runtime.",
6288
- options.api ? `The generated app centralizes service identity in \`${appPath}/src/service-identity.ts\`. Runtime code reads the provisioned Auth service id from the Worker \`SERVICE_ID\` binding and exposes it to the browser through the same-origin service-identity route.` : `The generated app centralizes browser service identity in \`${appPath}/src/service-identity.ts\` and reads Auth identity from the app runtime instead of calling Auth for slug lookups.`,
7218
+ "The generated browser surface in `apps/web` uses `@topolo-io/ui-kit` for the shared landing page, shared login page, auth callback state, and shared signed shell. `apps/web/vite.config.ts` includes the standard React plugin plus React runtime dedupe so linked Topolo packages resolve a single browser React runtime.",
7219
+ options.api ? `The generated app keeps browser service identity in \`${appPath}/apps/web/src/service-identity.ts\` and API runtime service identity in \`${appPath}/apps/api/src/service-identity.ts\`. Runtime code reads the provisioned Auth service id from the API Worker \`SERVICE_ID\` binding and exposes it to the browser through the same-origin service-identity route forwarded by the web Worker.` : `The generated app centralizes browser service identity in \`${appPath}/apps/web/src/service-identity.ts\` and reads Auth identity from the app runtime instead of calling Auth for slug lookups.`,
6289
7220
  "",
6290
7221
  "## Runtime Surfaces",
6291
7222
  "",
6292
- `- \`${appPath}/src/App.tsx\``,
6293
- `- \`${appPath}/src/service-identity.ts\``,
7223
+ `- \`${appPath}/apps/web/src/App.tsx\``,
7224
+ `- \`${appPath}/apps/web/src/service-identity.ts\``,
7225
+ `- \`${appPath}/apps/web/wrangler.toml\``,
6294
7226
  "- public landing route: `/`",
6295
7227
  "- public sign-in route: `/login`",
6296
7228
  "- auth callback route: `/auth/callback`",
6297
- ...options.api ? [`- \`${appPath}/src/worker.ts\``] : [],
7229
+ ...options.api ? [`- \`${appPath}/apps/api/src/worker.ts\``, `- \`${appPath}/apps/api/wrangler.toml\``] : [],
7230
+ `- \`${appPath}/topolo.app.json\``,
6298
7231
  `- \`${appPath}/topolo.cloudcontrol.json\``,
6299
7232
  ...options.api && application.serviceId ? [`- \`Apps/system/TopoloAuth/catalog/service-manifests/${application.serviceId}.json\``] : [],
6300
7233
  "",
@@ -6453,6 +7386,12 @@ function serviceManifestJson(application, options) {
6453
7386
  name: "workspace:write",
6454
7387
  description: `Manage ${title} ${resourceLabel} data`,
6455
7388
  resourcePattern: null
7389
+ },
7390
+ {
7391
+ id: `perm_${permissionPrefix}_usage_write`,
7392
+ name: "usage:write",
7393
+ description: `Record Nexus usage events for ${title}`,
7394
+ resourcePattern: null
6456
7395
  }
6457
7396
  ],
6458
7397
  apiKeyScopes: [
@@ -6467,6 +7406,12 @@ function serviceManifestJson(application, options) {
6467
7406
  name: "workspace.write",
6468
7407
  description: `Manage ${title} ${resourceLabel} data`,
6469
7408
  resourcePattern: null
7409
+ },
7410
+ {
7411
+ id: `aks_${permissionPrefix}_usage_write`,
7412
+ name: "usage.write",
7413
+ description: `Record Nexus usage events for ${title}`,
7414
+ resourcePattern: null
6470
7415
  }
6471
7416
  ],
6472
7417
  roleBundles: {
@@ -6881,9 +7826,41 @@ function readRequiredString(value, message) {
6881
7826
  }
6882
7827
  return resolved;
6883
7828
  }
7829
+ async function applyProvisionedServiceId(manifestPath, serviceId) {
7830
+ let raw;
7831
+ try {
7832
+ raw = await readFile2(manifestPath, "utf8");
7833
+ } catch {
7834
+ return { changed: false, previous: null };
7835
+ }
7836
+ let json2;
7837
+ try {
7838
+ json2 = JSON.parse(raw);
7839
+ } catch {
7840
+ return { changed: false, previous: null };
7841
+ }
7842
+ const previous = typeof json2.serviceId === "string" ? json2.serviceId : null;
7843
+ if (previous === serviceId) {
7844
+ return { changed: false, previous };
7845
+ }
7846
+ json2.serviceId = serviceId;
7847
+ await writeFile3(manifestPath, `${JSON.stringify(json2, null, 2)}
7848
+ `, "utf8");
7849
+ return { changed: true, previous };
7850
+ }
6884
7851
 
6885
7852
  // src/commands/apps.ts
7853
+ import { join as join3 } from "path";
6886
7854
  var APPLICATION_IDS = Object.keys(APPLICATIONS).sort();
7855
+ var CLOUDCONTROL_LOCAL_APPLICATION = {
7856
+ id: "cloudcontrol",
7857
+ appDir: "TopoloCloudControl",
7858
+ name: "topolo-cloudcontrol",
7859
+ packageName: "topolo-cloudcontrol",
7860
+ productionUrl: null,
7861
+ services: [],
7862
+ deployTargets: []
7863
+ };
6887
7864
  function registerAppsCommand(program2) {
6888
7865
  const apps = program2.command("apps").description("List every Topolo platform application known to the SDK catalog").option("--json", "Output JSON", true).option("--no-json", "Output human-readable table").action((opts) => {
6889
7866
  const mode = resolveOutputMode(opts.json);
@@ -6894,7 +7871,7 @@ function registerAppsCommand(program2) {
6894
7871
  fail(err, mode);
6895
7872
  }
6896
7873
  });
6897
- apps.command("get <application>").description(`Show one Topolo application. Known applications: ${APPLICATION_IDS.join(", ")}.`).option("--json", "Output JSON", true).option("--no-json", "Output human-readable details").action((application, opts) => {
7874
+ apps.command("get <application>").description("Show one Topolo application. Run `topolo apps --json` to discover application IDs.").option("--json", "Output JSON", true).option("--no-json", "Output human-readable details").action((application, opts) => {
6898
7875
  const mode = resolveOutputMode(opts.json);
6899
7876
  try {
6900
7877
  const app = resolveApplication(application);
@@ -6981,15 +7958,25 @@ function registerAppsCommand(program2) {
6981
7958
  });
6982
7959
  const writeResult = opts.write ? await writeApplicationScaffold(payload, { repoRoot: opts.repoRoot, force: opts.force }) : null;
6983
7960
  const provisionResult = opts.provision ? await provisionApplicationScaffold(payload) : null;
7961
+ let serviceIdWriteBack = null;
7962
+ if (writeResult && provisionResult?.authService?.id) {
7963
+ const manifestFile = writeResult.files.find((f) => f.path.endsWith("topolo.app.json"));
7964
+ if (manifestFile) {
7965
+ const manifestPath = join3(writeResult.root, manifestFile.path);
7966
+ const result = await applyProvisionedServiceId(manifestPath, provisionResult.authService.id);
7967
+ serviceIdWriteBack = { ...result, path: manifestFile.path };
7968
+ }
7969
+ }
6984
7970
  const output = {
6985
7971
  ...payload,
6986
7972
  ...writeResult ? { write: writeResult } : {},
6987
- ...provisionResult ? { provision: provisionResult } : {}
7973
+ ...provisionResult ? { provision: provisionResult } : {},
7974
+ ...serviceIdWriteBack ? { serviceIdWriteBack } : {}
6988
7975
  };
6989
7976
  emit(
6990
7977
  output,
6991
7978
  mode,
6992
- () => formatScaffold(payload, writeResult, provisionResult)
7979
+ () => formatScaffold(payload, writeResult, provisionResult, serviceIdWriteBack)
6993
7980
  );
6994
7981
  } catch (err) {
6995
7982
  fail(err, mode);
@@ -7013,7 +8000,7 @@ function applicationRows() {
7013
8000
  function resolveApplication(application) {
7014
8001
  const id = application.trim().toLowerCase();
7015
8002
  if (!APPLICATION_IDS.includes(id)) {
7016
- throw new Error(`Unknown application "${application}". Known applications: ${APPLICATION_IDS.join(", ")}`);
8003
+ throw new Error(`Unknown application "${application}". ${formatKnownValues(APPLICATION_IDS, "applications")}`);
7017
8004
  }
7018
8005
  return APPLICATIONS[id];
7019
8006
  }
@@ -7035,7 +8022,12 @@ function applicationRequirementsPayload(application) {
7035
8022
  };
7036
8023
  }
7037
8024
  function applicationAuditPayload(application, options = {}) {
7038
- const report = application ? auditAllApplicationRequirements([resolveApplication(application).id]) : auditAllApplicationRequirements();
8025
+ const requestedId = application?.trim().toLowerCase();
8026
+ const report = requestedId === "cloudcontrol" && options.local ? {
8027
+ version: APPLICATION_REQUIREMENTS_VERSION,
8028
+ applications: [auditApplicationRequirements(CLOUDCONTROL_LOCAL_APPLICATION)],
8029
+ migrationQueue: []
8030
+ } : application ? auditAllApplicationRequirements([resolveApplication(application).id]) : auditAllApplicationRequirements();
7039
8031
  if (!options.local) return report;
7040
8032
  return enrichApplicationAuditWithLocalEvidence(report, { repoRoot: options.repoRoot });
7041
8033
  }
@@ -7159,6 +8151,7 @@ function formatRows(rows) {
7159
8151
  return lines.join("\n");
7160
8152
  }
7161
8153
  function formatApplication(application) {
8154
+ const deployTargets = application.deployTargets;
7162
8155
  const lines = [
7163
8156
  `ID: ${application.id}`,
7164
8157
  `Name: ${application.name}`,
@@ -7168,10 +8161,10 @@ function formatApplication(application) {
7168
8161
  `Services: ${application.services.length ? application.services.join(", ") : "-"}`,
7169
8162
  "Targets:"
7170
8163
  ];
7171
- if (application.deployTargets.length === 0) {
8164
+ if (deployTargets.length === 0) {
7172
8165
  lines.push(" - none");
7173
8166
  } else {
7174
- for (const target of application.deployTargets) {
8167
+ for (const target of deployTargets) {
7175
8168
  const parts = [`${target.name} (${target.kind})`];
7176
8169
  if (target.serviceId) parts.push(`service=${target.serviceId}`);
7177
8170
  if (target.apiUrl) parts.push(`api=${target.apiUrl}`);
@@ -7223,7 +8216,7 @@ function formatAudit(payload) {
7223
8216
  }
7224
8217
  return lines.join("\n");
7225
8218
  }
7226
- function formatScaffold(payload, writeResult = null, provisionResult = null) {
8219
+ function formatScaffold(payload, writeResult = null, provisionResult = null, serviceIdWriteBack = null) {
7227
8220
  const lines = [
7228
8221
  `Version: ${payload.version}`,
7229
8222
  `Target: ${payload.target}`,
@@ -7283,6 +8276,11 @@ function formatScaffold(payload, writeResult = null, provisionResult = null) {
7283
8276
  );
7284
8277
  }
7285
8278
  }
8279
+ if (serviceIdWriteBack) {
8280
+ lines.push(
8281
+ serviceIdWriteBack.changed ? ` - service id written back to ${serviceIdWriteBack.path} (was ${serviceIdWriteBack.previous ?? "unset"})` : ` - service id already current in ${serviceIdWriteBack.path}`
8282
+ );
8283
+ }
7286
8284
  return lines.join("\n");
7287
8285
  }
7288
8286
  function resolveScaffoldAudience(value) {
@@ -7327,6 +8325,12 @@ function normalizeFailOn(value) {
7327
8325
  }
7328
8326
  throw new Error("Invalid --fail-on value. Use missing, needs_review, or partial.");
7329
8327
  }
8328
+ function formatKnownValues(values, label) {
8329
+ const sorted = [...values].sort();
8330
+ const preview = sorted.slice(0, 25).join(", ");
8331
+ const suffix = sorted.length > 25 ? `, ... +${sorted.length - 25} more` : "";
8332
+ return `Run discovery for the full ${label} list. Known snapshot sample: ${preview}${suffix}`;
8333
+ }
7330
8334
  function statusesAtOrWorse(threshold) {
7331
8335
  if (threshold === "missing") return ["missing"];
7332
8336
  if (threshold === "needs_review") return ["missing", "needs_review"];
@@ -7915,7 +8919,7 @@ function normalizeList(values, fallback = []) {
7915
8919
 
7916
8920
  // src/commands/integrations.ts
7917
8921
  import { readdir, readFile as readFile3, stat } from "fs/promises";
7918
- import { join as join3, relative as relative2 } from "path";
8922
+ import { join as join4, relative as relative2 } from "path";
7919
8923
  function registerIntegrationsCommand(program2) {
7920
8924
  const integrations = program2.command("integrations").description("Generate and validate Topolo integration handoffs.");
7921
8925
  integrations.command("prompt").description("Print an agent prompt for adding a Topolo integration.").argument("<integration>", "Integration prompt id, currently login-with-topolo").option("--framework <framework>", "auto, authjs, express, or generic", "auto").option("--json", "Emit JSON output").action((integration, options) => {
@@ -7973,7 +8977,7 @@ ${file.content}`).join("\n");
7973
8977
  makeCheck(
7974
8978
  "package",
7975
8979
  "@topolo/login dependency",
7976
- packageJson2?.dependencies?.["@topolo/login"] || packageJson2?.devDependencies?.["@topolo/login"] ? ["package.json declares @topolo/login"] : [],
8980
+ topoloLoginPackageEvidence(packageJson2, files),
7977
8981
  true
7978
8982
  ),
7979
8983
  makeCheck(
@@ -8025,6 +9029,25 @@ function makeCheck(id, label, evidence, required) {
8025
9029
  function matchEvidence(haystack, needles) {
8026
9030
  return needles.filter((needle) => haystack.toLowerCase().includes(needle.toLowerCase()));
8027
9031
  }
9032
+ function topoloLoginPackageEvidence(rootPackageJson, files) {
9033
+ const evidence = [];
9034
+ if (declaresTopoloLogin(rootPackageJson)) evidence.push("package.json declares @topolo/login");
9035
+ for (const file of files) {
9036
+ if (!file.path.endsWith("package.json") || file.path === "package.json") continue;
9037
+ try {
9038
+ if (declaresTopoloLogin(JSON.parse(file.content))) {
9039
+ evidence.push(`${file.path} declares @topolo/login`);
9040
+ }
9041
+ } catch {
9042
+ }
9043
+ }
9044
+ return evidence;
9045
+ }
9046
+ function declaresTopoloLogin(packageJson2) {
9047
+ return Boolean(
9048
+ packageJson2?.dependencies?.["@topolo/login"] || packageJson2?.devDependencies?.["@topolo/login"] || packageJson2?.peerDependencies?.["@topolo/login"]
9049
+ );
9050
+ }
8028
9051
  function detectFramework(packageJson2, haystack) {
8029
9052
  const deps = {
8030
9053
  ...packageJson2?.dependencies ?? {},
@@ -8039,7 +9062,7 @@ function detectFramework(packageJson2, haystack) {
8039
9062
  }
8040
9063
  async function readPackageJson2(repoRoot) {
8041
9064
  try {
8042
- return JSON.parse(await readFile3(join3(repoRoot, "package.json"), "utf8"));
9065
+ return JSON.parse(await readFile3(join4(repoRoot, "package.json"), "utf8"));
8043
9066
  } catch {
8044
9067
  return null;
8045
9068
  }
@@ -8051,7 +9074,7 @@ async function collectFiles(repoRoot) {
8051
9074
  if (result.length > 2e3) return;
8052
9075
  const entries = await readdir(dir, { withFileTypes: true });
8053
9076
  for (const entry of entries) {
8054
- const fullPath = join3(dir, entry.name);
9077
+ const fullPath = join4(dir, entry.name);
8055
9078
  if (entry.isDirectory()) {
8056
9079
  if ([".git", "node_modules", "dist", "build", ".next", "coverage"].includes(entry.name)) continue;
8057
9080
  await walk(fullPath);