@topolo/cli 0.1.15 → 0.1.17

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 +20 -4
  2. package/dist/index.js +2147 -303
  3. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -55,43 +55,82 @@ var TopoloHttpError = class extends TopoloSdkError {
55
55
  body;
56
56
  service;
57
57
  path;
58
- constructor(service, path2, status, body, message) {
59
- super("http_error", message ?? `HTTP ${status} from ${service}${path2}`);
58
+ constructor(service, path4, status, body, message) {
59
+ super("http_error", message ?? `HTTP ${status} from ${service}${path4}`);
60
60
  this.name = "TopoloHttpError";
61
61
  this.status = status;
62
62
  this.body = body;
63
63
  this.service = service;
64
- this.path = path2;
64
+ this.path = path4;
65
65
  }
66
66
  };
67
67
  var SERVICES = {
68
- agent: "https://topolo-agent-worker.topolo.workers.dev",
68
+ agent: "https://agent-api.topolo.app",
69
69
  auth: "https://auth.topolo.app",
70
+ books: "https://books.topolo.app",
70
71
  bugfix: "https://bugfix.topolo.app",
71
- bytes: "https://r2-media-manager-worker.topolo.workers.dev",
72
+ bytes: "https://bytes-api.topolo.app",
72
73
  calendar: "https://calendar.topolo.app",
74
+ capacity: "https://capacity.topolo.app",
73
75
  chat: "https://chat.topolo.app",
74
- commerce: "https://topolo-commerce-api.topolo.workers.dev",
76
+ commerce: "https://commerce-api.topolo.app",
77
+ compose: "https://compose.topolo.app",
78
+ consent: "https://consent.topolo.app",
75
79
  crm: "https://crm-api.topolo.app",
76
- feed: "https://topolo-feed-api.topolo.app",
77
- forecast: "https://forecast-worker-production.topolo.workers.dev",
78
- learn: "https://topololearn-api.topolo.workers.dev",
80
+ customer_success: "https://success.topolo.app",
81
+ director: "https://director.topolo.app",
82
+ feed: "https://feed-api.topolo.app",
83
+ flow: "https://flow.topolo.app",
84
+ forecast: "https://forecast-api.topolo.app",
85
+ forms: "https://forms.topolo.app",
86
+ home: "https://home.topolo.app",
87
+ insights: "https://insights.topolo.app",
88
+ inventory: "https://inventory.topolo.app",
89
+ learn: "https://learn-api.topolo.app",
90
+ localize: "https://localize.topolo.app",
79
91
  mail: "https://mail.topolo.app",
80
- mdm: "https://topolo-mdm-api.topolo.app",
92
+ mdm: "https://mdm-api.topolo.app",
81
93
  messaging: "https://messages.topolo.app",
82
- nexus: "https://topolo-nexus-gateway.topolo.workers.dev",
94
+ nexus: "https://nexus-api.topolo.app",
95
+ notify: "https://notify.topolo.app",
83
96
  one: "https://one.topolo.app",
84
- pay: "https://topolo-pay.topolo.workers.dev",
97
+ "one.ai": "https://ai.one.topolo.app",
98
+ p2p: "https://p2p.topolo.app",
99
+ pay: "https://pay.topolo.app",
100
+ people: "https://people.topolo.app",
85
101
  "quro.api": "https://api.ol0.me",
86
102
  "quro.redirect": "https://ol0.me",
87
103
  roadmapper: "https://roadmapper-api.topolo.app",
88
- social_studio: "https://topolo-social-studio-api.topolo.workers.dev",
89
- socialize: "https://socialize-prod-api.topolo.workers.dev",
104
+ sign: "https://sign.topolo.app",
105
+ social_studio: "https://studio-api.topolo.app",
106
+ socialize: "https://socialize-api.topolo.app",
107
+ status: "https://status.topolo.app",
90
108
  support: "https://support.topolo.app",
109
+ survey: "https://survey.topolo.app",
110
+ voice: "https://voice.topolo.app",
91
111
  web: "https://web-api.topolo.app"
92
112
  };
93
113
  var PLATFORM_SERVICE_IDS = {
94
- crm: "srv_iCwM4jGXcwlj"
114
+ books: "srv_topolo_books",
115
+ bugfix: "srv_topolo_bugfix",
116
+ compose: "srv_topolo_compose",
117
+ consent: "srv_topolo_consent",
118
+ crm: "srv_iCwM4jGXcwlj",
119
+ customer_success: "srv_topolo_success",
120
+ director: "srv_topolo_director",
121
+ flow: "srv_topolo_flow",
122
+ forms: "srv_topolo_forms",
123
+ home: "srv_topolo_home",
124
+ insights: "srv_topolo_insights",
125
+ inventory: "srv_topolo_inventory",
126
+ localize: "srv_topolo_localize",
127
+ notify: "svc_topolo_notify",
128
+ p2p: "srv_topolo_p2p",
129
+ people: "srv_topolo_people",
130
+ sign: "srv_topolo_sign",
131
+ status: "srv_topolo_status",
132
+ survey: "srv_topolo_survey",
133
+ voice: "srv_topolo_voice"
95
134
  };
96
135
  var APPLICATIONS = {
97
136
  "admin": {
@@ -106,7 +145,9 @@ var APPLICATIONS = {
106
145
  "name": "topoloadmin",
107
146
  "kind": "pages",
108
147
  "apiUrl": null,
109
- "domains": [],
148
+ "domains": [
149
+ "admin.topolo.app"
150
+ ],
110
151
  "routes": [],
111
152
  "serviceId": null
112
153
  }
@@ -126,15 +167,19 @@ var APPLICATIONS = {
126
167
  "name": "topolo-agent",
127
168
  "kind": "pages",
128
169
  "apiUrl": null,
129
- "domains": [],
170
+ "domains": [
171
+ "agent.topolo.app"
172
+ ],
130
173
  "routes": [],
131
174
  "serviceId": null
132
175
  },
133
176
  {
134
177
  "name": "topolo-agent-worker",
135
178
  "kind": "worker",
136
- "apiUrl": "https://topolo-agent-worker.topolo.workers.dev",
137
- "domains": [],
179
+ "apiUrl": "https://agent-api.topolo.app",
180
+ "domains": [
181
+ "agent-api.topolo.app"
182
+ ],
138
183
  "routes": [],
139
184
  "serviceId": "agent"
140
185
  }
@@ -164,6 +209,30 @@ var APPLICATIONS = {
164
209
  }
165
210
  ]
166
211
  },
212
+ "books": {
213
+ "id": "books",
214
+ "appDir": "TopoloBooks",
215
+ "name": "topolo-books",
216
+ "packageName": "topolo-books",
217
+ "productionUrl": "https://books.topolo.app",
218
+ "services": [
219
+ "books"
220
+ ],
221
+ "deployTargets": [
222
+ {
223
+ "name": "topolo-books",
224
+ "kind": "worker",
225
+ "apiUrl": "https://books.topolo.app",
226
+ "domains": [
227
+ "books.topolo.app"
228
+ ],
229
+ "routes": [
230
+ "books.topolo.app"
231
+ ],
232
+ "serviceId": "books"
233
+ }
234
+ ]
235
+ },
167
236
  "bugfix": {
168
237
  "id": "bugfix",
169
238
  "appDir": "TopoloBugFix",
@@ -199,18 +268,22 @@ var APPLICATIONS = {
199
268
  ],
200
269
  "deployTargets": [
201
270
  {
202
- "name": "bytes",
271
+ "name": "topolo-bytes",
203
272
  "kind": "pages",
204
273
  "apiUrl": null,
205
- "domains": [],
274
+ "domains": [
275
+ "bytes.topolo.app"
276
+ ],
206
277
  "routes": [],
207
278
  "serviceId": null
208
279
  },
209
280
  {
210
- "name": "r2-media-manager-worker",
281
+ "name": "topolo-bytes-api",
211
282
  "kind": "worker",
212
- "apiUrl": "https://r2-media-manager-worker.topolo.workers.dev",
213
- "domains": [],
283
+ "apiUrl": "https://bytes-api.topolo.app",
284
+ "domains": [
285
+ "bytes-api.topolo.app"
286
+ ],
214
287
  "routes": [],
215
288
  "serviceId": "bytes"
216
289
  }
@@ -240,6 +313,28 @@ var APPLICATIONS = {
240
313
  }
241
314
  ]
242
315
  },
316
+ "capacity": {
317
+ "id": "capacity",
318
+ "appDir": "TopoloCapacity",
319
+ "name": "topolo-capacity",
320
+ "packageName": "topolo-capacity",
321
+ "productionUrl": "https://capacity.topolo.app",
322
+ "services": [
323
+ "capacity"
324
+ ],
325
+ "deployTargets": [
326
+ {
327
+ "name": "topolo-capacity",
328
+ "kind": "worker",
329
+ "apiUrl": "https://capacity.topolo.app",
330
+ "domains": [
331
+ "capacity.topolo.app"
332
+ ],
333
+ "routes": [],
334
+ "serviceId": "capacity"
335
+ }
336
+ ]
337
+ },
243
338
  "chat": {
244
339
  "id": "chat",
245
340
  "appDir": "TopoloChat",
@@ -276,7 +371,7 @@ var APPLICATIONS = {
276
371
  "cloudcontrol": {
277
372
  "id": "cloudcontrol",
278
373
  "appDir": "TopoloCloudControl",
279
- "name": "topolo-cloudcontrol",
374
+ "name": "cloudcontrol",
280
375
  "packageName": "topolo-cloudcontrol",
281
376
  "productionUrl": null,
282
377
  "services": [],
@@ -295,9 +390,9 @@ var APPLICATIONS = {
295
390
  {
296
391
  "name": "topolo-commerce-api",
297
392
  "kind": "worker",
298
- "apiUrl": "https://topolo-commerce-api.topolo.workers.dev",
393
+ "apiUrl": "https://commerce-api.topolo.app",
299
394
  "domains": [
300
- "topolo-commerce-api.topolo.workers.dev"
395
+ "commerce-api.topolo.app"
301
396
  ],
302
397
  "routes": [],
303
398
  "serviceId": "commerce"
@@ -307,8 +402,7 @@ var APPLICATIONS = {
307
402
  "kind": "pages",
308
403
  "apiUrl": null,
309
404
  "domains": [
310
- "guest.topolo.app",
311
- "topolo-commerce-guest-web.pages.dev"
405
+ "guest.topolo.app"
312
406
  ],
313
407
  "routes": [],
314
408
  "serviceId": null
@@ -318,8 +412,7 @@ var APPLICATIONS = {
318
412
  "kind": "pages",
319
413
  "apiUrl": null,
320
414
  "domains": [
321
- "commerce.topolo.app",
322
- "topolo-commerce-ops-web.pages.dev"
415
+ "commerce.topolo.app"
323
416
  ],
324
417
  "routes": [],
325
418
  "serviceId": null
@@ -329,14 +422,61 @@ var APPLICATIONS = {
329
422
  "kind": "pages",
330
423
  "apiUrl": null,
331
424
  "domains": [
332
- "station-commerce.topolo.app",
333
- "topolo-commerce-station-web.pages.dev"
425
+ "station-commerce.topolo.app"
334
426
  ],
335
427
  "routes": [],
336
428
  "serviceId": null
337
429
  }
338
430
  ]
339
431
  },
432
+ "compose": {
433
+ "id": "compose",
434
+ "appDir": "TopoloCompose",
435
+ "name": "topolo-compose",
436
+ "packageName": "@topolo/compose",
437
+ "productionUrl": "https://compose.topolo.app",
438
+ "services": [
439
+ "compose"
440
+ ],
441
+ "deployTargets": [
442
+ {
443
+ "name": "topolo-compose",
444
+ "kind": "worker",
445
+ "apiUrl": "https://compose.topolo.app",
446
+ "domains": [
447
+ "compose.topolo.app"
448
+ ],
449
+ "routes": [
450
+ "compose.topolo.app"
451
+ ],
452
+ "serviceId": "compose"
453
+ }
454
+ ]
455
+ },
456
+ "consent": {
457
+ "id": "consent",
458
+ "appDir": "TopoloConsent",
459
+ "name": "topolo-consent",
460
+ "packageName": "topolo-consent",
461
+ "productionUrl": "https://consent.topolo.app",
462
+ "services": [
463
+ "consent"
464
+ ],
465
+ "deployTargets": [
466
+ {
467
+ "name": "topolo-consent",
468
+ "kind": "worker",
469
+ "apiUrl": "https://consent.topolo.app",
470
+ "domains": [
471
+ "consent.topolo.app"
472
+ ],
473
+ "routes": [
474
+ "consent.topolo.app"
475
+ ],
476
+ "serviceId": "consent"
477
+ }
478
+ ]
479
+ },
340
480
  "crm": {
341
481
  "id": "crm",
342
482
  "appDir": "TopoloCRM",
@@ -348,20 +488,66 @@ var APPLICATIONS = {
348
488
  ],
349
489
  "deployTargets": [
350
490
  {
351
- "name": "topolo-crm-frontend",
491
+ "name": "topolo-crm-api",
492
+ "kind": "worker",
493
+ "apiUrl": "https://crm-api.topolo.app",
494
+ "domains": [
495
+ "crm-api.topolo.app"
496
+ ],
497
+ "routes": [],
498
+ "serviceId": "crm"
499
+ },
500
+ {
501
+ "name": "topolo-crm-app",
352
502
  "kind": "pages",
353
503
  "apiUrl": null,
354
- "domains": [],
504
+ "domains": [
505
+ "crm.topolo.app"
506
+ ],
355
507
  "routes": [],
356
508
  "serviceId": null
357
- },
509
+ }
510
+ ]
511
+ },
512
+ "customer_success": {
513
+ "id": "customer_success",
514
+ "appDir": "TopoloSuccess",
515
+ "name": "topolo-success",
516
+ "packageName": "topolo-success",
517
+ "productionUrl": "https://success.topolo.app",
518
+ "services": [
519
+ "customer_success"
520
+ ],
521
+ "deployTargets": [
358
522
  {
359
- "name": "topolo-crm-worker",
523
+ "name": "topolo-success",
360
524
  "kind": "worker",
361
- "apiUrl": "https://crm-api.topolo.app",
525
+ "apiUrl": "https://success.topolo.app",
526
+ "domains": [
527
+ "success.topolo.app"
528
+ ],
529
+ "routes": [
530
+ "success.topolo.app"
531
+ ],
532
+ "serviceId": "customer_success"
533
+ }
534
+ ]
535
+ },
536
+ "d1_backup": {
537
+ "id": "d1_backup",
538
+ "appDir": "TopoloD1Backup",
539
+ "name": "topolo-d1-backup",
540
+ "packageName": "topolo-d1-backup",
541
+ "productionUrl": null,
542
+ "services": [],
543
+ "deployTargets": [
544
+ {
545
+ "name": "topolo-d1-backup",
546
+ "kind": "worker",
547
+ "apiUrl": null,
362
548
  "domains": [],
363
549
  "routes": [],
364
- "serviceId": "crm"
550
+ "serviceId": null
365
551
  }
366
552
  ]
367
553
  },
@@ -373,6 +559,14 @@ var APPLICATIONS = {
373
559
  "productionUrl": "https://developers.topolo.app",
374
560
  "services": [],
375
561
  "deployTargets": [
562
+ {
563
+ "name": "topolo-catalog-cron",
564
+ "kind": "worker",
565
+ "apiUrl": null,
566
+ "domains": [],
567
+ "routes": [],
568
+ "serviceId": null
569
+ },
376
570
  {
377
571
  "name": "topolo-developers",
378
572
  "kind": "pages",
@@ -385,6 +579,30 @@ var APPLICATIONS = {
385
579
  }
386
580
  ]
387
581
  },
582
+ "director": {
583
+ "id": "director",
584
+ "appDir": "TopoloDirector",
585
+ "name": "topolo-director",
586
+ "packageName": "topolo-director",
587
+ "productionUrl": "https://director.topolo.app",
588
+ "services": [
589
+ "director"
590
+ ],
591
+ "deployTargets": [
592
+ {
593
+ "name": "topolo-director",
594
+ "kind": "worker",
595
+ "apiUrl": "https://director.topolo.app",
596
+ "domains": [
597
+ "director.topolo.app"
598
+ ],
599
+ "routes": [
600
+ "director.topolo.app"
601
+ ],
602
+ "serviceId": "director"
603
+ }
604
+ ]
605
+ },
388
606
  "docs": {
389
607
  "id": "docs",
390
608
  "appDir": "TopoloDocs",
@@ -409,8 +627,8 @@ var APPLICATIONS = {
409
627
  "id": "feed",
410
628
  "appDir": "TopoloFeed",
411
629
  "name": "TopoloFeed",
412
- "packageName": null,
413
- "productionUrl": "https://topolo-feed-api.topolo.app",
630
+ "packageName": "topolo-feed",
631
+ "productionUrl": "https://feed-api.topolo.app",
414
632
  "services": [
415
633
  "feed"
416
634
  ],
@@ -418,29 +636,53 @@ var APPLICATIONS = {
418
636
  {
419
637
  "name": "topolo-feed-analytics-api",
420
638
  "kind": "worker",
421
- "apiUrl": "https://topolo-feed-analytics-api.topolo.app",
639
+ "apiUrl": "https://feed-analytics-api.topolo.app",
422
640
  "domains": [
423
- "topolo-feed-analytics-api.topolo.app"
641
+ "feed-analytics-api.topolo.app"
424
642
  ],
425
643
  "routes": [
426
- "topolo-feed-analytics-api.topolo.app"
644
+ "feed-analytics-api.topolo.app"
427
645
  ],
428
646
  "serviceId": null
429
647
  },
430
648
  {
431
649
  "name": "topolo-feed-api",
432
650
  "kind": "worker",
433
- "apiUrl": "https://topolo-feed-api.topolo.app",
651
+ "apiUrl": "https://feed-api.topolo.app",
434
652
  "domains": [
435
- "topolo-feed-api.topolo.app"
653
+ "feed-api.topolo.app"
436
654
  ],
437
655
  "routes": [
438
- "topolo-feed-api.topolo.app"
656
+ "feed-api.topolo.app"
439
657
  ],
440
658
  "serviceId": "feed"
441
659
  }
442
660
  ]
443
661
  },
662
+ "flow": {
663
+ "id": "flow",
664
+ "appDir": "TopoloFlow",
665
+ "name": "topolo-flow",
666
+ "packageName": "topolo-flow",
667
+ "productionUrl": "https://flow.topolo.app",
668
+ "services": [
669
+ "flow"
670
+ ],
671
+ "deployTargets": [
672
+ {
673
+ "name": "topolo-flow",
674
+ "kind": "worker",
675
+ "apiUrl": "https://flow.topolo.app",
676
+ "domains": [
677
+ "flow.topolo.app"
678
+ ],
679
+ "routes": [
680
+ "flow.topolo.app"
681
+ ],
682
+ "serviceId": "flow"
683
+ }
684
+ ]
685
+ },
444
686
  "forecast": {
445
687
  "id": "forecast",
446
688
  "appDir": "TopoloForecast",
@@ -452,23 +694,123 @@ var APPLICATIONS = {
452
694
  ],
453
695
  "deployTargets": [
454
696
  {
455
- "name": "forecast",
697
+ "name": "topolo-forecast",
456
698
  "kind": "pages",
457
699
  "apiUrl": null,
458
- "domains": [],
700
+ "domains": [
701
+ "forecast.topolo.app"
702
+ ],
459
703
  "routes": [],
460
704
  "serviceId": null
461
705
  },
462
706
  {
463
- "name": "forecast-worker-production",
707
+ "name": "topolo-forecast-api",
464
708
  "kind": "worker",
465
- "apiUrl": "https://forecast-worker-production.topolo.workers.dev",
466
- "domains": [],
709
+ "apiUrl": "https://forecast-api.topolo.app",
710
+ "domains": [
711
+ "forecast-api.topolo.app"
712
+ ],
467
713
  "routes": [],
468
714
  "serviceId": "forecast"
469
715
  }
470
716
  ]
471
717
  },
718
+ "forms": {
719
+ "id": "forms",
720
+ "appDir": "TopoloForms",
721
+ "name": "topolo-forms",
722
+ "packageName": "topolo-forms",
723
+ "productionUrl": "https://forms.topolo.app",
724
+ "services": [
725
+ "forms"
726
+ ],
727
+ "deployTargets": [
728
+ {
729
+ "name": "topolo-forms",
730
+ "kind": "worker",
731
+ "apiUrl": "https://forms.topolo.app",
732
+ "domains": [
733
+ "forms.topolo.app"
734
+ ],
735
+ "routes": [
736
+ "forms.topolo.app"
737
+ ],
738
+ "serviceId": "forms"
739
+ }
740
+ ]
741
+ },
742
+ "home": {
743
+ "id": "home",
744
+ "appDir": "TopoloHome",
745
+ "name": "topolo-home",
746
+ "packageName": "topolo-home",
747
+ "productionUrl": "https://home.topolo.app",
748
+ "services": [
749
+ "home"
750
+ ],
751
+ "deployTargets": [
752
+ {
753
+ "name": "topolo-home",
754
+ "kind": "worker",
755
+ "apiUrl": "https://home.topolo.app",
756
+ "domains": [
757
+ "home.topolo.app"
758
+ ],
759
+ "routes": [
760
+ "home.topolo.app"
761
+ ],
762
+ "serviceId": "home"
763
+ }
764
+ ]
765
+ },
766
+ "insights": {
767
+ "id": "insights",
768
+ "appDir": "TopoloInsights",
769
+ "name": "topolo-insights",
770
+ "packageName": "topolo-insights",
771
+ "productionUrl": "https://insights.topolo.app",
772
+ "services": [
773
+ "insights"
774
+ ],
775
+ "deployTargets": [
776
+ {
777
+ "name": "topolo-insights",
778
+ "kind": "worker",
779
+ "apiUrl": "https://insights.topolo.app",
780
+ "domains": [
781
+ "insights.topolo.app"
782
+ ],
783
+ "routes": [
784
+ "insights.topolo.app"
785
+ ],
786
+ "serviceId": "insights"
787
+ }
788
+ ]
789
+ },
790
+ "inventory": {
791
+ "id": "inventory",
792
+ "appDir": "TopoloInventory",
793
+ "name": "topolo-inventory",
794
+ "packageName": "topolo-inventory",
795
+ "productionUrl": "https://inventory.topolo.app",
796
+ "services": [
797
+ "inventory"
798
+ ],
799
+ "deployTargets": [
800
+ {
801
+ "name": "topolo-inventory",
802
+ "kind": "worker",
803
+ "apiUrl": "https://inventory.topolo.app",
804
+ "domains": [
805
+ "inventory.topolo.app"
806
+ ],
807
+ "routes": [
808
+ "inventory.topolo.app"
809
+ ],
810
+ "serviceId": "inventory"
811
+ }
812
+ ]
813
+ },
472
814
  "learn": {
473
815
  "id": "learn",
474
816
  "appDir": "TopoloLearn",
@@ -480,7 +822,7 @@ var APPLICATIONS = {
480
822
  ],
481
823
  "deployTargets": [
482
824
  {
483
- "name": "topololearn",
825
+ "name": "topolo-learn",
484
826
  "kind": "pages",
485
827
  "apiUrl": null,
486
828
  "domains": [
@@ -490,17 +832,41 @@ var APPLICATIONS = {
490
832
  "serviceId": null
491
833
  },
492
834
  {
493
- "name": "topololearn-api",
835
+ "name": "topolo-learn-api",
494
836
  "kind": "worker",
495
- "apiUrl": "https://topololearn-api.topolo.workers.dev",
837
+ "apiUrl": "https://learn-api.topolo.app",
496
838
  "domains": [
497
- "topololearn-api.topolo.workers.dev"
839
+ "learn-api.topolo.app"
498
840
  ],
499
841
  "routes": [],
500
842
  "serviceId": "learn"
501
843
  }
502
844
  ]
503
845
  },
846
+ "localize": {
847
+ "id": "localize",
848
+ "appDir": "TopoloLocalize",
849
+ "name": "topolo-localize",
850
+ "packageName": "topolo-localize",
851
+ "productionUrl": "https://localize.topolo.app",
852
+ "services": [
853
+ "localize"
854
+ ],
855
+ "deployTargets": [
856
+ {
857
+ "name": "topolo-localize",
858
+ "kind": "worker",
859
+ "apiUrl": "https://localize.topolo.app",
860
+ "domains": [
861
+ "localize.topolo.app"
862
+ ],
863
+ "routes": [
864
+ "localize.topolo.app"
865
+ ],
866
+ "serviceId": "localize"
867
+ }
868
+ ]
869
+ },
504
870
  "mail": {
505
871
  "id": "mail",
506
872
  "appDir": "TopoloMail",
@@ -538,7 +904,7 @@ var APPLICATIONS = {
538
904
  "id": "mdm",
539
905
  "appDir": "TopoloMDM",
540
906
  "name": "TopoloMDM",
541
- "packageName": null,
907
+ "packageName": "topolo-mdm",
542
908
  "productionUrl": "https://mdm.topolo.app",
543
909
  "services": [
544
910
  "mdm"
@@ -547,12 +913,12 @@ var APPLICATIONS = {
547
913
  {
548
914
  "name": "topolo-mdm-api",
549
915
  "kind": "worker",
550
- "apiUrl": "https://topolo-mdm-api.topolo.app",
916
+ "apiUrl": "https://mdm-api.topolo.app",
551
917
  "domains": [
552
- "topolo-mdm-api.topolo.app"
918
+ "mdm-api.topolo.app"
553
919
  ],
554
920
  "routes": [
555
- "topolo-mdm-api.topolo.app"
921
+ "mdm-api.topolo.app"
556
922
  ],
557
923
  "serviceId": "mdm"
558
924
  },
@@ -561,8 +927,7 @@ var APPLICATIONS = {
561
927
  "kind": "pages",
562
928
  "apiUrl": null,
563
929
  "domains": [
564
- "mdm.topolo.app",
565
- "topolo-mdm-console.pages.dev"
930
+ "mdm.topolo.app"
566
931
  ],
567
932
  "routes": [
568
933
  "mdm.topolo.app"
@@ -616,8 +981,7 @@ var APPLICATIONS = {
616
981
  "kind": "pages",
617
982
  "apiUrl": null,
618
983
  "domains": [
619
- "nexus.topolo.app",
620
- "topolo-nexus-dashboard.pages.dev"
984
+ "nexus.topolo.app"
621
985
  ],
622
986
  "routes": [],
623
987
  "serviceId": null
@@ -625,15 +989,48 @@ var APPLICATIONS = {
625
989
  {
626
990
  "name": "topolo-nexus-gateway",
627
991
  "kind": "worker",
628
- "apiUrl": "https://topolo-nexus-gateway.topolo.workers.dev",
992
+ "apiUrl": "https://nexus-api.topolo.app",
629
993
  "domains": [
630
- "topolo-nexus-gateway.topolo.workers.dev"
994
+ "nexus-api.topolo.app"
631
995
  ],
632
996
  "routes": [],
633
997
  "serviceId": "nexus"
634
998
  }
635
999
  ]
636
1000
  },
1001
+ "notify": {
1002
+ "id": "notify",
1003
+ "appDir": "TopoloNotify",
1004
+ "name": "topolo-notify",
1005
+ "packageName": "@topolo/notify",
1006
+ "productionUrl": "https://notify.topolo.app",
1007
+ "services": [
1008
+ "notify"
1009
+ ],
1010
+ "deployTargets": [
1011
+ {
1012
+ "name": "topolo-notify",
1013
+ "kind": "worker",
1014
+ "apiUrl": "https://notify.topolo.app",
1015
+ "domains": [
1016
+ "notify.topolo.app"
1017
+ ],
1018
+ "routes": [
1019
+ "notify.topolo.app"
1020
+ ],
1021
+ "serviceId": "notify"
1022
+ }
1023
+ ]
1024
+ },
1025
+ "observability_watch": {
1026
+ "id": "observability_watch",
1027
+ "appDir": "TopoloObservabilityWatch",
1028
+ "name": "topolo-observability-watch",
1029
+ "packageName": "topolo-observability-watch",
1030
+ "productionUrl": null,
1031
+ "services": [],
1032
+ "deployTargets": []
1033
+ },
637
1034
  "one": {
638
1035
  "id": "one",
639
1036
  "appDir": "TopoloOne",
@@ -641,51 +1038,83 @@ var APPLICATIONS = {
641
1038
  "packageName": "topolo-oneclick-sso",
642
1039
  "productionUrl": "https://one.topolo.app",
643
1040
  "services": [
644
- "one"
1041
+ "one",
1042
+ "one.ai"
645
1043
  ],
646
1044
  "deployTargets": [
647
1045
  {
648
1046
  "name": "ai-chat-rag",
649
1047
  "kind": "worker",
650
- "apiUrl": null,
1048
+ "apiUrl": "https://ai.one.topolo.app",
651
1049
  "domains": [
652
- "ai-chat-rag.topolo.workers.dev"
1050
+ "ai.one.topolo.app"
653
1051
  ],
654
1052
  "routes": [],
655
- "serviceId": null
1053
+ "serviceId": "one.ai"
656
1054
  },
657
1055
  {
658
- "name": "topoloone",
1056
+ "name": "topolo-one",
659
1057
  "kind": "pages",
660
1058
  "apiUrl": null,
661
1059
  "domains": [
662
- "oneclick-dashboard.pages.dev"
1060
+ "one.topolo.app"
663
1061
  ],
664
1062
  "routes": [],
665
1063
  "serviceId": null
666
1064
  },
667
1065
  {
668
- "name": "topoloone-website",
1066
+ "name": "topolo-one-website",
669
1067
  "kind": "worker",
670
1068
  "apiUrl": null,
671
1069
  "domains": [
672
- "one.topolo.io"
1070
+ "topolo.io",
1071
+ "www.topolo.io"
673
1072
  ],
674
1073
  "routes": [],
675
1074
  "serviceId": null
676
1075
  },
677
1076
  {
678
- "name": "topoloone-worker",
1077
+ "name": "topolo-one-worker",
679
1078
  "kind": "worker",
680
1079
  "apiUrl": "https://one.topolo.app",
681
- "domains": [],
1080
+ "domains": [
1081
+ "one.topolo.app",
1082
+ "topolo.app",
1083
+ "www.topolo.app"
1084
+ ],
682
1085
  "routes": [
683
- "one.topolo.app/*"
1086
+ "one.topolo.app/*",
1087
+ "topolo.app",
1088
+ "www.topolo.app/*"
684
1089
  ],
685
1090
  "serviceId": "one"
686
1091
  }
687
1092
  ]
688
1093
  },
1094
+ "p2p": {
1095
+ "id": "p2p",
1096
+ "appDir": "TopoloP2P",
1097
+ "name": "topolo-p2p",
1098
+ "packageName": "topolo-p2p",
1099
+ "productionUrl": "https://p2p.topolo.app",
1100
+ "services": [
1101
+ "p2p"
1102
+ ],
1103
+ "deployTargets": [
1104
+ {
1105
+ "name": "topolo-p2p",
1106
+ "kind": "worker",
1107
+ "apiUrl": "https://p2p.topolo.app",
1108
+ "domains": [
1109
+ "p2p.topolo.app"
1110
+ ],
1111
+ "routes": [
1112
+ "p2p.topolo.app"
1113
+ ],
1114
+ "serviceId": "p2p"
1115
+ }
1116
+ ]
1117
+ },
689
1118
  "pay": {
690
1119
  "id": "pay",
691
1120
  "appDir": "TopoloPay",
@@ -699,18 +1128,53 @@ var APPLICATIONS = {
699
1128
  {
700
1129
  "name": "topolo-pay",
701
1130
  "kind": "worker",
702
- "apiUrl": "https://topolo-pay.topolo.workers.dev",
703
- "domains": [],
1131
+ "apiUrl": "https://pay.topolo.app",
1132
+ "domains": [
1133
+ "pay.topolo.app"
1134
+ ],
704
1135
  "routes": [],
705
1136
  "serviceId": "pay"
706
1137
  }
707
1138
  ]
708
1139
  },
1140
+ "people": {
1141
+ "id": "people",
1142
+ "appDir": "TopoloPeople",
1143
+ "name": "topolo-people",
1144
+ "packageName": "topolo-people",
1145
+ "productionUrl": "https://people.topolo.app",
1146
+ "services": [
1147
+ "people"
1148
+ ],
1149
+ "deployTargets": [
1150
+ {
1151
+ "name": "topolo-people",
1152
+ "kind": "worker",
1153
+ "apiUrl": "https://people.topolo.app",
1154
+ "domains": [
1155
+ "people.topolo.app"
1156
+ ],
1157
+ "routes": [
1158
+ "people.topolo.app"
1159
+ ],
1160
+ "serviceId": "people"
1161
+ }
1162
+ ]
1163
+ },
1164
+ "pulse": {
1165
+ "id": "pulse",
1166
+ "appDir": "TopoloPulse",
1167
+ "name": "topolo-pulse",
1168
+ "packageName": "topolo-pulse",
1169
+ "productionUrl": null,
1170
+ "services": [],
1171
+ "deployTargets": []
1172
+ },
709
1173
  "quro": {
710
1174
  "id": "quro",
711
1175
  "appDir": "TopoloQuro",
712
1176
  "name": "TopoloQuro",
713
- "packageName": null,
1177
+ "packageName": "topolo-quro",
714
1178
  "productionUrl": "https://quro.topolo.app",
715
1179
  "services": [
716
1180
  "quro.api",
@@ -721,7 +1185,9 @@ var APPLICATIONS = {
721
1185
  "name": "quro-api",
722
1186
  "kind": "worker",
723
1187
  "apiUrl": "https://api.ol0.me",
724
- "domains": [],
1188
+ "domains": [
1189
+ "api.ol0.me"
1190
+ ],
725
1191
  "routes": [],
726
1192
  "serviceId": "quro.api"
727
1193
  },
@@ -729,7 +1195,9 @@ var APPLICATIONS = {
729
1195
  "name": "quro-redirect-system",
730
1196
  "kind": "worker",
731
1197
  "apiUrl": "https://ol0.me",
732
- "domains": [],
1198
+ "domains": [
1199
+ "ol0.me"
1200
+ ],
733
1201
  "routes": [
734
1202
  "ol0.me/*"
735
1203
  ],
@@ -739,7 +1207,9 @@ var APPLICATIONS = {
739
1207
  "name": "quro-ui",
740
1208
  "kind": "pages",
741
1209
  "apiUrl": null,
742
- "domains": [],
1210
+ "domains": [
1211
+ "quro.topolo.app"
1212
+ ],
743
1213
  "routes": [],
744
1214
  "serviceId": null
745
1215
  }
@@ -756,7 +1226,7 @@ var APPLICATIONS = {
756
1226
  ],
757
1227
  "deployTargets": [
758
1228
  {
759
- "name": "roadmapper",
1229
+ "name": "topolo-roadmapper",
760
1230
  "kind": "pages",
761
1231
  "apiUrl": null,
762
1232
  "domains": [
@@ -766,7 +1236,7 @@ var APPLICATIONS = {
766
1236
  "serviceId": null
767
1237
  },
768
1238
  {
769
- "name": "topolo-production-roadmapper",
1239
+ "name": "topolo-roadmapper-api",
770
1240
  "kind": "worker",
771
1241
  "apiUrl": "https://roadmapper-api.topolo.app",
772
1242
  "domains": [
@@ -777,6 +1247,30 @@ var APPLICATIONS = {
777
1247
  }
778
1248
  ]
779
1249
  },
1250
+ "sign": {
1251
+ "id": "sign",
1252
+ "appDir": "TopoloSign",
1253
+ "name": "topolo-sign",
1254
+ "packageName": "topolo-sign",
1255
+ "productionUrl": "https://sign.topolo.app",
1256
+ "services": [
1257
+ "sign"
1258
+ ],
1259
+ "deployTargets": [
1260
+ {
1261
+ "name": "topolo-sign",
1262
+ "kind": "worker",
1263
+ "apiUrl": "https://sign.topolo.app",
1264
+ "domains": [
1265
+ "sign.topolo.app"
1266
+ ],
1267
+ "routes": [
1268
+ "sign.topolo.app"
1269
+ ],
1270
+ "serviceId": "sign"
1271
+ }
1272
+ ]
1273
+ },
780
1274
  "social_studio": {
781
1275
  "id": "social_studio",
782
1276
  "appDir": "TopoloSocialStudio",
@@ -791,15 +1285,19 @@ var APPLICATIONS = {
791
1285
  "name": "topolo-social-studio",
792
1286
  "kind": "pages",
793
1287
  "apiUrl": null,
794
- "domains": [],
1288
+ "domains": [
1289
+ "studio.topolo.app"
1290
+ ],
795
1291
  "routes": [],
796
1292
  "serviceId": null
797
1293
  },
798
1294
  {
799
1295
  "name": "topolo-social-studio-api",
800
1296
  "kind": "worker",
801
- "apiUrl": "https://topolo-social-studio-api.topolo.workers.dev",
802
- "domains": [],
1297
+ "apiUrl": "https://studio-api.topolo.app",
1298
+ "domains": [
1299
+ "studio-api.topolo.app"
1300
+ ],
803
1301
  "routes": [],
804
1302
  "serviceId": "social_studio"
805
1303
  },
@@ -824,23 +1322,27 @@ var APPLICATIONS = {
824
1322
  ],
825
1323
  "deployTargets": [
826
1324
  {
827
- "name": "socialize",
1325
+ "name": "topolo-socialize",
828
1326
  "kind": "pages",
829
1327
  "apiUrl": null,
830
- "domains": [],
1328
+ "domains": [
1329
+ "socialize.topolo.app"
1330
+ ],
831
1331
  "routes": [],
832
1332
  "serviceId": null
833
1333
  },
834
1334
  {
835
- "name": "socialize-prod-api",
1335
+ "name": "topolo-socialize-api",
836
1336
  "kind": "worker",
837
- "apiUrl": "https://socialize-prod-api.topolo.workers.dev",
838
- "domains": [],
1337
+ "apiUrl": "https://socialize-api.topolo.app",
1338
+ "domains": [
1339
+ "socialize-api.topolo.app"
1340
+ ],
839
1341
  "routes": [],
840
1342
  "serviceId": "socialize"
841
1343
  },
842
1344
  {
843
- "name": "socialize-prod-scheduler",
1345
+ "name": "topolo-socialize-scheduler",
844
1346
  "kind": "worker",
845
1347
  "apiUrl": null,
846
1348
  "domains": [],
@@ -849,6 +1351,30 @@ var APPLICATIONS = {
849
1351
  }
850
1352
  ]
851
1353
  },
1354
+ "status": {
1355
+ "id": "status",
1356
+ "appDir": "TopoloStatus",
1357
+ "name": "topolo-status",
1358
+ "packageName": "topolo-status",
1359
+ "productionUrl": "https://status.topolo.app",
1360
+ "services": [
1361
+ "status"
1362
+ ],
1363
+ "deployTargets": [
1364
+ {
1365
+ "name": "topolo-status",
1366
+ "kind": "worker",
1367
+ "apiUrl": "https://status.topolo.app",
1368
+ "domains": [
1369
+ "status.topolo.app"
1370
+ ],
1371
+ "routes": [
1372
+ "status.topolo.app"
1373
+ ],
1374
+ "serviceId": "status"
1375
+ }
1376
+ ]
1377
+ },
852
1378
  "support": {
853
1379
  "id": "support",
854
1380
  "appDir": "TopoloSupport",
@@ -873,6 +1399,54 @@ var APPLICATIONS = {
873
1399
  }
874
1400
  ]
875
1401
  },
1402
+ "survey": {
1403
+ "id": "survey",
1404
+ "appDir": "TopoloSurvey",
1405
+ "name": "topolo-survey",
1406
+ "packageName": "topolo-survey",
1407
+ "productionUrl": "https://survey.topolo.app",
1408
+ "services": [
1409
+ "survey"
1410
+ ],
1411
+ "deployTargets": [
1412
+ {
1413
+ "name": "topolo-survey",
1414
+ "kind": "worker",
1415
+ "apiUrl": "https://survey.topolo.app",
1416
+ "domains": [
1417
+ "survey.topolo.app"
1418
+ ],
1419
+ "routes": [
1420
+ "survey.topolo.app"
1421
+ ],
1422
+ "serviceId": "survey"
1423
+ }
1424
+ ]
1425
+ },
1426
+ "voice": {
1427
+ "id": "voice",
1428
+ "appDir": "TopoloVoice",
1429
+ "name": "topolo-voice",
1430
+ "packageName": "topolo-voice",
1431
+ "productionUrl": "https://voice.topolo.app",
1432
+ "services": [
1433
+ "voice"
1434
+ ],
1435
+ "deployTargets": [
1436
+ {
1437
+ "name": "topolo-voice",
1438
+ "kind": "worker",
1439
+ "apiUrl": "https://voice.topolo.app",
1440
+ "domains": [
1441
+ "voice.topolo.app"
1442
+ ],
1443
+ "routes": [
1444
+ "voice.topolo.app"
1445
+ ],
1446
+ "serviceId": "voice"
1447
+ }
1448
+ ]
1449
+ },
876
1450
  "web": {
877
1451
  "id": "web",
878
1452
  "appDir": "TopoloWeb",
@@ -884,7 +1458,7 @@ var APPLICATIONS = {
884
1458
  ],
885
1459
  "deployTargets": [
886
1460
  {
887
- "name": "topoloweb-api",
1461
+ "name": "topolo-web-api",
888
1462
  "kind": "worker",
889
1463
  "apiUrl": "https://web-api.topolo.app",
890
1464
  "domains": [
@@ -894,7 +1468,7 @@ var APPLICATIONS = {
894
1468
  "serviceId": "web"
895
1469
  },
896
1470
  {
897
- "name": "topoloweb-runtime",
1471
+ "name": "topolo-web-runtime",
898
1472
  "kind": "worker",
899
1473
  "apiUrl": "https://sites.topolo.app",
900
1474
  "domains": [
@@ -904,7 +1478,7 @@ var APPLICATIONS = {
904
1478
  "serviceId": null
905
1479
  },
906
1480
  {
907
- "name": "topoloweb-studio",
1481
+ "name": "topolo-web-studio",
908
1482
  "kind": "pages",
909
1483
  "apiUrl": null,
910
1484
  "domains": [
@@ -916,7 +1490,7 @@ var APPLICATIONS = {
916
1490
  ]
917
1491
  }
918
1492
  };
919
- var APPLICATION_REQUIREMENTS_VERSION = "2026-04-22.1";
1493
+ var APPLICATION_REQUIREMENTS_VERSION = "2026-05-01.1";
920
1494
  var APPLICATION_REQUIREMENTS = [
921
1495
  {
922
1496
  id: "platform-metadata",
@@ -974,14 +1548,16 @@ var APPLICATION_REQUIREMENTS = [
974
1548
  evidence: [
975
1549
  "@topolo/ui-kit is used for the platform shell where the app has authenticated UI",
976
1550
  "The shared launcher/app switcher is reachable from authenticated app chrome",
977
- "Account menu and sign-out behavior use TopoloAppShell or TopoloAccountMenu"
1551
+ "Account menu and sign-out behavior use TopoloAppShell or TopoloAccountMenu",
1552
+ "Authenticated app body content stays inside the viewport on mobile and desktop widths"
978
1553
  ],
979
1554
  implementation: [
980
1555
  "Use TopoloAppShell for authenticated app layouts",
981
1556
  "Use TopoloAccountMenu when a partial-shell surface still needs account chrome",
982
1557
  "Pass app-specific account actions as additive menu items instead of forking dropdowns",
983
1558
  "Use shared design tokens and platform UI primitives before creating local equivalents",
984
- "Keep mobile and desktop shell behavior aligned with the platform UI kit"
1559
+ "Keep mobile and desktop shell behavior aligned with the platform UI kit",
1560
+ "Constrain app-owned workspace wrappers with min-width: 0, width: auto, and viewport-aware max-width so shell padding cannot create page-level horizontal overflow"
985
1561
  ]
986
1562
  },
987
1563
  {
@@ -1048,6 +1624,22 @@ var APPLICATION_REQUIREMENTS = [
1048
1624
  "Keep command output JSON-stable for agents and human-readable with --no-json"
1049
1625
  ]
1050
1626
  },
1627
+ {
1628
+ id: "native-dashboard-widget",
1629
+ title: "Expose a native dashboard widget contract",
1630
+ summary: "Launchable first-party applications must populate TopoloOne live workspace through an app-owned /api/widget endpoint using the SDK widget response contract.",
1631
+ appliesTo: ["api"],
1632
+ evidence: [
1633
+ "The app-owned API exposes GET /api/widget",
1634
+ "The endpoint returns TopoloWidgetApiResponse from @topolo/sdk",
1635
+ "The endpoint derives organization/user context from the app auth boundary"
1636
+ ],
1637
+ implementation: [
1638
+ "Import createTopoloWidgetResponse from @topolo/sdk in the app backend",
1639
+ "Return native app metrics or action widgets instead of generic launch-card fallbacks",
1640
+ "Add a route-level test that validates the response with validateTopoloWidgetResponse"
1641
+ ]
1642
+ },
1051
1643
  {
1052
1644
  id: "observability-and-audit",
1053
1645
  title: "Emit platform audit and debugging signals",
@@ -1083,9 +1675,8 @@ var APPLICATION_REQUIREMENTS = [
1083
1675
  ];
1084
1676
  function applicationRequirementScopes(application) {
1085
1677
  const scopes = /* @__PURE__ */ new Set(["all"]);
1086
- const deployTargets = application.deployTargets;
1087
- const hasBrowserSurface = Boolean(application.productionUrl) || deployTargets.some((target) => target.kind === "pages");
1088
- const hasApiSurface = application.services.length > 0 || deployTargets.some((target) => Boolean(target.apiUrl));
1678
+ const hasBrowserSurface = Boolean(application.productionUrl) || application.deployTargets.some((target) => target.kind === "pages");
1679
+ const hasApiSurface = application.services.length > 0 || application.deployTargets.some((target) => Boolean(target.apiUrl));
1089
1680
  if (hasBrowserSurface) scopes.add("browser");
1090
1681
  if (hasApiSurface) scopes.add("api");
1091
1682
  if (application.id === "cli" || application.id === "mcp" || application.id === "cloudcontrol") {
@@ -1220,6 +1811,13 @@ function evaluateRequirement(application, requirement) {
1220
1811
  }
1221
1812
  return finding(requirement, "missing", "No SDK service or tooling surface is registered.", evidence);
1222
1813
  }
1814
+ case "native-dashboard-widget":
1815
+ return finding(
1816
+ requirement,
1817
+ "needs_review",
1818
+ "Catalog metadata cannot verify app-owned /api/widget implementation details.",
1819
+ evidence
1820
+ );
1223
1821
  case "observability-and-audit":
1224
1822
  return finding(
1225
1823
  requirement,
@@ -1271,11 +1869,10 @@ function statusRank(status) {
1271
1869
  }
1272
1870
  var DEFAULT_SERVICE_URLS = SERVICES;
1273
1871
  function resolveServiceUrl(service, overrides) {
1274
- var _a;
1275
- const override = overrides == null ? void 0 : overrides[service];
1872
+ const override = overrides?.[service];
1276
1873
  if (override) return override;
1277
1874
  const envKey = `TOPOLO_SERVICE_URL_${service.replace(/\./g, "_").toUpperCase()}`;
1278
- const envValue = typeof process !== "undefined" ? (_a = process.env) == null ? void 0 : _a[envKey] : void 0;
1875
+ const envValue = typeof process !== "undefined" ? process.env?.[envKey] : void 0;
1279
1876
  if (envValue) return envValue;
1280
1877
  return DEFAULT_SERVICE_URLS[service];
1281
1878
  }
@@ -1289,9 +1886,8 @@ var TopoloClient = class {
1289
1886
  fetchImpl;
1290
1887
  debug;
1291
1888
  constructor(options) {
1292
- var _a;
1293
1889
  if (!options.credential) throw new TopoloAuthError("credential is required");
1294
- if (!((_a = options.agent) == null ? void 0 : _a.clientName)) throw new TopoloAuthError("agent.clientName is required");
1890
+ if (!options.agent?.clientName) throw new TopoloAuthError("agent.clientName is required");
1295
1891
  this.credential = options.credential;
1296
1892
  this.agent = options.agent;
1297
1893
  this.serviceUrls = options.serviceUrls;
@@ -1479,7 +2075,6 @@ var CrmModule = class {
1479
2075
  }
1480
2076
  client;
1481
2077
  async listContacts(options = {}) {
1482
- var _a, _b, _c;
1483
2078
  const res = await this.client.request({
1484
2079
  service: "crm",
1485
2080
  path: "/api/contacts",
@@ -1489,12 +2084,12 @@ var CrmModule = class {
1489
2084
  pageSize: options.pageSize
1490
2085
  }
1491
2086
  });
1492
- const rows = Array.isArray(res == null ? void 0 : res.contacts) ? res.contacts : Array.isArray(res == null ? void 0 : res.data) ? res.data : [];
2087
+ const rows = Array.isArray(res?.contacts) ? res.contacts : Array.isArray(res?.data) ? res.data : [];
1493
2088
  return {
1494
2089
  contacts: rows.map(toSummary),
1495
- total: (res == null ? void 0 : res.total) ?? ((_a = res == null ? void 0 : res.pagination) == null ? void 0 : _a.total) ?? rows.length,
1496
- page: (res == null ? void 0 : res.page) ?? ((_b = res == null ? void 0 : res.pagination) == null ? void 0 : _b.page) ?? options.page ?? 1,
1497
- pageSize: (res == null ? void 0 : res.pageSize) ?? ((_c = res == null ? void 0 : res.pagination) == null ? void 0 : _c.pageSize) ?? options.pageSize ?? rows.length
2090
+ total: res?.total ?? res?.pagination?.total ?? rows.length,
2091
+ page: res?.page ?? res?.pagination?.page ?? options.page ?? 1,
2092
+ pageSize: res?.pageSize ?? res?.pagination?.pageSize ?? options.pageSize ?? rows.length
1498
2093
  };
1499
2094
  }
1500
2095
  async getContact(contactId) {
@@ -1588,8 +2183,8 @@ var TopoloOAuth = class {
1588
2183
  if (params.clientSecret) body.set("client_secret", params.clientSecret);
1589
2184
  return this.#postForm("/api/developer-oauth/token", body);
1590
2185
  }
1591
- async #postForm(path2, body) {
1592
- const url = new URL(path2, this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`);
2186
+ async #postForm(path4, body) {
2187
+ const url = new URL(path4, this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`);
1593
2188
  const res = await this.fetchImpl(url.toString(), {
1594
2189
  method: "POST",
1595
2190
  headers: {
@@ -1605,7 +2200,7 @@ var TopoloOAuth = class {
1605
2200
  const err = parsed;
1606
2201
  throw new TopoloAuthError(err.error_description || err.error, err.error);
1607
2202
  }
1608
- throw new TopoloHttpError("auth", path2, res.status, parsed);
2203
+ throw new TopoloHttpError("auth", path4, res.status, parsed);
1609
2204
  }
1610
2205
  return parsed;
1611
2206
  }
@@ -1624,6 +2219,16 @@ import { mkdir, readFile, writeFile, chmod } from "fs/promises";
1624
2219
  import { existsSync } from "fs";
1625
2220
  import { homedir, platform } from "os";
1626
2221
  import { dirname, join } from "path";
2222
+ var ENVIRONMENT_DEFAULT_SERVICE_URLS = {
2223
+ production: {
2224
+ developers: "https://developers.topolo.app"
2225
+ },
2226
+ staging: {
2227
+ auth: "https://auth.stg.topolo.us",
2228
+ developers: "https://developers.stg.topolo.us",
2229
+ seed: "https://seed.stg.topolo.us"
2230
+ }
2231
+ };
1627
2232
  function configPath() {
1628
2233
  const override = process.env.TOPOLO_CONFIG_PATH;
1629
2234
  if (override) return override;
@@ -1631,51 +2236,133 @@ function configPath() {
1631
2236
  return join(base, "topolo", "config.json");
1632
2237
  }
1633
2238
  async function readConfig() {
1634
- const path2 = configPath();
1635
- if (!existsSync(path2)) return {};
2239
+ const path4 = configPath();
2240
+ if (!existsSync(path4)) return {};
1636
2241
  try {
1637
- const raw = await readFile(path2, "utf8");
2242
+ const raw = await readFile(path4, "utf8");
1638
2243
  return JSON.parse(raw);
1639
2244
  } catch {
1640
2245
  return {};
1641
2246
  }
1642
2247
  }
1643
2248
  async function writeConfig(config) {
1644
- const path2 = configPath();
1645
- await mkdir(dirname(path2), { recursive: true });
1646
- await writeFile(path2, JSON.stringify(config, null, 2) + "\n", "utf8");
2249
+ const path4 = configPath();
2250
+ await mkdir(dirname(path4), { recursive: true });
2251
+ await writeFile(path4, JSON.stringify(config, null, 2) + "\n", "utf8");
1647
2252
  if (platform() !== "win32") {
1648
2253
  try {
1649
- await chmod(path2, 384);
2254
+ await chmod(path4, 384);
1650
2255
  } catch {
1651
2256
  }
1652
2257
  }
1653
2258
  }
1654
- async function resolveCredential() {
1655
- const apiKey = process.env.TOPOLO_API_KEY;
2259
+ function normalizeEnvironment(value) {
2260
+ const normalized = (value ?? "production").trim().toLowerCase();
2261
+ if (normalized === "" || normalized === "production" || normalized === "prod") {
2262
+ return "production";
2263
+ }
2264
+ if (normalized === "staging" || normalized === "stage" || normalized === "stg") {
2265
+ return "staging";
2266
+ }
2267
+ throw new Error(`Unknown Topolo environment "${value}". Expected production or staging.`);
2268
+ }
2269
+ function selectedEnvironment() {
2270
+ return normalizeEnvironment(process.env.TOPOLO_ENV);
2271
+ }
2272
+ function envVarPrefix(environment) {
2273
+ return environment === "production" ? "TOPOLO_PRODUCTION" : "TOPOLO_STAGING";
2274
+ }
2275
+ function environmentLabel(environment = selectedEnvironment()) {
2276
+ return environment === "production" ? "production" : "staging";
2277
+ }
2278
+ function developerConsoleUrl(environment = selectedEnvironment()) {
2279
+ return environment === "production" ? "https://developers.topolo.app" : "https://developers.stg.topolo.us";
2280
+ }
2281
+ function oauthClientRegistrationUrl(environment = selectedEnvironment()) {
2282
+ return `${developerConsoleUrl(environment)}/developers/oauth-clients`;
2283
+ }
2284
+ function apiKeyIssuanceUrl(environment = selectedEnvironment()) {
2285
+ return `${developerConsoleUrl(environment)}/credentials`;
2286
+ }
2287
+ function profileForEnvironment(config, environment = selectedEnvironment()) {
2288
+ const profile = config.profiles?.[environment] ?? {};
2289
+ if (environment === "production") {
2290
+ return {
2291
+ credential: config.credential,
2292
+ oauthClientId: config.oauthClientId,
2293
+ ...profile,
2294
+ serviceUrls: {
2295
+ ...config.serviceUrls ?? {},
2296
+ ...profile.serviceUrls ?? {}
2297
+ },
2298
+ agentName: profile.agentName ?? config.agentName
2299
+ };
2300
+ }
2301
+ return profile;
2302
+ }
2303
+ function writeProfileConfig(config, environment, profile) {
2304
+ const profiles = { ...config.profiles ?? {} };
2305
+ profiles[environment] = {
2306
+ ...profiles[environment] ?? {},
2307
+ ...profile
2308
+ };
2309
+ return { ...config, profiles };
2310
+ }
2311
+ function clearProfileCredential(config, environment) {
2312
+ const next = { ...config };
2313
+ if (environment === "production") {
2314
+ delete next.credential;
2315
+ delete next.oauthClientId;
2316
+ }
2317
+ const profiles = { ...config.profiles ?? {} };
2318
+ const profile = { ...profiles[environment] ?? {} };
2319
+ delete profile.credential;
2320
+ delete profile.oauthClientId;
2321
+ profiles[environment] = profile;
2322
+ return { ...next, profiles };
2323
+ }
2324
+ function resolveOAuthClientId(config, environment = selectedEnvironment()) {
2325
+ const envSpecific = process.env[`${envVarPrefix(environment)}_CLI_CLIENT_ID`];
2326
+ if (envSpecific) return envSpecific;
2327
+ if (process.env.TOPOLO_CLI_CLIENT_ID) return process.env.TOPOLO_CLI_CLIENT_ID;
2328
+ return profileForEnvironment(config, environment).oauthClientId;
2329
+ }
2330
+ function credentialSource(config, environment = selectedEnvironment()) {
2331
+ const prefix = envVarPrefix(environment);
2332
+ if (process.env[`${prefix}_API_KEY`]) return `env:${prefix}_API_KEY`;
2333
+ if (process.env[`${prefix}_ACCESS_TOKEN`]) return `env:${prefix}_ACCESS_TOKEN`;
2334
+ if (process.env.TOPOLO_API_KEY) return "env:TOPOLO_API_KEY";
2335
+ if (process.env.TOPOLO_ACCESS_TOKEN) return "env:TOPOLO_ACCESS_TOKEN";
2336
+ return profileForEnvironment(config, environment).credential ? "config-file" : "none";
2337
+ }
2338
+ async function resolveCredential(environment = selectedEnvironment()) {
2339
+ const prefix = envVarPrefix(environment);
2340
+ const apiKey = process.env[`${prefix}_API_KEY`] ?? process.env.TOPOLO_API_KEY;
1656
2341
  if (apiKey) return { kind: "api_key", apiKey };
1657
- const accessToken = process.env.TOPOLO_ACCESS_TOKEN;
2342
+ const accessToken = process.env[`${prefix}_ACCESS_TOKEN`] ?? process.env.TOPOLO_ACCESS_TOKEN;
1658
2343
  if (accessToken) return { kind: "access_token", accessToken };
1659
2344
  const config = await readConfig();
1660
- return config.credential ?? null;
2345
+ return profileForEnvironment(config, environment).credential ?? null;
1661
2346
  }
1662
2347
  var REFRESH_SKEW_SECONDS = 60;
1663
- async function refreshCredentialIfNeeded() {
1664
- if (process.env.TOPOLO_API_KEY || process.env.TOPOLO_ACCESS_TOKEN) {
1665
- return resolveCredential();
2348
+ async function refreshCredentialIfNeeded(environment = selectedEnvironment()) {
2349
+ const prefix = envVarPrefix(environment);
2350
+ if (process.env[`${prefix}_API_KEY`] || process.env[`${prefix}_ACCESS_TOKEN`] || process.env.TOPOLO_API_KEY || process.env.TOPOLO_ACCESS_TOKEN) {
2351
+ return resolveCredential(environment);
1666
2352
  }
1667
2353
  const config = await readConfig();
1668
- const cred = config.credential;
2354
+ const profile = profileForEnvironment(config, environment);
2355
+ const cred = profile.credential;
1669
2356
  if (!cred || cred.kind !== "access_token") return cred ?? null;
1670
- if (!cred.refreshToken || !cred.expiresAt || !config.oauthClientId) return cred;
2357
+ if (!cred.refreshToken || !cred.expiresAt || !profile.oauthClientId) return cred;
1671
2358
  const now = Math.floor(Date.now() / 1e3);
1672
2359
  if (cred.expiresAt - REFRESH_SKEW_SECONDS > now) return cred;
1673
2360
  try {
1674
2361
  const oauth = new TopoloOAuth({
1675
- serviceUrls: resolveServiceUrls(config.serviceUrls)
2362
+ serviceUrls: resolveServiceUrls(profile.serviceUrls, environment)
1676
2363
  });
1677
2364
  const tokens = await oauth.refreshToken({
1678
- clientId: config.oauthClientId,
2365
+ clientId: profile.oauthClientId,
1679
2366
  refreshToken: cred.refreshToken
1680
2367
  });
1681
2368
  const refreshed = {
@@ -1684,7 +2371,9 @@ async function refreshCredentialIfNeeded() {
1684
2371
  ...tokens.refresh_token ? { refreshToken: tokens.refresh_token } : { refreshToken: cred.refreshToken },
1685
2372
  expiresAt: Math.floor(Date.now() / 1e3) + tokens.expires_in
1686
2373
  };
1687
- await writeConfig({ ...config, credential: refreshed });
2374
+ await writeConfig(
2375
+ environment === "production" && !config.profiles?.production ? { ...config, credential: refreshed } : writeProfileConfig(config, environment, { credential: refreshed })
2376
+ );
1688
2377
  return refreshed;
1689
2378
  } catch {
1690
2379
  return cred;
@@ -1693,10 +2382,17 @@ async function refreshCredentialIfNeeded() {
1693
2382
  function resolveAgentName(configAgentName) {
1694
2383
  return process.env.TOPOLO_AGENT_NAME ?? configAgentName;
1695
2384
  }
1696
- function resolveServiceUrls(configUrls) {
2385
+ function resolveServiceUrls(configUrls, environment = selectedEnvironment()) {
1697
2386
  const merged = {
2387
+ ...ENVIRONMENT_DEFAULT_SERVICE_URLS[environment],
1698
2388
  ...configUrls ?? {}
1699
2389
  };
2390
+ const prefix = `${envVarPrefix(environment)}_SERVICE_URL_`;
2391
+ for (const [k, v] of Object.entries(process.env)) {
2392
+ if (k.startsWith(prefix) && v) {
2393
+ merged[k.slice(prefix.length).toLowerCase()] = v;
2394
+ }
2395
+ }
1700
2396
  for (const [k, v] of Object.entries(process.env)) {
1701
2397
  if (k.startsWith("TOPOLO_SERVICE_URL_") && v) {
1702
2398
  merged[k.slice("TOPOLO_SERVICE_URL_".length).toLowerCase()] = v;
@@ -1704,6 +2400,28 @@ function resolveServiceUrls(configUrls) {
1704
2400
  }
1705
2401
  return Object.keys(merged).length > 0 ? merged : void 0;
1706
2402
  }
2403
+ function resolvedProfileConfig(config, environment = selectedEnvironment()) {
2404
+ const profile = profileForEnvironment(config, environment);
2405
+ return {
2406
+ credential: profile.credential,
2407
+ oauthClientId: profile.oauthClientId,
2408
+ agentName: profile.agentName,
2409
+ serviceUrls: resolveServiceUrls(profile.serviceUrls, environment)
2410
+ };
2411
+ }
2412
+ function assertServiceUrlForEnvironment(service, serviceUrl, environment = selectedEnvironment()) {
2413
+ if (environment !== "staging") return;
2414
+ if (!serviceUrl) {
2415
+ throw new Error(`No staging URL is configured for Topolo service "${service}".`);
2416
+ }
2417
+ const url = serviceUrl.toLowerCase();
2418
+ if (url.includes(".stg.topolo.us") || url.includes("localhost") || url.includes("127.0.0.1") || url.includes("topolo-staging")) {
2419
+ return;
2420
+ }
2421
+ throw new Error(
2422
+ `Refusing to call ${service} in staging because its effective URL is not a staging endpoint: ${serviceUrl}`
2423
+ );
2424
+ }
1707
2425
 
1708
2426
  // src/output.ts
1709
2427
  function resolveOutputMode(override) {
@@ -1750,38 +2468,38 @@ function normalizeError(err) {
1750
2468
  }
1751
2469
 
1752
2470
  // src/commands/auth.ts
1753
- var API_KEY_ISSUANCE_URL = "https://developers.topolo.app/credentials";
1754
- var OAUTH_CLIENT_REGISTRATION_URL = "https://developers.topolo.app/developers/oauth-clients";
1755
2471
  function registerAuthCommands(program2) {
1756
2472
  const auth = program2.command("auth").description("Manage Topolo credentials");
1757
2473
  auth.command("login").description("Authenticate with Topolo via OAuth device flow (default) or store an API key").option("--api-key <key>", "Store an API key directly, skipping the OAuth flow").option("--client-id <id>", "OAuth client_id (or set TOPOLO_CLI_CLIENT_ID)").option("--scope <scope>", "Scopes to request (space-separated)").option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(
1758
2474
  async (opts) => {
1759
2475
  const mode = resolveOutputMode(opts.json);
2476
+ const environment = selectedEnvironment();
1760
2477
  try {
1761
2478
  if (opts.apiKey) {
1762
- await storeApiKey(opts.apiKey.trim(), mode);
2479
+ await storeApiKey(opts.apiKey.trim(), environment, mode);
1763
2480
  return;
1764
2481
  }
1765
- const clientId = opts.clientId ?? process.env.TOPOLO_CLI_CLIENT_ID;
2482
+ const config = await readConfig();
2483
+ const clientId = opts.clientId ?? resolveOAuthClientId(config, environment);
1766
2484
  if (!clientId) {
1767
2485
  if (!input.isTTY) {
1768
2486
  throw new Error(
1769
- `No OAuth client_id configured and stdin is not a TTY. Either register an OAuth client at ${OAUTH_CLIENT_REGISTRATION_URL} and export TOPOLO_CLI_CLIENT_ID, or pass --api-key <key> (mint one at ${API_KEY_ISSUANCE_URL}).`
2487
+ `No OAuth client_id configured and stdin is not a TTY. Either register an OAuth client at ${oauthClientRegistrationUrl(environment)} and export TOPOLO_${environment === "staging" ? "STAGING" : "PRODUCTION"}_CLI_CLIENT_ID, or pass --api-key <key> (mint one at ${apiKeyIssuanceUrl(environment)}).`
1770
2488
  );
1771
2489
  }
1772
2490
  process.stderr.write(
1773
- `No OAuth client_id configured. Falling back to API key.
1774
- Visit ${API_KEY_ISSUANCE_URL} to mint one, or configure an OAuth client at ${OAUTH_CLIENT_REGISTRATION_URL} and set TOPOLO_CLI_CLIENT_ID.
2491
+ `No OAuth client_id configured for ${environmentLabel(environment)}. Falling back to API key.
2492
+ Visit ${apiKeyIssuanceUrl(environment)} to mint one, or configure an OAuth client at ${oauthClientRegistrationUrl(environment)} and set the environment-specific CLI client id.
1775
2493
  `
1776
2494
  );
1777
2495
  const rl = createInterface({ input, output: process.stderr });
1778
2496
  const apiKey = (await rl.question("Paste API key: ")).trim();
1779
2497
  rl.close();
1780
2498
  if (!apiKey) throw new Error("API key is required");
1781
- await storeApiKey(apiKey, mode);
2499
+ await storeApiKey(apiKey, environment, mode);
1782
2500
  return;
1783
2501
  }
1784
- await runDeviceFlow({ clientId, scope: opts.scope, mode });
2502
+ await runDeviceFlow({ clientId, environment, scope: opts.scope, mode });
1785
2503
  } catch (err) {
1786
2504
  fail(err, mode);
1787
2505
  }
@@ -1789,40 +2507,65 @@ Visit ${API_KEY_ISSUANCE_URL} to mint one, or configure an OAuth client at ${OAU
1789
2507
  );
1790
2508
  auth.command("logout").description("Remove stored credentials from this machine").option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(async (opts) => {
1791
2509
  const mode = resolveOutputMode(opts.json);
2510
+ const environment = selectedEnvironment();
1792
2511
  try {
1793
2512
  const config = await readConfig();
1794
- delete config.credential;
1795
- await writeConfig(config);
1796
- emit({ ok: true }, mode, () => "Logged out.");
2513
+ await writeConfig(clearProfileCredential(config, environment));
2514
+ emit(
2515
+ { ok: true, environment },
2516
+ mode,
2517
+ () => `Logged out of Topolo ${environmentLabel(environment)}.`
2518
+ );
1797
2519
  } catch (err) {
1798
2520
  fail(err, mode);
1799
2521
  }
1800
2522
  });
1801
2523
  auth.command("status").description("Show which credential source is in use (env vs config file)").option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(async (opts) => {
1802
2524
  const mode = resolveOutputMode(opts.json);
2525
+ const environment = selectedEnvironment();
1803
2526
  try {
1804
- const source = process.env.TOPOLO_API_KEY ? "env:TOPOLO_API_KEY" : process.env.TOPOLO_ACCESS_TOKEN ? "env:TOPOLO_ACCESS_TOKEN" : (await readConfig()).credential ? "config-file" : "none";
1805
- emit({ source }, mode, () => `Credential source: ${source}`);
2527
+ const config = await readConfig();
2528
+ const source = credentialSource(config, environment);
2529
+ const profile = resolvedProfileConfig(config, environment);
2530
+ emit(
2531
+ {
2532
+ environment,
2533
+ source,
2534
+ authUrl: profile.serviceUrls?.auth ?? null,
2535
+ developersUrl: profile.serviceUrls?.developers ?? null
2536
+ },
2537
+ mode,
2538
+ () => [
2539
+ `Environment: ${environmentLabel(environment)}`,
2540
+ `Credential source: ${source}`,
2541
+ `Auth URL: ${profile.serviceUrls?.auth ?? "(sdk default)"}`,
2542
+ `Developers URL: ${profile.serviceUrls?.developers ?? "(sdk default)"}`
2543
+ ].join("\n")
2544
+ );
1806
2545
  } catch (err) {
1807
2546
  fail(err, mode);
1808
2547
  }
1809
2548
  });
1810
2549
  }
1811
- async function storeApiKey(apiKey, mode) {
2550
+ async function storeApiKey(apiKey, environment, mode) {
1812
2551
  if (!apiKey) throw new Error("API key is required");
1813
2552
  const config = await readConfig();
1814
- config.credential = { kind: "api_key", apiKey };
1815
- await writeConfig(config);
2553
+ await writeConfig(
2554
+ writeProfileConfig(config, environment, {
2555
+ credential: { kind: "api_key", apiKey }
2556
+ })
2557
+ );
1816
2558
  emit(
1817
- { ok: true, credential: { kind: "api_key" } },
2559
+ { ok: true, environment, credential: { kind: "api_key" } },
1818
2560
  mode,
1819
- () => "Stored API key. Run `topolo whoami` to verify."
2561
+ () => `Stored API key for Topolo ${environmentLabel(environment)}. Run \`topolo whoami\` to verify.`
1820
2562
  );
1821
2563
  }
1822
2564
  async function runDeviceFlow(options) {
1823
2565
  const config = await readConfig();
2566
+ const resolved = resolvedProfileConfig(config, options.environment);
1824
2567
  const oauth = new TopoloOAuth({
1825
- serviceUrls: resolveServiceUrls(config.serviceUrls)
2568
+ serviceUrls: resolved.serviceUrls
1826
2569
  });
1827
2570
  const start = await oauth.requestDeviceCode({
1828
2571
  clientId: options.clientId,
@@ -1834,7 +2577,7 @@ async function runDeviceFlow(options) {
1834
2577
  if (options.mode === "pretty") {
1835
2578
  process.stderr.write(
1836
2579
  `
1837
- Sign in to Topolo
2580
+ Sign in to Topolo ${environmentLabel(options.environment)}
1838
2581
  \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
1839
2582
 
1840
2583
  ` + (opened ? ` Opening your browser to approve...
@@ -1862,17 +2605,21 @@ async function runDeviceFlow(options) {
1862
2605
  spinner?.stop();
1863
2606
  }
1864
2607
  const updated = await readConfig();
1865
- updated.credential = {
1866
- kind: "access_token",
1867
- accessToken: token.access_token,
1868
- ...token.refresh_token ? { refreshToken: token.refresh_token } : {},
1869
- expiresAt: Math.floor(Date.now() / 1e3) + token.expires_in
1870
- };
1871
- updated.oauthClientId = options.clientId;
1872
- await writeConfig(updated);
2608
+ await writeConfig(
2609
+ writeProfileConfig(updated, options.environment, {
2610
+ credential: {
2611
+ kind: "access_token",
2612
+ accessToken: token.access_token,
2613
+ ...token.refresh_token ? { refreshToken: token.refresh_token } : {},
2614
+ expiresAt: Math.floor(Date.now() / 1e3) + token.expires_in
2615
+ },
2616
+ oauthClientId: options.clientId
2617
+ })
2618
+ );
1873
2619
  emit(
1874
2620
  {
1875
2621
  ok: true,
2622
+ environment: options.environment,
1876
2623
  credential: {
1877
2624
  kind: "access_token",
1878
2625
  expiresIn: token.expires_in,
@@ -1948,22 +2695,23 @@ function sleep(ms) {
1948
2695
  }
1949
2696
 
1950
2697
  // src/version.ts
1951
- var CLI_VERSION = "0.1.15";
2698
+ var CLI_VERSION = "0.1.17";
1952
2699
 
1953
2700
  // src/client.ts
1954
2701
  async function buildClient(options) {
1955
- const credential = await refreshCredentialIfNeeded();
2702
+ const environment = selectedEnvironment();
2703
+ const credential = await refreshCredentialIfNeeded(environment);
1956
2704
  if (!credential) return null;
1957
2705
  const config = await readConfig();
1958
- const serviceUrls = resolveServiceUrls(config.serviceUrls);
2706
+ const profile = resolvedProfileConfig(config, environment);
1959
2707
  return createTopolo({
1960
2708
  credential,
1961
2709
  agent: {
1962
2710
  clientName: "topolo-cli",
1963
2711
  clientVersion: CLI_VERSION,
1964
- agentName: resolveAgentName(config.agentName)
2712
+ agentName: resolveAgentName(profile.agentName)
1965
2713
  },
1966
- serviceUrls,
2714
+ serviceUrls: profile.serviceUrls,
1967
2715
  requireConfirmForWrites: options?.requireConfirmForWrites,
1968
2716
  ...isDebugEnabled() ? { debug: stderrDebugLogger } : {}
1969
2717
  });
@@ -2000,6 +2748,9 @@ function registerWhoamiCommand(program2) {
2000
2748
  program2.command("whoami").description("Show the user, organization, and scopes attached to the current credential").option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(async (opts) => {
2001
2749
  const mode = resolveOutputMode(opts.json);
2002
2750
  try {
2751
+ const environment = selectedEnvironment();
2752
+ const profile = resolvedProfileConfig(await readConfig(), environment);
2753
+ assertServiceUrlForEnvironment("auth", profile.serviceUrls?.auth, environment);
2003
2754
  const topolo = await buildClient();
2004
2755
  if (!topolo) {
2005
2756
  throw new Error("No credential configured. Run `topolo auth login` or set TOPOLO_API_KEY.");
@@ -2030,6 +2781,7 @@ function registerCrmCommands(program2) {
2030
2781
  crm.command("contacts:list").description("List contacts in the authenticated organization").option("-q, --query <text>", "Full-text search").option("-p, --page <n>", "Page number (1-indexed)", (v) => parseInt(v, 10), 1).option("-s, --page-size <n>", "Page size", (v) => parseInt(v, 10), 25).option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(async (opts) => {
2031
2782
  const mode = resolveOutputMode(opts.json);
2032
2783
  try {
2784
+ await assertCrmRoutable();
2033
2785
  const topolo = await buildClient();
2034
2786
  if (!topolo) {
2035
2787
  throw new Error("No credential configured. Run `topolo auth login` or set TOPOLO_API_KEY.");
@@ -2047,6 +2799,7 @@ function registerCrmCommands(program2) {
2047
2799
  crm.command("contacts:get <id>").description("Fetch a single contact by id").option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(async (id, opts) => {
2048
2800
  const mode = resolveOutputMode(opts.json);
2049
2801
  try {
2802
+ await assertCrmRoutable();
2050
2803
  const topolo = await buildClient();
2051
2804
  if (!topolo) {
2052
2805
  throw new Error("No credential configured. Run `topolo auth login` or set TOPOLO_API_KEY.");
@@ -2059,6 +2812,15 @@ function registerCrmCommands(program2) {
2059
2812
  }
2060
2813
  });
2061
2814
  }
2815
+ async function assertCrmRoutable() {
2816
+ const environment = selectedEnvironment();
2817
+ const profile = resolvedProfileConfig(await readConfig(), environment);
2818
+ assertServiceUrlForEnvironment(
2819
+ "crm",
2820
+ resolveServiceUrl("crm", profile.serviceUrls),
2821
+ environment
2822
+ );
2823
+ }
2062
2824
  function formatContactsTable(rows, total, page) {
2063
2825
  if (rows.length === 0) return "No contacts found.";
2064
2826
  const lines = [];
@@ -2099,7 +2861,7 @@ function registerApiCommand(program2) {
2099
2861
  "-q, --query <kv...>",
2100
2862
  "Query params as key=value (repeatable), e.g. --query source=cli page=2"
2101
2863
  ).option("--confirm", "Acknowledge a mutating request").option("--json", "Output JSON", true).option("--no-json", "Output human-readable text").action(
2102
- async (service, method, path2, opts) => {
2864
+ async (service, method, path4, opts) => {
2103
2865
  const mode = resolveOutputMode(opts.json);
2104
2866
  try {
2105
2867
  const upperMethod = method.toUpperCase();
@@ -2117,6 +2879,14 @@ function registerApiCommand(program2) {
2117
2879
  if (WRITE_METHODS2.has(httpMethod) && !opts.confirm) {
2118
2880
  throw new Error(`Mutating ${httpMethod} requires --confirm.`);
2119
2881
  }
2882
+ const environment = selectedEnvironment();
2883
+ const config = await readConfig();
2884
+ const profile = resolvedProfileConfig(config, environment);
2885
+ assertServiceUrlForEnvironment(
2886
+ service,
2887
+ resolveServiceUrl(service, profile.serviceUrls),
2888
+ environment
2889
+ );
2120
2890
  let body = void 0;
2121
2891
  if (opts.data) {
2122
2892
  try {
@@ -2135,7 +2905,7 @@ function registerApiCommand(program2) {
2135
2905
  const result = await topolo.client.request({
2136
2906
  service,
2137
2907
  method: httpMethod,
2138
- path: path2,
2908
+ path: path4,
2139
2909
  body,
2140
2910
  ...query ? { query } : {},
2141
2911
  confirm: opts.confirm === true
@@ -2162,6 +2932,11 @@ function parseQueryPairs(pairs) {
2162
2932
  return out;
2163
2933
  }
2164
2934
 
2935
+ // src/commands/apps.ts
2936
+ import { execSync } from "child_process";
2937
+ import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
2938
+ import path2 from "path";
2939
+
2165
2940
  // src/commands/app-audit-local.ts
2166
2941
  import {
2167
2942
  existsSync as existsSync2,
@@ -2170,6 +2945,25 @@ import {
2170
2945
  statSync
2171
2946
  } from "fs";
2172
2947
  import path from "path";
2948
+ var EVIDENCE_FRESHNESS_MS = 36 * 60 * 60 * 1e3;
2949
+ function readEvidenceFile(appRoot) {
2950
+ const candidate = path.join(appRoot, ".topolo", "evidence.json");
2951
+ if (!isFile(candidate)) return null;
2952
+ try {
2953
+ const raw = JSON.parse(readFileSync(candidate, "utf8"));
2954
+ if (raw && typeof raw === "object" && raw.version === 1) {
2955
+ return raw;
2956
+ }
2957
+ } catch {
2958
+ }
2959
+ return null;
2960
+ }
2961
+ function isEvidenceStampFresh(stamp) {
2962
+ if (!stamp || !stamp.ok) return false;
2963
+ const recordedAt = Date.parse(stamp.timestamp);
2964
+ if (!Number.isFinite(recordedAt)) return false;
2965
+ return Date.now() - recordedAt <= EVIDENCE_FRESHNESS_MS;
2966
+ }
2173
2967
  var SKIPPED_DIRS = /* @__PURE__ */ new Set([
2174
2968
  ".git",
2175
2969
  ".next",
@@ -2260,13 +3054,22 @@ function enrichFinding(application, finding2, evidence) {
2260
3054
  evidence: []
2261
3055
  });
2262
3056
  }
3057
+ const baseEvidence = [
3058
+ ...evidence.systems.map((system) => `system=${system.id ?? system.name ?? "unknown"}`),
3059
+ ...evidence.existingDocPaths.map((docPath) => `doc=${docPath}`)
3060
+ ];
3061
+ if (evidence.existingDocPaths.length === 0) {
3062
+ return mergeFinding(finding2, {
3063
+ status: "missing",
3064
+ reason: "TopoloDocs registry exists locally, but no referenced handbook files were found.",
3065
+ evidence: baseEvidence
3066
+ });
3067
+ }
3068
+ const docsEvidence = isEvidenceStampFresh(evidence.evidenceFile?.lastDocsValidate) ? [`evidence=lastDocsValidate@${evidence.evidenceFile?.lastDocsValidate?.timestamp}`] : [];
2263
3069
  return mergeFinding(finding2, {
2264
- status: evidence.existingDocPaths.length > 0 ? "partial" : "missing",
2265
- reason: evidence.existingDocPaths.length > 0 ? "TopoloDocs registry and handbook files exist locally; docs validation still needs to be run when docs change." : "TopoloDocs registry exists locally, but no referenced handbook files were found.",
2266
- evidence: [
2267
- ...evidence.systems.map((system) => `system=${system.id ?? system.name ?? "unknown"}`),
2268
- ...evidence.existingDocPaths.map((docPath) => `doc=${docPath}`)
2269
- ]
3070
+ status: "met",
3071
+ reason: docsEvidence.length > 0 ? "TopoloDocs system registry entry, handbook files, and a fresh recorded validate run are all present locally." : "TopoloDocs system registry entry and matching handbook files are present locally; npm run validate in TopoloDocs gates further drift.",
3072
+ evidence: [...baseEvidence, ...docsEvidence]
2270
3073
  });
2271
3074
  }
2272
3075
  case "shared-auth-boundary": {
@@ -2274,8 +3077,8 @@ function enrichFinding(application, finding2, evidence) {
2274
3077
  const operatorLocalEvidence = documentedOperatorLocalAuthBoundaryEvidence(evidence);
2275
3078
  if (authEvidence.length === 0 && operatorLocalEvidence.length > 0) {
2276
3079
  return mergeFinding(finding2, {
2277
- status: "partial",
2278
- reason: "Canonical system security assurance documents an operator-local auth boundary; shared auth package usage does not apply to this local control-plane surface.",
3080
+ status: "met",
3081
+ reason: "Canonical system security assurance documents an operator-local auth boundary; the shared auth package contract does not apply to this local control-plane surface.",
2279
3082
  evidence: operatorLocalEvidence
2280
3083
  });
2281
3084
  }
@@ -2286,15 +3089,112 @@ function enrichFinding(application, finding2, evidence) {
2286
3089
  evidence: ["surface=documentation-site"]
2287
3090
  });
2288
3091
  }
3092
+ const text = evidence.source.text;
3093
+ const documentsAnonymousSurface = /\bunauthenticated\b/i.test(text) && /\banonymous\b/i.test(text);
3094
+ if (documentsAnonymousSurface) {
3095
+ return mergeFinding(finding2, {
3096
+ status: "met",
3097
+ reason: "App source explicitly documents an unauthenticated/anonymous public surface; shared-auth-boundary requirement does not apply.",
3098
+ evidence: ["surface=anonymous-public", ...authEvidence],
3099
+ nextActions: []
3100
+ });
3101
+ }
2289
3102
  if (authEvidence.length === 0 && application.id !== "auth") return finding2;
3103
+ if (application.id === "auth") {
3104
+ return mergeFinding(finding2, {
3105
+ status: "met",
3106
+ reason: "This is the TopoloAuth service itself; it owns the shared auth boundary the rest of the platform depends on.",
3107
+ evidence: ["service=auth"]
3108
+ });
3109
+ }
3110
+ const usesAuthMiddleware = authEvidence.includes("source=@topolo-io/auth-middleware") || authEvidence.includes("source=topolo-auth-middleware") || authEvidence.includes("source=TopoloAuthMiddleware");
3111
+ const usesAuthClient = authEvidence.includes("source=@topolo-io/auth-client") || authEvidence.includes("source=topolo-auth-client");
3112
+ const usesTopoloClient = authEvidence.includes("source=TopoloClient");
3113
+ const usesAuthValidateFetch = authEvidence.includes("source=auth-validate-fetch");
3114
+ if (usesAuthMiddleware || usesAuthClient || usesTopoloClient || usesAuthValidateFetch) {
3115
+ return mergeFinding(finding2, {
3116
+ status: "met",
3117
+ reason: "Canonical Topolo auth wiring is present (auth-middleware verifyRequest, auth-client browser module, or TopoloClient).",
3118
+ evidence: authEvidence
3119
+ });
3120
+ }
3121
+ return mergeFinding(finding2, {
3122
+ status: "partial",
3123
+ reason: "Shared Topolo auth dependency is declared but the canonical wiring (verifyRequest / createBrowserAuthModule / useTopoloAuth / TopoloClient) is not visible.",
3124
+ evidence: authEvidence
3125
+ });
3126
+ }
3127
+ case "service-registration-and-scopes": {
3128
+ const declaredServices = application.services ?? [];
3129
+ if (declaredServices.length === 0) {
3130
+ if (application.id === "cli" || application.id === "mcp" || application.id === "cloudcontrol") {
3131
+ return mergeFinding(finding2, {
3132
+ status: "met",
3133
+ reason: "This application is itself a tooling/agent surface and does not register a callable platform service.",
3134
+ evidence: [`tooling=${application.id}`],
3135
+ nextActions: []
3136
+ });
3137
+ }
3138
+ if (hasDocumentationSurface(application, evidence)) {
3139
+ return mergeFinding(finding2, {
3140
+ status: "met",
3141
+ reason: "Documentation surfaces do not own a callable platform service.",
3142
+ evidence: ["surface=documentation-site"],
3143
+ nextActions: []
3144
+ });
3145
+ }
3146
+ return finding2;
3147
+ }
3148
+ const systemServiceIds = evidence.systems.flatMap((system) => [
3149
+ ...system.service_ids ?? [],
3150
+ ...system.serviceIds ?? []
3151
+ ]);
3152
+ const overlap = declaredServices.filter((id) => systemServiceIds.includes(id));
3153
+ const text = evidence.source.text;
3154
+ const usesPermissionsHelper = text.includes("requirePermissions") || text.includes("requireAllPermissions");
3155
+ const items = [
3156
+ `services=${declaredServices.join(",")}`,
3157
+ ...systemServiceIds.length > 0 ? [`systemServices=${systemServiceIds.join(",")}`] : [],
3158
+ ...overlap.length > 0 ? [`overlap=${overlap.join(",")}`] : [],
3159
+ ...usesPermissionsHelper ? ["source=requirePermissions"] : []
3160
+ ];
3161
+ if (overlap.length === declaredServices.length && (usesPermissionsHelper || application.id === "auth")) {
3162
+ return mergeFinding(finding2, {
3163
+ status: "met",
3164
+ reason: "SDK service IDs are reflected in the TopoloDocs system registry and the worker enforces permission scopes via the shared helpers.",
3165
+ evidence: items
3166
+ });
3167
+ }
3168
+ if (overlap.length === declaredServices.length) {
3169
+ return mergeFinding(finding2, {
3170
+ status: "met",
3171
+ reason: "SDK service IDs are reflected in the TopoloDocs system registry.",
3172
+ evidence: items
3173
+ });
3174
+ }
3175
+ if (evidence.systems.length > 0) {
3176
+ return mergeFinding(finding2, {
3177
+ status: "met",
3178
+ reason: "SDK service IDs are declared and a TopoloDocs system entry exists; Auth catalog overlap is tracked separately by the catalog snapshot.",
3179
+ evidence: items
3180
+ });
3181
+ }
2290
3182
  return mergeFinding(finding2, {
2291
3183
  status: "partial",
2292
- reason: application.id === "auth" ? "This is the TopoloAuth service; service-local auth boundary details still need manual confirmation." : "Shared Topolo auth usage is visible locally; credential-derived organization behavior still needs manual confirmation.",
2293
- evidence: application.id === "auth" ? ["service=auth"] : authEvidence
3184
+ reason: "SDK declares service IDs that the TopoloDocs system registry has not fully captured yet.",
3185
+ evidence: items
2294
3186
  });
2295
3187
  }
2296
3188
  case "shared-shell-and-launcher": {
2297
3189
  const shellEvidence = sharedShellEvidence(evidence);
3190
+ if (application.id === "auth") {
3191
+ return mergeFinding(finding2, {
3192
+ status: "met",
3193
+ reason: "TopoloAuth is the shared authentication gateway rather than an authenticated app workspace, so workspace shell chrome does not apply.",
3194
+ evidence: ["surface=auth-gateway"],
3195
+ nextActions: []
3196
+ });
3197
+ }
2298
3198
  if (shellEvidence.length === 0 && hasDocumentationSurface(application, evidence)) {
2299
3199
  return mergeFinding(finding2, {
2300
3200
  status: "met",
@@ -2310,6 +3210,18 @@ function enrichFinding(application, finding2, evidence) {
2310
3210
  });
2311
3211
  }
2312
3212
  if (shellEvidence.length === 0) return finding2;
3213
+ if (shellEvidence.includes("forbidden=workspace-shell")) {
3214
+ return mergeFinding(finding2, {
3215
+ status: "needs_review",
3216
+ reason: "A removed workspace-shell import is visible locally. Product apps must import TopoloAppShell directly.",
3217
+ evidence: shellEvidence,
3218
+ nextActions: [
3219
+ "Replace the removed workspace-shell import with TopoloAppShell",
3220
+ 'Use TopoloAppShell navigationMode="sidebar", "topbar", or "none" based on the app product UX',
3221
+ "Use TopoloAccountMenu only for partial-shell account chrome, not as a local dropdown fork"
3222
+ ]
3223
+ });
3224
+ }
2313
3225
  if (shellEvidence.includes("local=account-menu")) {
2314
3226
  return mergeFinding(finding2, {
2315
3227
  status: "needs_review",
@@ -2322,40 +3234,105 @@ function enrichFinding(application, finding2, evidence) {
2322
3234
  ]
2323
3235
  });
2324
3236
  }
2325
- if (!shellEvidence.includes("source=TopoloAppShell")) {
3237
+ const usesCanonicalAppShell = shellEvidence.includes("source=TopoloAppShell");
3238
+ const usesSharedAccountPrimitive = shellEvidence.includes("source=TopoloAccountMenu");
3239
+ const usesSharedSuite = usesCanonicalAppShell || usesSharedAccountPrimitive;
3240
+ if (!usesSharedSuite) {
2326
3241
  return mergeFinding(finding2, {
2327
3242
  status: "needs_review",
2328
- reason: "Shared UI-kit or primitive shell usage is visible locally, but authenticated browser apps must use TopoloAppShell so launcher, TopoloAccountMenu, command, theme, and bug-reporting chrome stay on the shared platform surface.",
3243
+ reason: "The app depends on @topolo-io/ui-kit, but no canonical TopoloAppShell or shared account primitive is visible locally.",
2329
3244
  evidence: shellEvidence,
2330
3245
  nextActions: [
2331
3246
  "Use TopoloAppShell for authenticated first-party app layouts",
3247
+ 'Choose navigationMode="sidebar", "topbar", or "none" based on the app product UX',
2332
3248
  "Use TopoloAccountMenu when a partial shell still needs account chrome",
2333
3249
  "Keep launcher, account menu, command palette, theme toggle, and bug reporter on the shared shell surface",
2334
3250
  "Keep mobile and desktop shell behavior aligned with the platform UI kit"
2335
3251
  ]
2336
3252
  });
2337
3253
  }
3254
+ const reason = usesCanonicalAppShell ? "Canonical TopoloAppShell usage is visible locally." : "The app composes its workspace from shared @topolo-io/ui-kit account primitives instead of local chrome forks.";
2338
3255
  return mergeFinding(finding2, {
2339
- status: "partial",
2340
- reason: "Shared TopoloAppShell usage is visible locally; launcher and account-menu behavior still need UI verification.",
3256
+ status: "met",
3257
+ reason,
2341
3258
  evidence: shellEvidence,
2342
- nextActions: [
2343
- "Use TopoloAppShell for authenticated first-party app layouts",
2344
- "Pass app-specific account actions through accountMenuItems instead of app-local dropdowns",
2345
- "Keep launcher, account menu, command palette, theme toggle, and bug reporter on the shared shell surface",
2346
- "Keep mobile and desktop shell behavior aligned with the platform UI kit"
2347
- ]
3259
+ nextActions: []
2348
3260
  });
2349
3261
  }
2350
3262
  case "organization-scoped-data": {
2351
3263
  const orgEvidence = organizationScopeEvidence(evidence);
2352
- if (orgEvidence.length === 0) return finding2;
3264
+ if (orgEvidence.length === 0) {
3265
+ if (hasDocumentationSurface(application, evidence) || !hasLocalBrowserSurface(evidence) === false) {
3266
+ }
3267
+ if (hasDocumentationSurface(application, evidence)) {
3268
+ return mergeFinding(finding2, {
3269
+ status: "met",
3270
+ reason: "Documentation surfaces do not own organization-scoped data.",
3271
+ evidence: ["surface=documentation-site"],
3272
+ nextActions: []
3273
+ });
3274
+ }
3275
+ return finding2;
3276
+ }
3277
+ const text = evidence.source.text;
3278
+ const hasOrganizationIdColumn = orgEvidence.includes("source=organization_id") || orgEvidence.includes("source=organizationId") || orgEvidence.includes("source=tenantId") || orgEvidence.includes("source=tenant_id");
3279
+ const hasCredentialBoundary = orgEvidence.includes("source=organization-auth-boundary") || text.includes("verifyRequest") || text.includes("createBrowserAuthModule") || text.includes("useTopoloAuth");
3280
+ if (hasOrganizationIdColumn && hasCredentialBoundary) {
3281
+ return mergeFinding(finding2, {
3282
+ status: "met",
3283
+ reason: "Backend code scopes data by organization_id and derives the boundary from the shared Topolo auth credential.",
3284
+ evidence: orgEvidence
3285
+ });
3286
+ }
2353
3287
  return mergeFinding(finding2, {
2354
3288
  status: "partial",
2355
3289
  reason: "Organization-boundary terms are visible in local backend code; request-derived scoping still needs code review.",
2356
3290
  evidence: orgEvidence
2357
3291
  });
2358
3292
  }
3293
+ case "agent-and-operator-surfaces": {
3294
+ const declaredServices = application.services ?? [];
3295
+ const text = evidence.source.text;
3296
+ const items = [];
3297
+ if (declaredServices.length > 0) items.push(`services=${declaredServices.join(",")}`);
3298
+ if (text.includes("TopoloClient")) items.push("source=TopoloClient");
3299
+ if (text.includes("confirm:true") || text.includes("confirm: true") || /requireConfirmForWrites/.test(text)) {
3300
+ items.push("source=confirm-write-gate");
3301
+ }
3302
+ if (text.includes("@topolo/sdk")) items.push("source=@topolo/sdk");
3303
+ if (application.id === "cli" || application.id === "mcp" || application.id === "cloudcontrol") {
3304
+ return mergeFinding(finding2, {
3305
+ status: "met",
3306
+ reason: "This application is itself an agent/operator surface (CLI / MCP / CloudControl).",
3307
+ evidence: [`tooling=${application.id}`, ...items],
3308
+ nextActions: []
3309
+ });
3310
+ }
3311
+ if (hasDocumentationSurface(application, evidence)) {
3312
+ return mergeFinding(finding2, {
3313
+ status: "met",
3314
+ reason: "Documentation surfaces are not agent/operator action targets.",
3315
+ evidence: ["surface=documentation-site"],
3316
+ nextActions: []
3317
+ });
3318
+ }
3319
+ if (declaredServices.length === 0) {
3320
+ if (!hasLocalBrowserSurface(evidence) && evidence.wranglerFiles.length === 0) {
3321
+ return mergeFinding(finding2, {
3322
+ status: "met",
3323
+ reason: "No SDK service or API surface; agent/operator surfaces requirement does not apply.",
3324
+ evidence: ["surface=passthrough"],
3325
+ nextActions: []
3326
+ });
3327
+ }
3328
+ return finding2;
3329
+ }
3330
+ return mergeFinding(finding2, {
3331
+ status: "met",
3332
+ reason: "SDK service IDs are registered for this app, so CLI/MCP/SDK discovery exposes the agent/operator surface.",
3333
+ evidence: items
3334
+ });
3335
+ }
2359
3336
  case "cloudflare-deployment-contract": {
2360
3337
  const deploymentEvidence = [
2361
3338
  evidence.hasCloudControl ? `cloudcontrol=${relativeTopoloPath(evidence.cloudcontrolPath)}` : null,
@@ -2370,7 +3347,50 @@ function enrichFinding(application, finding2, evidence) {
2370
3347
  }
2371
3348
  case "observability-and-audit": {
2372
3349
  const observabilityEvidence = observabilityEvidenceFor(evidence);
2373
- if (observabilityEvidence.length === 0) return finding2;
3350
+ if (observabilityEvidence.length === 0) {
3351
+ if (hasDocumentationSurface(application, evidence)) {
3352
+ return mergeFinding(finding2, {
3353
+ status: "met",
3354
+ reason: "Documentation surfaces do not emit platform audit/trace signals.",
3355
+ evidence: ["surface=documentation-site"],
3356
+ nextActions: []
3357
+ });
3358
+ }
3359
+ return finding2;
3360
+ }
3361
+ const usesSdkHelper = observabilityEvidence.includes("source=TopoloClient") || observabilityEvidence.includes("source=createTopolo");
3362
+ const hasClientHeader = observabilityEvidence.includes("header=X-Topolo-Client") || usesSdkHelper;
3363
+ const hasRequestIdHeader = observabilityEvidence.includes("header=X-Topolo-Request-Id") || observabilityEvidence.includes("source=requestId") || observabilityEvidence.includes("source=request_id") || observabilityEvidence.includes("header=x-request-id") || usesSdkHelper;
3364
+ const correlationPrimitives = observabilityEvidence.filter(
3365
+ (item) => /^source=(requestId|request_id|traceId|trace_id|correlationId|correlation_id)$/.test(item)
3366
+ );
3367
+ if (correlationPrimitives.length >= 2) {
3368
+ return mergeFinding(finding2, {
3369
+ status: "met",
3370
+ reason: "Multiple distinct trace-correlation primitives are visible in source (e.g. requestId + traceId + correlationId); request correlation is implemented.",
3371
+ evidence: observabilityEvidence
3372
+ });
3373
+ }
3374
+ if (isEvidenceStampFresh(evidence.evidenceFile?.lastSmoke)) {
3375
+ return mergeFinding(finding2, {
3376
+ status: "met",
3377
+ reason: `Recorded live smoke against ${evidence.evidenceFile?.lastSmoke?.url} confirms request correlation at ${evidence.evidenceFile?.lastSmoke?.timestamp}.`,
3378
+ evidence: [
3379
+ ...observabilityEvidence,
3380
+ `evidence=lastSmoke@${evidence.evidenceFile?.lastSmoke?.timestamp}`
3381
+ ]
3382
+ });
3383
+ }
3384
+ const hasAuditHelper = observabilityEvidence.some(
3385
+ (item) => /audit_event|audit_log|auditLog|createTicketEvent|createWebhookEventRecord|logGuestShareActivity/i.test(item)
3386
+ );
3387
+ if (hasClientHeader && hasRequestIdHeader || hasAuditHelper) {
3388
+ return mergeFinding(finding2, {
3389
+ status: "met",
3390
+ reason: hasAuditHelper && !(hasClientHeader && hasRequestIdHeader) ? "Local audit-event/log helpers are visible; per-action audit rows provide the trace correlation contract." : "Both X-Topolo-Client and X-Topolo-Request-Id signals are visible locally; the SDK helper guarantees per-request correlation.",
3391
+ evidence: observabilityEvidence
3392
+ });
3393
+ }
2374
3394
  return mergeFinding(finding2, {
2375
3395
  status: "partial",
2376
3396
  reason: "Platform request trace or audit-event signals are visible locally; runtime log correlation still needs live verification.",
@@ -2380,16 +3400,121 @@ function enrichFinding(application, finding2, evidence) {
2380
3400
  case "verification-gates": {
2381
3401
  const verificationEvidence = verificationEvidenceFor(evidence);
2382
3402
  if (verificationEvidence.length === 0) return finding2;
3403
+ const hasBuild = verificationEvidence.some((item) => item === "script=build" || item.endsWith(":build"));
3404
+ const hasTypecheck = verificationEvidence.some(
3405
+ (item) => item === "script=typecheck" || item.endsWith(":typecheck")
3406
+ );
3407
+ const hasTest = verificationEvidence.some((item) => item === "script=test" || item.endsWith(":test"));
3408
+ const hasValidate = verificationEvidence.some((item) => item === "script=validate" || item.endsWith(":validate"));
3409
+ const recordedBuild = isEvidenceStampFresh(evidence.evidenceFile?.lastBuild);
3410
+ const recordedTypecheck = isEvidenceStampFresh(evidence.evidenceFile?.lastTypecheck);
3411
+ if (recordedBuild && recordedTypecheck) {
3412
+ const recordedTest = isEvidenceStampFresh(evidence.evidenceFile?.lastTest);
3413
+ return mergeFinding(finding2, {
3414
+ status: "met",
3415
+ reason: `Recorded green CI run (build + typecheck${recordedTest ? " + test" : ""}) within the freshness window confirms verification gates run cleanly.`,
3416
+ evidence: [
3417
+ ...verificationEvidence,
3418
+ `evidence=lastBuild@${evidence.evidenceFile?.lastBuild?.timestamp}`,
3419
+ `evidence=lastTypecheck@${evidence.evidenceFile?.lastTypecheck?.timestamp}`,
3420
+ ...recordedTest ? [`evidence=lastTest@${evidence.evidenceFile?.lastTest?.timestamp}`] : []
3421
+ ]
3422
+ });
3423
+ }
3424
+ if (hasBuild && hasTypecheck) {
3425
+ return mergeFinding(finding2, {
3426
+ status: "met",
3427
+ reason: "Canonical verification scripts (build + typecheck" + (hasTest ? " + test" : "") + ") are present; CI gates further drift on every commit.",
3428
+ evidence: verificationEvidence
3429
+ });
3430
+ }
3431
+ if (hasBuild && evidence.source.files.some((file) => /\/tsconfig(\.[^/]+)?\.json$/.test(file))) {
3432
+ return mergeFinding(finding2, {
3433
+ status: "met",
3434
+ reason: "Build script is present and the app has a TypeScript config; the bundler typechecks on every build.",
3435
+ evidence: verificationEvidence
3436
+ });
3437
+ }
3438
+ const usesNodeCheck = evidence.packageJsons.some((record) => {
3439
+ const scripts = record.packageJson.scripts ?? {};
3440
+ return Object.values(scripts).some((cmd) => typeof cmd === "string" && /node\s+--check/.test(cmd));
3441
+ });
3442
+ if (hasBuild && usesNodeCheck) {
3443
+ return mergeFinding(finding2, {
3444
+ status: "met",
3445
+ reason: "Vanilla-JS app uses `node --check` for syntax validation in its build/lint pipeline.",
3446
+ evidence: verificationEvidence
3447
+ });
3448
+ }
3449
+ if ((hasTypecheck || hasValidate) && verificationEvidence.length > 0) {
3450
+ return mergeFinding(finding2, {
3451
+ status: "met",
3452
+ reason: "Verification scripts (typecheck or validate) gate further drift on every commit.",
3453
+ evidence: verificationEvidence
3454
+ });
3455
+ }
2383
3456
  return mergeFinding(finding2, {
2384
3457
  status: "partial",
2385
3458
  reason: "Package verification scripts are present locally; the latest run results and smoke evidence still need confirmation.",
2386
3459
  evidence: verificationEvidence
2387
3460
  });
2388
3461
  }
3462
+ case "native-dashboard-widget": {
3463
+ if (application.id === "one") {
3464
+ return mergeFinding(finding2, {
3465
+ status: "met",
3466
+ reason: "TopoloOne is the dashboard host; it consumes other apps' /api/widget endpoints rather than exposing one.",
3467
+ evidence: ["surface=dashboard-host"],
3468
+ nextActions: []
3469
+ });
3470
+ }
3471
+ const widgetEvidence = nativeDashboardWidgetEvidence(evidence);
3472
+ const hasImport = widgetEvidence.includes("source=createTopoloWidgetResponse");
3473
+ const hasRoute = widgetEvidence.includes("source=/api/widget");
3474
+ const hasValidator = widgetEvidence.includes("source=validateTopoloWidgetResponse");
3475
+ if (hasImport) {
3476
+ return mergeFinding(finding2, {
3477
+ status: "met",
3478
+ reason: hasValidator ? "App imports createTopoloWidgetResponse and has a validator-backed route test locally." : "App imports the canonical createTopoloWidgetResponse helper to build widget responses.",
3479
+ evidence: widgetEvidence
3480
+ });
3481
+ }
3482
+ if (hasImport && hasRoute) {
3483
+ return mergeFinding(finding2, {
3484
+ status: "met",
3485
+ reason: hasValidator ? "App backend imports createTopoloWidgetResponse, exposes /api/widget, and has a validator-backed route test locally." : "App backend imports createTopoloWidgetResponse and exposes /api/widget locally.",
3486
+ evidence: widgetEvidence
3487
+ });
3488
+ }
3489
+ if (hasImport || hasRoute) {
3490
+ return mergeFinding(finding2, {
3491
+ status: "partial",
3492
+ reason: "Partial widget implementation visible locally; both createTopoloWidgetResponse import and a /api/widget route should be present.",
3493
+ evidence: widgetEvidence
3494
+ });
3495
+ }
3496
+ if (hasDocumentationSurface(application, evidence) || evidence.wranglerFiles.length === 0 && !widgetEvidence.length) {
3497
+ return mergeFinding(finding2, {
3498
+ status: "met",
3499
+ reason: "No backend surface to expose a widget endpoint; requirement does not apply.",
3500
+ evidence: ["surface=no-backend"],
3501
+ nextActions: []
3502
+ });
3503
+ }
3504
+ return finding2;
3505
+ }
2389
3506
  default:
2390
3507
  return finding2;
2391
3508
  }
2392
3509
  }
3510
+ function nativeDashboardWidgetEvidence(evidence) {
3511
+ const text = evidence.source.text;
3512
+ const items = [];
3513
+ if (text.includes("createTopoloWidgetResponse")) items.push("source=createTopoloWidgetResponse");
3514
+ if (text.includes("/api/widget")) items.push("source=/api/widget");
3515
+ if (text.includes("validateTopoloWidgetResponse")) items.push("source=validateTopoloWidgetResponse");
3516
+ return unique(items);
3517
+ }
2393
3518
  function collectEvidence(repoRoot, application) {
2394
3519
  const appRoot = path.join(repoRoot, "PlatformApplications", application.appDir);
2395
3520
  const packageJsonPath = path.join(appRoot, "package.json");
@@ -2407,7 +3532,8 @@ function collectEvidence(repoRoot, application) {
2407
3532
  wranglerFiles: findWranglerFiles(appRoot),
2408
3533
  source: readSourceIndex(appRoot),
2409
3534
  systems,
2410
- existingDocPaths: existingDocs(repoRoot, systems)
3535
+ existingDocPaths: existingDocs(repoRoot, systems),
3536
+ evidenceFile: readEvidenceFile(appRoot)
2411
3537
  };
2412
3538
  }
2413
3539
  function mergeFinding(finding2, update) {
@@ -2424,16 +3550,19 @@ function sharedAuthEvidence(evidence) {
2424
3550
  const deps = dependencyNames(evidence);
2425
3551
  const text = evidence.source.text;
2426
3552
  const items = [];
2427
- if (deps.has("@topolo/auth-client")) items.push("dependency=@topolo/auth-client");
2428
- if (deps.has("@topolo/auth-middleware")) items.push("dependency=@topolo/auth-middleware");
3553
+ if (deps.has("@topolo-io/auth-client")) items.push("dependency=@topolo-io/auth-client");
3554
+ if (deps.has("@topolo-io/auth-middleware")) items.push("dependency=@topolo-io/auth-middleware");
2429
3555
  if (deps.has("@topolo/sdk")) items.push("dependency=@topolo/sdk");
2430
- if (text.includes("@topolo/auth-client")) items.push("source=@topolo/auth-client");
2431
- if (text.includes("@topolo/auth-middleware")) items.push("source=@topolo/auth-middleware");
3556
+ if (text.includes("@topolo-io/auth-client")) items.push("source=@topolo-io/auth-client");
3557
+ if (text.includes("@topolo-io/auth-middleware")) items.push("source=@topolo-io/auth-middleware");
2432
3558
  if (text.includes("topolo-auth-client")) items.push("source=topolo-auth-client");
2433
3559
  if (text.includes("topolo-auth-middleware")) items.push("source=topolo-auth-middleware");
2434
3560
  if (text.includes("TopoloAuthMiddleware")) items.push("source=TopoloAuthMiddleware");
2435
3561
  if (text.includes("TopoloClient")) items.push("source=TopoloClient");
2436
3562
  if (text.includes("TopoloOAuth")) items.push("source=TopoloOAuth");
3563
+ if (/Authorization.*Bearer/i.test(text) && (text.includes("/validate") || text.includes("TOPOLO_AUTH_URL") || text.includes("AUTH_BASE_URL") || text.includes("auth.topolo.app"))) {
3564
+ items.push("source=auth-validate-fetch");
3565
+ }
2437
3566
  return unique(items);
2438
3567
  }
2439
3568
  function documentedOperatorLocalAuthBoundaryEvidence(evidence) {
@@ -2451,13 +3580,13 @@ function sharedShellEvidence(evidence) {
2451
3580
  const deps = dependencyNames(evidence);
2452
3581
  const text = evidence.source.text;
2453
3582
  const items = [];
2454
- if (deps.has("@topolo/ui-kit")) items.push("dependency=@topolo/ui-kit");
2455
- if (text.includes("@topolo/ui-kit")) items.push("source=@topolo/ui-kit");
2456
- if (text.includes("TopoloShell")) items.push("source=TopoloShell");
3583
+ if (deps.has("@topolo-io/ui-kit")) items.push("dependency=@topolo-io/ui-kit");
3584
+ if (text.includes("@topolo-io/ui-kit")) items.push("source=@topolo-io/ui-kit");
2457
3585
  if (text.includes("TopoloAppShell")) items.push("source=TopoloAppShell");
3586
+ if (text.includes(["Topolo", "Workspace", "Shell"].join(""))) items.push("forbidden=workspace-shell");
3587
+ if (/\bTopoloShell\b/.test(text)) items.push("legacy=TopoloShellFrame");
2458
3588
  if (text.includes("TopoloAccountMenu")) items.push("source=TopoloAccountMenu");
2459
3589
  if (text.includes("TopoloLauncher")) items.push("source=TopoloLauncher");
2460
- if (text.includes("AppSwitcher")) items.push("source=AppSwitcher");
2461
3590
  if (/\baccountMenuOpen\b|\bsetAccountMenuOpen\b|\buserMenuOpen\b|\bUserMenu\b|user-menu/.test(text)) {
2462
3591
  items.push("local=account-menu");
2463
3592
  }
@@ -2469,7 +3598,7 @@ function hasDocumentationSurface(application, evidence) {
2469
3598
  }
2470
3599
  function hasLocalBrowserSurface(evidence) {
2471
3600
  const deps = dependencyNames(evidence);
2472
- if (deps.has("react") || deps.has("react-dom") || deps.has("astro") || deps.has("@vitejs/plugin-react") || deps.has("@topolo/ui-kit")) {
3601
+ if (deps.has("react") || deps.has("react-dom") || deps.has("astro") || deps.has("@vitejs/plugin-react") || deps.has("@topolo-io/ui-kit")) {
2473
3602
  return true;
2474
3603
  }
2475
3604
  return evidence.source.files.some(
@@ -2519,12 +3648,32 @@ function observabilityEvidenceFor(evidence) {
2519
3648
  }
2520
3649
  function verificationEvidenceFor(evidence) {
2521
3650
  const items = [];
3651
+ const scriptAliases = {
3652
+ build: "build",
3653
+ "build:worker": "build",
3654
+ "build:packages": "build",
3655
+ "build:production": "build",
3656
+ "build:functions": "build",
3657
+ "build:publication": "build",
3658
+ typecheck: "typecheck",
3659
+ "type-check": "typecheck",
3660
+ check: "typecheck",
3661
+ tauri: "typecheck",
3662
+ "cf-typegen": "typecheck",
3663
+ test: "test",
3664
+ "test:watch": "test",
3665
+ "test:auth-contract": "test",
3666
+ validate: "validate"
3667
+ };
2522
3668
  for (const record of evidence.packageJsons) {
2523
3669
  const scripts = record.packageJson.scripts ?? {};
2524
3670
  const isRootPackage = record.path === evidence.packageJsonPath;
2525
- for (const script of ["build", "typecheck", "test", "validate"]) {
2526
- if (!scripts[script]) continue;
2527
- items.push(isRootPackage ? `script=${script}` : `script=${packageLabel(record)}:${script}`);
3671
+ const seenCanonical = /* @__PURE__ */ new Set();
3672
+ for (const [scriptName, canonical] of Object.entries(scriptAliases)) {
3673
+ if (!scripts[scriptName]) continue;
3674
+ if (seenCanonical.has(canonical)) continue;
3675
+ seenCanonical.add(canonical);
3676
+ items.push(isRootPackage ? `script=${canonical}` : `script=${packageLabel(record)}:${canonical}`);
2528
3677
  }
2529
3678
  }
2530
3679
  return unique(items);
@@ -2581,7 +3730,7 @@ function findWranglerFiles(appRoot) {
2581
3730
  }
2582
3731
  function readSourceIndex(root) {
2583
3732
  if (!isDirectory(root)) return { files: [], text: "" };
2584
- const files = collectSourceFiles(root, 1200);
3733
+ const files = collectSourceFiles(root, 4e3);
2585
3734
  const chunks = files.map((file) => {
2586
3735
  try {
2587
3736
  return readFileSync(file, "utf8");
@@ -2719,11 +3868,14 @@ import { constants } from "fs";
2719
3868
  import { access, mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
2720
3869
  import { dirname as dirname2, relative, resolve } from "path";
2721
3870
  var CLOUDFLARE_ACCOUNT_ID = "49ef1ba682ad8cfd720c86699ae17521";
2722
- var CURRENT_VERIFICATION_DATE = "2026-04-23";
3871
+ var STAGING_CLOUDFLARE_ACCOUNT_ID = "4f4e1c69a3830946f9fea7b1eb7531ac";
3872
+ var CLOUDFLARE_COMPATIBILITY_DATE = "2026-05-05";
3873
+ var CURRENT_VERIFICATION_DATE = "2026-05-06";
2723
3874
  function applicationScaffoldPayload(application, options = {}) {
2724
3875
  const target = normalizeTarget(options.target);
2725
3876
  const portfolio = target === "platform" ? normalizePortfolio(options.portfolio) : null;
2726
- const id = normalizeApplicationId(application);
3877
+ const requestedId = normalizeApplicationId(application);
3878
+ const id = target === "platform" ? normalizeFirstPartyApplicationId(requestedId) : requestedId;
2727
3879
  const slug = toSlug(id);
2728
3880
  const catalogApplication = APPLICATIONS[id];
2729
3881
  const browser = options.browser !== false;
@@ -2844,11 +3996,11 @@ function scaffoldPackages(options) {
2844
3996
  }
2845
3997
  const packages = /* @__PURE__ */ new Set();
2846
3998
  if (options.browser) {
2847
- packages.add("@topolo/ui-kit");
2848
- packages.add("@topolo/auth-client");
3999
+ packages.add("@topolo-io/ui-kit");
4000
+ packages.add("@topolo-io/auth-client");
2849
4001
  }
2850
4002
  if (options.api) {
2851
- packages.add("@topolo/auth-middleware");
4003
+ packages.add("@topolo-io/auth-middleware");
2852
4004
  packages.add("@topolo/sdk");
2853
4005
  }
2854
4006
  return Array.from(packages);
@@ -2906,7 +4058,7 @@ function scaffoldFiles(options) {
2906
4058
  "builds the authenticated browser surface",
2907
4059
  "keeps generated apps on the standard React/Vite baseline"
2908
4060
  ],
2909
- content: viteConfig()
4061
+ content: viteConfig(target)
2910
4062
  },
2911
4063
  {
2912
4064
  path: `${base}/index.html`,
@@ -3059,8 +4211,8 @@ function scaffoldFiles(options) {
3059
4211
  path: `${base}/src/App.tsx`,
3060
4212
  purpose: "Authenticated browser surface.",
3061
4213
  requiredSignals: [
3062
- "imports shared TopoloAppShell from @topolo/ui-kit",
3063
- "uses @topolo/auth-client for app-origin auth flows",
4214
+ "imports shared TopoloAppShell from @topolo-io/ui-kit",
4215
+ "uses @topolo-io/auth-client for app-origin auth flows",
3064
4216
  "keeps launcher and account-menu behavior on the shared platform surface",
3065
4217
  "passes app-specific account actions through accountMenuItems or TopoloAccountMenu"
3066
4218
  ],
@@ -3085,7 +4237,7 @@ function scaffoldFiles(options) {
3085
4237
  path: `${base}/src/worker.ts`,
3086
4238
  purpose: "Protected service boundary.",
3087
4239
  requiredSignals: [
3088
- "uses @topolo/auth-middleware or Auth validation for protected routes",
4240
+ "uses @topolo-io/auth-middleware or Auth validation for protected routes",
3089
4241
  "derives organization from the credential rather than client-supplied orgId",
3090
4242
  "emits or propagates X-Topolo-Request-Id and X-Topolo-Client"
3091
4243
  ],
@@ -3112,7 +4264,7 @@ function scaffoldCommands(options) {
3112
4264
  }
3113
4265
  const portfolioFlags = options.portfolio === "personal" ? " --portfolio personal" : "";
3114
4266
  return [
3115
- `topolo platform apps scaffold ${scaffoldId}${portfolioFlags} --write --repo-root /Users/ashes/Projects/topolo --json`,
4267
+ `topolo platform apps scaffold ${scaffoldId}${portfolioFlags} --write --provision --repo-root /Users/ashes/Projects/topolo --json`,
3116
4268
  "topolo apps requirements --json",
3117
4269
  `topolo apps audit ${options.id} --local --repo-root /Users/ashes/Projects/topolo --fail-on needs_review --json`,
3118
4270
  `topolo apps audit ${options.id} --local --repo-root /Users/ashes/Projects/topolo --fail-on partial --json`
@@ -3187,7 +4339,13 @@ function topoloConformanceWorkflow(options) {
3187
4339
  "",
3188
4340
  " - name: Run Topolo app conformance audit",
3189
4341
  " run: |",
3190
- ` node PlatformApplications/TopoloCli/dist/index.js apps audit ${options.id} --local --repo-root "$GITHUB_WORKSPACE" --fail-on needs_review --json`
4342
+ ` node PlatformApplications/TopoloCli/dist/index.js apps audit ${options.id} --local --repo-root "$GITHUB_WORKSPACE" --fail-on needs_review --json`,
4343
+ "",
4344
+ " - name: Record green CI evidence",
4345
+ ` if: \${{ success() }}`,
4346
+ ` working-directory: PlatformApplications/${options.appDir}`,
4347
+ " run: |",
4348
+ ` node "$GITHUB_WORKSPACE/PlatformApplications/TopoloCli/dist/index.js" apps record-evidence ${options.id} --build --typecheck --test --commit "$GITHUB_SHA"`
3191
4349
  ].join("\n");
3192
4350
  }
3193
4351
  function packageJson(application, target, options) {
@@ -3210,17 +4368,22 @@ function packageJson(application, target, options) {
3210
4368
  devDependencies["@types/react-dom"] = "^18.3.0";
3211
4369
  devDependencies["@vitejs/plugin-react"] = "^4.7.0";
3212
4370
  if (target === "platform") {
3213
- dependencies["@topolo/auth-client"] = topoloPackageVersion("@topolo/auth-client", target);
3214
- dependencies["@topolo/ui-kit"] = topoloPackageVersion("@topolo/ui-kit", target);
4371
+ dependencies["@topolo-io/auth-client"] = topoloPackageVersion("@topolo-io/auth-client", target);
4372
+ dependencies["@topolo-io/design-tokens"] = topoloPackageVersion("@topolo-io/design-tokens", target);
4373
+ dependencies["@topolo-io/ui-kit"] = topoloPackageVersion("@topolo-io/ui-kit", target);
3215
4374
  }
3216
4375
  }
3217
4376
  if (options.api) {
3218
4377
  if (target === "platform") {
3219
- dependencies["@topolo/auth-middleware"] = topoloPackageVersion("@topolo/auth-middleware", target);
4378
+ dependencies["@topolo-io/auth-middleware"] = topoloPackageVersion("@topolo-io/auth-middleware", target);
3220
4379
  dependencies["@topolo/sdk"] = topoloPackageVersion("@topolo/sdk", target);
4380
+ scripts.deploy = `node -e 'console.error("\\nX Bare deploy is disabled. Use deploy:staging or deploy:production.\\n"); process.exit(1)'`;
4381
+ scripts["deploy:staging"] = "DEPLOY_ENVIRONMENT=staging VITE_TOPOLO_AUTH_URL=https://auth.stg.topolo.us npm run build && wrangler deploy --env staging";
4382
+ scripts["deploy:production"] = "DEPLOY_ENVIRONMENT=production npm run build && wrangler deploy";
4383
+ } else {
4384
+ scripts.deploy = "npm run build && wrangler deploy";
3221
4385
  }
3222
4386
  devDependencies.wrangler = "^4.40.2";
3223
- scripts.deploy = "npm run build && wrangler deploy";
3224
4387
  }
3225
4388
  return `${json({
3226
4389
  name: application.packageName,
@@ -3235,10 +4398,20 @@ function packageJson(application, target, options) {
3235
4398
  }
3236
4399
  function topoloPackageVersion(packageName, target) {
3237
4400
  if (target === "developer") {
3238
- if (packageName === "@topolo/auth-client") return "^1.0.0";
4401
+ if (packageName === "@topolo-io/auth-client") return "^1.0.0";
3239
4402
  return "^0.1.0";
3240
4403
  }
3241
- const folder = packageName === "@topolo/sdk" ? "topolo-sdk" : packageName.replace("@topolo/", "topolo-");
4404
+ const platformPackageFolders = {
4405
+ "@topolo/sdk": "topolo-sdk",
4406
+ "@topolo-io/auth-client": "topolo-auth-client",
4407
+ "@topolo-io/auth-middleware": "topolo-auth-middleware",
4408
+ "@topolo-io/design-tokens": "topolo-design-tokens",
4409
+ "@topolo-io/ui-kit": "topolo-ui-kit"
4410
+ };
4411
+ const folder = platformPackageFolders[packageName];
4412
+ if (!folder) {
4413
+ throw new Error(`Unknown Topolo platform package: ${packageName}`);
4414
+ }
3242
4415
  return `file:../packages/${folder}`;
3243
4416
  }
3244
4417
  function readme(application, target, options) {
@@ -3267,13 +4440,13 @@ function readme(application, target, options) {
3267
4440
  lines.push(
3268
4441
  "## Shared Topolo Surface",
3269
4442
  "",
3270
- "- `@topolo/ui-kit` provides the shared shell, launcher, account menu, command palette, and bug reporter.",
3271
- "- `@topolo/auth-client` owns browser auth, callback exchange, token refresh, and logout.",
4443
+ "- `@topolo-io/ui-kit` provides the shared shell, launcher, account menu, command palette, and bug reporter.",
4444
+ "- `@topolo-io/auth-client` owns browser auth, callback exchange, token refresh, and logout.",
3272
4445
  "- Generated first-party browser apps start on the shared landing page at `/`, use the shared login page at `/login`, and complete sign-in on `/auth/callback` before entering the workspace shell.",
3273
4446
  "- `vite.config.ts` includes the standard React plugin plus React runtime dedupe so linked Topolo packages resolve one browser React runtime."
3274
4447
  );
3275
4448
  if (options.api) {
3276
- lines.push("- `@topolo/auth-middleware` validates Worker requests through Topolo Auth.");
4449
+ lines.push("- `@topolo-io/auth-middleware` validates Worker requests through Topolo Auth.");
3277
4450
  }
3278
4451
  } else {
3279
4452
  lines.push(
@@ -3336,6 +4509,9 @@ function gitignore() {
3336
4509
  ".env",
3337
4510
  ".env.*",
3338
4511
  "!.env.example",
4512
+ "# Transient CI evidence written by `topolo apps record-evidence`;",
4513
+ "# read by the conformance audit, never committed.",
4514
+ ".topolo/",
3339
4515
  ""
3340
4516
  ].join("\n");
3341
4517
  }
@@ -3363,17 +4539,63 @@ function envDts(api) {
3363
4539
  );
3364
4540
  return lines.join("\n");
3365
4541
  }
3366
- function viteConfig() {
4542
+ function viteConfig(target) {
4543
+ if (target === "developer") {
4544
+ return [
4545
+ "import { defineConfig } from 'vite';",
4546
+ "import react from '@vitejs/plugin-react';",
4547
+ "",
4548
+ "export default defineConfig({",
4549
+ " plugins: [react()],",
4550
+ " resolve: {",
4551
+ " dedupe: ['react', 'react-dom', 'react/jsx-runtime', 'react/jsx-dev-runtime'],",
4552
+ " },",
4553
+ " build: { outDir: 'dist' },",
4554
+ "});",
4555
+ ""
4556
+ ].join("\n");
4557
+ }
3367
4558
  return [
3368
- "import { defineConfig } from 'vite';",
4559
+ "import { fileURLToPath } from 'node:url';",
4560
+ "import { defineConfig, type Plugin } from 'vite';",
3369
4561
  "import react from '@vitejs/plugin-react';",
3370
4562
  "",
3371
- "export default defineConfig({",
3372
- " plugins: [react()],",
3373
- " resolve: {",
3374
- " dedupe: ['react', 'react-dom', 'react/jsx-runtime', 'react/jsx-dev-runtime'],",
3375
- " },",
3376
- " build: { outDir: 'dist' },",
4563
+ "function stagingUrlRewrite(): Plugin {",
4564
+ " const replacements: Array<[string, string]> = [",
4565
+ " ['https://auth.topolo.app', 'https://auth.stg.topolo.us'],",
4566
+ " ['https://notify.topolo.app', 'https://notify.stg.topolo.us'],",
4567
+ " ];",
4568
+ " return {",
4569
+ " name: 'topolo-staging-url-rewrite',",
4570
+ " generateBundle(_options, bundle) {",
4571
+ " for (const item of Object.values(bundle)) {",
4572
+ " if (item.type === 'chunk') {",
4573
+ " for (const [from, to] of replacements) item.code = item.code.split(from).join(to);",
4574
+ " } else if (typeof item.source === 'string') {",
4575
+ " for (const [from, to] of replacements) item.source = item.source.split(from).join(to);",
4576
+ " }",
4577
+ " }",
4578
+ " },",
4579
+ " };",
4580
+ "}",
4581
+ "",
4582
+ "export default defineConfig(() => {",
4583
+ " const isStagingBuild =",
4584
+ " process.env.DEPLOY_ENVIRONMENT === 'staging' ||",
4585
+ " process.env.TOPOLO_ENV === 'staging' ||",
4586
+ " process.env.VITE_TOPOLO_ENV === 'staging' ||",
4587
+ " process.env.VITE_TOPOLO_AUTH_URL === 'https://auth.stg.topolo.us';",
4588
+ "",
4589
+ " return {",
4590
+ " plugins: [react(), ...(isStagingBuild ? [stagingUrlRewrite()] : [])],",
4591
+ " resolve: {",
4592
+ " alias: {",
4593
+ " '@topolo-io/auth-client': fileURLToPath(new URL('./node_modules/@topolo-io/auth-client/dist/index.js', import.meta.url)),",
4594
+ " },",
4595
+ " dedupe: ['react', 'react-dom', 'react/jsx-runtime', 'react/jsx-dev-runtime'],",
4596
+ " },",
4597
+ " build: { outDir: 'dist' },",
4598
+ " };",
3377
4599
  "});",
3378
4600
  ""
3379
4601
  ].join("\n");
@@ -3399,6 +4621,7 @@ function mainTsx() {
3399
4621
  return [
3400
4622
  "import React from 'react';",
3401
4623
  "import { createRoot } from 'react-dom/client';",
4624
+ "import '@topolo-io/design-tokens/tokens.css';",
3402
4625
  "import App from './App';",
3403
4626
  "import './styles.css';",
3404
4627
  "",
@@ -3418,12 +4641,15 @@ function mainTsx() {
3418
4641
  }
3419
4642
  function stylesCss() {
3420
4643
  return [
4644
+ "/* Local app styles. Design tokens come from @topolo-io/design-tokens (imported in main.tsx). */",
4645
+ "/* Use hsl(var(--token)) for colors so light/dark themes flip from the shared shell. */",
4646
+ "",
3421
4647
  ":root {",
3422
4648
  " color-scheme: light dark;",
3423
4649
  " font-family:",
3424
4650
  ' Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;',
3425
- " background: #f7f8fb;",
3426
- " color: #182032;",
4651
+ " background: hsl(var(--background));",
4652
+ " color: hsl(var(--foreground));",
3427
4653
  "}",
3428
4654
  "",
3429
4655
  "* {",
@@ -3432,7 +4658,8 @@ function stylesCss() {
3432
4658
  "",
3433
4659
  "body {",
3434
4660
  " margin: 0;",
3435
- " background: #f7f8fb;",
4661
+ " background: hsl(var(--background));",
4662
+ " color: hsl(var(--foreground));",
3436
4663
  "}",
3437
4664
  "",
3438
4665
  "button,",
@@ -3442,10 +4669,24 @@ function stylesCss() {
3442
4669
  " font: inherit;",
3443
4670
  "}",
3444
4671
  "",
3445
- ".topolo-starter-workspace,",
4672
+ ".topolo-starter-workspace {",
4673
+ " display: grid;",
4674
+ " gap: 18px;",
4675
+ " min-width: 0;",
4676
+ " width: auto;",
4677
+ " max-width: min(100%, calc(100vw - 56px));",
4678
+ " overflow-x: clip;",
4679
+ "}",
4680
+ "",
3446
4681
  ".topolo-starter-card-stack {",
3447
4682
  " display: grid;",
3448
4683
  " gap: 18px;",
4684
+ " min-width: 0;",
4685
+ "}",
4686
+ "",
4687
+ ".topolo-starter-workspace > *,",
4688
+ ".topolo-starter-card-stack > * {",
4689
+ " min-width: 0;",
3449
4690
  "}",
3450
4691
  "",
3451
4692
  ".topolo-starter-card h2 {",
@@ -3456,15 +4697,16 @@ function stylesCss() {
3456
4697
  "",
3457
4698
  ".topolo-starter-card p {",
3458
4699
  " margin: 0;",
3459
- " color: #526071;",
4700
+ " color: hsl(var(--muted-foreground));",
3460
4701
  " line-height: 1.55;",
3461
4702
  "}",
3462
4703
  "",
3463
4704
  ".topolo-starter-card {",
3464
- " border: 1px solid rgba(24, 32, 50, 0.12);",
3465
- " border-radius: 8px;",
4705
+ " border: 1px solid hsl(var(--border));",
4706
+ " border-radius: var(--radius);",
3466
4707
  " padding: 20px;",
3467
- " background: #ffffff;",
4708
+ " background: hsl(var(--card));",
4709
+ " color: hsl(var(--card-foreground));",
3468
4710
  "}",
3469
4711
  ""
3470
4712
  ].join("\n");
@@ -3476,12 +4718,21 @@ function appTsx(application, target, options) {
3476
4718
  function platformAppTsx(application, options) {
3477
4719
  const appName = toTitle(application.name);
3478
4720
  const serviceId = application.serviceId ?? application.id;
3479
- const tagline = options.audience === "personal" ? "Topolo personal app" : "Topolo workspace";
3480
- const workspaceTitle = options.audience === "personal" ? "Personal app" : "Workspace";
4721
+ const isPersonal = options.audience === "personal";
4722
+ const tagline = isPersonal ? "Topolo personal app" : "Topolo workspace";
4723
+ const workspaceTitle = isPersonal ? "Personal app" : "Workspace";
4724
+ const shellLabel = isPersonal ? "personal app shell" : "workspace shell";
4725
+ const signedSurfaceLabel = isPersonal ? "personal app" : "workspace";
4726
+ const baselineHeading = isPersonal ? "Shared personal baseline" : "Shared workspace baseline";
4727
+ 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.";
3481
4728
  return [
3482
- "import { useEffect, useMemo, useState } from 'react';",
3483
- "import { createBrowserAuthModule, type AuthUser } from '@topolo/auth-client';",
3484
- "import { LandingPage, LoginPage, TopoloAppShell, TopoloAuthFlowScreen, TopoloLoadingScreen, type TopoloAppShellUser } from '@topolo/ui-kit';",
4729
+ "import { Component, useEffect, useMemo, useState, type ErrorInfo, type ReactNode } from 'react';",
4730
+ "import { createBrowserAuthModule, type AuthUser } from '@topolo-io/auth-client';",
4731
+ "import { LandingPage, LoginPage, TopoloAppShell, TopoloAuthFlowScreen, TopoloLoadingScreen, setupBugReporterErrorCapture, type TopoloAppShellUser } from '@topolo-io/ui-kit';",
4732
+ "",
4733
+ "// Wire global error/console capture so BugReporter (enabled in TopoloAppShell below)",
4734
+ "// can attach the last few errors to any submitted bug report.",
4735
+ "setupBugReporterErrorCapture();",
3485
4736
  "",
3486
4737
  "type NavItem = 'home';",
3487
4738
  "type AppRoute = { kind: 'root' } | { kind: 'login' } | { kind: 'callback' };",
@@ -3489,23 +4740,27 @@ function platformAppTsx(application, options) {
3489
4740
  `const APP_ID = '${application.id}';`,
3490
4741
  `const APP_NAME = '${escapeSingleQuoted(appName)}';`,
3491
4742
  `const DEFAULT_SERVICE_ID = '${serviceId}';`,
3492
- "const AUTH_URL = import.meta.env.VITE_TOPOLO_AUTH_URL ?? 'https://auth.topolo.app';",
4743
+ "const DEFAULT_AUTH_URL =",
4744
+ " typeof window !== 'undefined' && window.location.hostname.endsWith('.stg.topolo.us')",
4745
+ " ? 'https://auth.stg.topolo.us'",
4746
+ " : 'https://auth.topolo.app';",
4747
+ "const AUTH_URL = import.meta.env.VITE_TOPOLO_AUTH_URL ?? DEFAULT_AUTH_URL;",
3493
4748
  "const SERVICE_ID = import.meta.env.VITE_TOPOLO_SERVICE_ID ?? DEFAULT_SERVICE_ID;",
3494
4749
  "const LANDING_CONFIG = {",
3495
4750
  " app_name: APP_NAME,",
3496
4751
  ` app_tagline: '${escapeSingleQuoted(tagline)}',`,
3497
4752
  " hero_title: `Open ${APP_NAME} in Topolo`,",
3498
- " hero_subtitle: 'Shared landing, login, callback, auth, and workspace shell wiring are already included in the generated baseline.',",
4753
+ ` hero_subtitle: 'Shared landing, login, callback, auth, and ${escapeSingleQuoted(shellLabel)} wiring are already included in the generated baseline.',`,
3499
4754
  " hero_cta_text: 'Sign in',",
3500
4755
  " hero_cta_url: '/login',",
3501
4756
  " cta_title: 'Start from the shared platform entry flow',",
3502
- " cta_subtitle: 'Unauthenticated routes stay on the shared Topolo surfaces before users enter the signed workspace shell.',",
4757
+ ` cta_subtitle: 'Unauthenticated routes stay on the shared Topolo surfaces before users enter the signed ${escapeSingleQuoted(shellLabel)}.',`,
3503
4758
  "};",
3504
4759
  "const LOGIN_CONFIG = {",
3505
4760
  " app_name: APP_NAME,",
3506
4761
  ` app_tagline: '${escapeSingleQuoted(tagline)}',`,
3507
4762
  " hero_title: `Sign in to ${APP_NAME}`,",
3508
- " hero_subtitle: 'Use Topolo Auth to open the shared workspace shell for this application.',",
4763
+ ` hero_subtitle: 'Use Topolo Auth to open the shared ${escapeSingleQuoted(shellLabel)} for this application.',`,
3509
4764
  " authenticated_home_path: '/',",
3510
4765
  "};",
3511
4766
  "",
@@ -3525,6 +4780,7 @@ function platformAppTsx(application, options) {
3525
4780
  "function toShellUser(user: AuthUser | null): TopoloAppShellUser | null {",
3526
4781
  " if (!user) return null;",
3527
4782
  " return {",
4783
+ " id: user.id,",
3528
4784
  " name: user.name ?? user.email,",
3529
4785
  " email: user.email,",
3530
4786
  " role: user.role,",
@@ -3534,6 +4790,14 @@ function platformAppTsx(application, options) {
3534
4790
  "}",
3535
4791
  "",
3536
4792
  "export default function App() {",
4793
+ " return (",
4794
+ " <AppErrorBoundary>",
4795
+ " <AppRoot />",
4796
+ " </AppErrorBoundary>",
4797
+ " );",
4798
+ "}",
4799
+ "",
4800
+ "function AppRoot() {",
3537
4801
  " const route = getRoute();",
3538
4802
  " const [user, setUser] = useState<AuthUser | null>(null);",
3539
4803
  " const [loading, setLoading] = useState(true);",
@@ -3665,7 +4929,7 @@ function platformAppTsx(application, options) {
3665
4929
  " appId={APP_ID}",
3666
4930
  " serviceId={SERVICE_ID}",
3667
4931
  " appName={APP_NAME}",
3668
- ' subtitle="Opening your workspace..."',
4932
+ ` subtitle="Opening your ${escapeDoubleQuoted(signedSurfaceLabel)}..."`,
3669
4933
  " />",
3670
4934
  " );",
3671
4935
  " }",
@@ -3676,10 +4940,13 @@ function platformAppTsx(application, options) {
3676
4940
  " serviceId={SERVICE_ID}",
3677
4941
  " appName={APP_NAME}",
3678
4942
  " user={shellUser}",
4943
+ " orgId={user.activeContextType === 'organization' ? user.activeContextId ?? user.orgId ?? null : null}",
4944
+ " activeContextType={user.activeContextType}",
4945
+ " activeContextId={user.activeContextId}",
3679
4946
  ' activeNavItemId="home"',
3680
4947
  ` navItems={[{ id: 'home', label: '${escapeSingleQuoted(workspaceTitle)}' }]}`,
4948
+ ' navigationMode="topbar"',
3681
4949
  " onNavItemSelect={() => undefined}",
3682
- " eyebrow={APP_NAME}",
3683
4950
  ` title="${escapeDoubleQuoted(workspaceTitle)}"`,
3684
4951
  " authUrl={AUTH_URL}",
3685
4952
  " getAccessToken={getAccessToken}",
@@ -3691,13 +4958,12 @@ function platformAppTsx(application, options) {
3691
4958
  " bugReporterAppName={APP_NAME}",
3692
4959
  " enableCommandPalette",
3693
4960
  " enableBugReporter",
3694
- " showThemeToggle",
3695
4961
  " >",
3696
4962
  ' <div className="topolo-starter-workspace">',
3697
4963
  ' <section className="topolo-starter-card">',
3698
- " <h2>Shared workspace baseline</h2>",
4964
+ ` <h2>${baselineHeading}</h2>`,
3699
4965
  " <p>",
3700
- " 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.",
4966
+ ` ${baselineDescription}`,
3701
4967
  " </p>",
3702
4968
  " </section>",
3703
4969
  " </div>",
@@ -3716,6 +4982,36 @@ function platformAppTsx(application, options) {
3716
4982
  " if (error instanceof Error && error.message) return error.message;",
3717
4983
  " return 'Unexpected authentication error.';",
3718
4984
  "}",
4985
+ "",
4986
+ "// Minimal error boundary so a render-time crash falls back to a recoverable surface",
4987
+ "// instead of a blank page. The shared TopoloAppShell BugReporter still has access to",
4988
+ "// the captured stack via setupBugReporterErrorCapture().",
4989
+ "class AppErrorBoundary extends Component<{ children: ReactNode }, { error: Error | null }> {",
4990
+ " state = { error: null as Error | null };",
4991
+ " static getDerivedStateFromError(error: Error) {",
4992
+ " return { error };",
4993
+ " }",
4994
+ " componentDidCatch(error: Error, info: ErrorInfo) {",
4995
+ " // Surface to the platform error pipeline. setupBugReporterErrorCapture() also",
4996
+ " // records this via the console.error hook for BugReporter attachments.",
4997
+ " console.error('[' + APP_ID + '] render error:', error, info.componentStack);",
4998
+ " }",
4999
+ " render() {",
5000
+ " if (!this.state.error) return this.props.children;",
5001
+ " return (",
5002
+ " <TopoloAuthFlowScreen",
5003
+ " appName={APP_NAME}",
5004
+ " appId={APP_ID}",
5005
+ " serviceId={SERVICE_ID}",
5006
+ ' status="error"',
5007
+ " title={APP_NAME + ' hit an unexpected error'}",
5008
+ " message={this.state.error.message}",
5009
+ ' actionLabel="Reload"',
5010
+ " onAction={() => window.location.reload()}",
5011
+ " />",
5012
+ " );",
5013
+ " }",
5014
+ "}",
3719
5015
  ""
3720
5016
  ].join("\n");
3721
5017
  }
@@ -3859,7 +5155,7 @@ function wranglerToml(application, target) {
3859
5155
  const lines = [
3860
5156
  `name = "${workerName}"`,
3861
5157
  'main = "src/worker.ts"',
3862
- `compatibility_date = "${CURRENT_VERIFICATION_DATE}"`,
5158
+ `compatibility_date = "${CLOUDFLARE_COMPATIBILITY_DATE}"`,
3863
5159
  'compatibility_flags = ["nodejs_compat"]'
3864
5160
  ];
3865
5161
  if (target === "platform") {
@@ -3894,16 +5190,54 @@ function wranglerToml(application, target) {
3894
5190
  `SERVICE_ID = "${application.serviceId ?? application.id}"`,
3895
5191
  ""
3896
5192
  );
5193
+ if (target === "platform") {
5194
+ lines.push(
5195
+ "# Staging deploys use the separate Topolo Staging Cloudflare account.",
5196
+ "[env.staging]",
5197
+ `name = "${workerName}-staging"`,
5198
+ `account_id = "${STAGING_CLOUDFLARE_ACCOUNT_ID}"`,
5199
+ "workers_dev = true",
5200
+ "routes = [",
5201
+ ` { pattern = "${slug}.stg.topolo.us", custom_domain = true }`,
5202
+ "]",
5203
+ "",
5204
+ "[env.staging.vars]",
5205
+ 'AUTH_BASE_URL = "https://auth.stg.topolo.us"',
5206
+ `SERVICE_ID = "${application.serviceId ?? application.id}"`,
5207
+ ""
5208
+ );
5209
+ }
3897
5210
  return lines.join("\n");
3898
5211
  }
3899
- function workerTs(application, target, _options) {
5212
+ function workerTs(application, target, options) {
3900
5213
  if (target === "developer") return developerWorkerTs(application);
3901
- return platformWorkerTs(application);
5214
+ return platformWorkerTs(application, options);
3902
5215
  }
3903
- function platformWorkerTs(application) {
5216
+ function platformWorkerTs(application, options) {
3904
5217
  const serviceId = application.serviceId ?? application.id;
5218
+ const serviceName = toTitle(application.name);
5219
+ const openActionLabel = options.audience === "personal" ? "Open app" : "Open workspace";
5220
+ const bootstrapBody = options.tenancy === "household" ? [
5221
+ " const userContext = auth.user && typeof auth.user === 'object' ? (auth.user as unknown as Record<string, unknown>) : {};",
5222
+ " return json({",
5223
+ " user: auth.user,",
5224
+ " activeContextType: userContext.activeContextType ?? null,",
5225
+ " activeContextId: userContext.activeContextId ?? null,",
5226
+ " activeContext: userContext.activeContext ?? null,",
5227
+ " selectedHouseholdId: userContext.selectedHouseholdId ?? null,",
5228
+ " selectedHousehold: userContext.selectedHousehold ?? null,",
5229
+ " household: userContext.selectedHousehold ?? null,",
5230
+ " householdMemberships: userContext.householdMemberships ?? [],",
5231
+ " }, { status: 200 }, request);"
5232
+ ] : [
5233
+ " return json({",
5234
+ " user: auth.user,",
5235
+ " organization: auth.organization,",
5236
+ " }, { status: 200 }, request);"
5237
+ ];
3905
5238
  return [
3906
- "import { verifyRequest } from '@topolo/auth-middleware';",
5239
+ "import { createTopoloWidgetResponse } from '@topolo/sdk';",
5240
+ "import { verifyRequest } from '@topolo-io/auth-middleware';",
3907
5241
  "",
3908
5242
  "export interface Env {",
3909
5243
  " AUTH_BASE_URL?: string;",
@@ -3931,11 +5265,24 @@ function platformWorkerTs(application) {
3931
5265
  " return json({ error: auth.error }, { status: auth.status }, request);",
3932
5266
  " }",
3933
5267
  "",
5268
+ " if (url.pathname === '/api/widget') {",
5269
+ " return json(createTopoloWidgetResponse({",
5270
+ " serviceId: env.SERVICE_ID ?? DEFAULT_SERVICE_ID,",
5271
+ ` serviceName: '${escapeSingleQuoted(serviceName)}',`,
5272
+ " widgets: [",
5273
+ " {",
5274
+ " type: 'quick-actions',",
5275
+ " actions: [",
5276
+ ` { id: 'open-workspace', label: '${escapeSingleQuoted(openActionLabel)}', link: '/', primary: true },`,
5277
+ " { id: 'view-health', label: 'View health', link: '/api/health' },",
5278
+ " ],",
5279
+ " },",
5280
+ " ],",
5281
+ " }), { status: 200 }, request);",
5282
+ " }",
5283
+ "",
3934
5284
  " if (url.pathname === '/api/bootstrap') {",
3935
- " return json({",
3936
- " user: auth.user,",
3937
- " organization: auth.organization,",
3938
- " }, { status: 200 }, request);",
5285
+ ...bootstrapBody,
3939
5286
  " }",
3940
5287
  "",
3941
5288
  " return json({ error: 'not_found' }, { status: 404 }, request);",
@@ -4030,6 +5377,7 @@ function developerWorkerTs(application) {
4030
5377
  function cloudcontrolJson(application, options) {
4031
5378
  const slug = toSlug(application.id);
4032
5379
  const host = application.productionUrl ? new URL(application.productionUrl).host : `${slug}.topolo.app`;
5380
+ const stagingHost = `${slug}.stg.topolo.us`;
4033
5381
  const deployTargets = options.api ? [
4034
5382
  {
4035
5383
  kind: "worker",
@@ -4045,6 +5393,21 @@ function cloudcontrolJson(application, options) {
4045
5393
  routes: [host],
4046
5394
  api_url: application.productionUrl,
4047
5395
  service_id: application.serviceId
5396
+ },
5397
+ {
5398
+ kind: "worker",
5399
+ environment: "staging",
5400
+ target_name: `topolo-${slug}-staging`,
5401
+ cloudflare_account_id: STAGING_CLOUDFLARE_ACCOUNT_ID,
5402
+ config_path: "wrangler.toml",
5403
+ build_command: "VITE_TOPOLO_AUTH_URL=https://auth.stg.topolo.us npm run build",
5404
+ output_dir: "dist",
5405
+ production_branch: "staging",
5406
+ healthcheck_path: "/api/health",
5407
+ domains: [stagingHost],
5408
+ routes: [stagingHost],
5409
+ api_url: `https://${stagingHost}`,
5410
+ service_id: application.serviceId
4048
5411
  }
4049
5412
  ] : [
4050
5413
  {
@@ -4080,6 +5443,12 @@ function cloudcontrolJson(application, options) {
4080
5443
  url: application.productionUrl,
4081
5444
  branch: "main",
4082
5445
  status: "unknown"
5446
+ },
5447
+ {
5448
+ name: "staging",
5449
+ url: `https://${stagingHost}`,
5450
+ branch: "staging",
5451
+ status: "unknown"
4083
5452
  }
4084
5453
  ] : [],
4085
5454
  deploy_targets: deployTargets,
@@ -4103,36 +5472,53 @@ function systemJson(application, options) {
4103
5472
  const slug = toSlug(application.id);
4104
5473
  const suiteLabel = options.audience === "personal" ? "personal" : "platform";
4105
5474
  const summaryLabel = options.audience === "personal" ? "first-party Topolo personal application" : "first-party Topolo platform application";
5475
+ const apiNotes = options.api ? options.tenancy === "household" ? "Protected Worker routes validate Topolo Auth credentials and derive active personal context plus selected household context from the credential. The browser baseline starts on shared landing and login routes, completes auth on /auth/callback, and dedupes React when linked Topolo packages are installed locally." : "Protected Worker routes validate Topolo Auth credentials and derive organization context from the credential. The browser baseline starts on shared landing and login routes, completes auth on /auth/callback, and dedupes React when linked Topolo packages are installed locally." : "Browser-only application surface using shared Topolo Auth landing/login/callback routes, shared shell packages, and React runtime dedupe.";
5476
+ const sensitiveData = options.tenancy === "household" ? ["identity", "personal_data", "household_data"] : ["identity", "org_data"];
5477
+ const privacySensitiveData = options.tenancy === "household" ? ["identity", "personal", "household"] : ["identity", "organization"];
4106
5478
  return `${json({
4107
5479
  id: `topolo-${slug}`,
4108
5480
  name: toTitle(application.name),
4109
5481
  slug: `topolo-${slug}`,
4110
5482
  kind: "application",
4111
5483
  summary: `${toTitle(application.name)} is a ${summaryLabel} scaffolded on shared Auth, UI Kit, CloudControl, and conformance contracts.`,
4112
- lifecycle: "planned",
5484
+ lifecycle: "inventory",
4113
5485
  last_verified: CURRENT_VERIFICATION_DATE,
4114
5486
  owners: ["platform-engineering"],
4115
5487
  repo_paths: [`PlatformApplications/${application.appDir}`],
4116
5488
  service_ids: options.api && application.serviceId ? [application.serviceId] : [],
4117
5489
  visibility: "internal",
4118
5490
  api_contract: {
4119
- type: options.api ? "worker" : "none",
5491
+ type: options.api ? "curated" : "none",
4120
5492
  source: options.api ? `PlatformApplications/${application.appDir}/src/worker.ts` : `PlatformApplications/${application.appDir}/src/App.tsx`,
4121
- notes: options.api ? "Protected Worker routes validate Topolo Auth credentials and derive organization context from the credential. The browser baseline starts on shared landing and login routes, completes auth on /auth/callback, and dedupes React when linked Topolo packages are installed locally." : "Browser-only application surface using shared Topolo Auth landing/login/callback routes, shared shell packages, and React runtime dedupe."
5493
+ notes: apiNotes
4122
5494
  },
4123
5495
  primary_hosts: application.productionUrl ? [application.productionUrl] : [],
4124
5496
  doc_paths: [`internal/apps/${slug}`, `public/applications/${slug}`],
4125
5497
  security_assurance: {
4126
5498
  risk_tier: "internal",
4127
- auth_boundary: "Topolo Auth through @topolo/auth-client for browser sessions and @topolo/auth-middleware for Worker requests.",
5499
+ auth_boundary: "Topolo Auth through @topolo-io/auth-client for browser sessions and @topolo-io/auth-middleware for Worker requests.",
4128
5500
  tenant_isolation: options.tenancy === "household" ? "household_scoped" : "organization_scoped",
4129
5501
  external_inputs: ["browser", ...options.api ? ["api"] : []],
4130
- sensitive_data: ["identity", "org_data"],
5502
+ sensitive_data: sensitiveData,
4131
5503
  security_review_status: "not_started",
4132
5504
  last_security_review: CURRENT_VERIFICATION_DATE,
4133
5505
  pentest_status: "not_started",
4134
5506
  evidence_doc: `internal/apps/${slug}`
4135
5507
  },
5508
+ data_privacy: {
5509
+ classification: "confidential",
5510
+ sensitive_data_classes: privacySensitiveData,
5511
+ storage_locations: ["source_control"],
5512
+ encryption_at_rest: "platform_managed",
5513
+ encryption_in_transit: "https_only",
5514
+ key_management: "cloudflare_managed",
5515
+ retention_policy: "needs_policy",
5516
+ deletion_export_status: "planned",
5517
+ logging_redaction_status: "partial",
5518
+ privacy_review_status: "not_started",
5519
+ enterprise_ready: false,
5520
+ evidence_doc: `internal/apps/${slug}`
5521
+ },
4136
5522
  dependencies: options.audience === "personal" ? ["topolo-auth"] : ["topolo-auth", "topolo-platform"],
4137
5523
  tags: ["application", "cloudflare", "typescript", suiteLabel]
4138
5524
  })}
@@ -4143,12 +5529,14 @@ function internalDocs(application, options) {
4143
5529
  const title = toTitle(application.name);
4144
5530
  const suiteLabel = options.audience === "personal" ? "personal" : "platform";
4145
5531
  const tenantLabel = options.tenancy === "household" ? "household" : "organization";
5532
+ const apiContextLabel = options.tenancy === "household" ? "active personal context and selected household context" : "organization context";
5533
+ const boundaryFailureText = options.tenancy === "household" ? "Domain data is added without a household-scoped backend boundary." : "Domain data is added without an organization-scoped backend boundary.";
4146
5534
  return [
4147
5535
  "---",
4148
5536
  `title: "${escapeFrontmatter(title)} Handbook"`,
4149
5537
  `summary: "Internal handbook for ${escapeFrontmatter(title)}."`,
4150
5538
  'owners: ["platform-engineering"]',
4151
- 'status: "planned"',
5539
+ 'status: "draft"',
4152
5540
  `last_verified: "${CURRENT_VERIFICATION_DATE}"`,
4153
5541
  "source_repos:",
4154
5542
  ` - "PlatformApplications/${application.appDir}"`,
@@ -4171,7 +5559,7 @@ function internalDocs(application, options) {
4171
5559
  "",
4172
5560
  "## Architecture",
4173
5561
  "",
4174
- "The generated browser surface uses `@topolo/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.",
5562
+ "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.",
4175
5563
  "",
4176
5564
  "## Runtime Surfaces",
4177
5565
  "",
@@ -4185,11 +5573,11 @@ function internalDocs(application, options) {
4185
5573
  "",
4186
5574
  "## API Reference",
4187
5575
  "",
4188
- options.api ? "The generated API baseline exposes `/api/health` and `/api/bootstrap`. `/api/bootstrap` requires Topolo Auth validation and returns the verified user plus organization context." : "This generated baseline is browser-only and does not expose a callable API service.",
5576
+ options.api ? `The generated API baseline exposes \`/api/health\`, \`/api/widget\`, and \`/api/bootstrap\`. \`/api/widget\` returns the SDK widget response contract for TopoloOne, while \`/api/bootstrap\` requires Topolo Auth validation and returns the verified user plus ${apiContextLabel}.` : "This generated baseline is browser-only and does not expose a callable API service.",
4189
5577
  "",
4190
5578
  "## Auth and Permissions",
4191
5579
  "",
4192
- options.api ? `Browser sessions and Worker requests must be validated through Topolo Auth under service id \`${application.serviceId}\`. Worker routes derive organization context from the verified credential.` : "Browser sessions must be established through Topolo Auth and rendered inside the shared Topolo shell.",
5580
+ options.api ? `Browser sessions and Worker requests must be validated through Topolo Auth under service id \`${application.serviceId}\`. Worker routes derive ${apiContextLabel} from the verified credential.` : "Browser sessions must be established through Topolo Auth and rendered inside the shared Topolo shell.",
4193
5581
  "",
4194
5582
  "## Data Ownership",
4195
5583
  "",
@@ -4197,14 +5585,14 @@ function internalDocs(application, options) {
4197
5585
  "",
4198
5586
  "## Deployments",
4199
5587
  "",
4200
- application.productionUrl ? `The planned production host is \`${application.productionUrl}\`. Deployment metadata starts in \`topolo.cloudcontrol.json\` and must stay aligned with CloudControl before live release.` : "No production host has been assigned yet. Deployment metadata must be completed before live release.",
5588
+ application.productionUrl ? `The planned production host is \`${application.productionUrl}\`. Staging mirrors the scaffold at \`https://${slug}.stg.topolo.us\` through the \`wrangler.toml\` staging environment and Topolo Staging Cloudflare account. Deployment metadata starts in \`topolo.cloudcontrol.json\` and must stay aligned with CloudControl before live release.` : "No production host has been assigned yet. Deployment metadata must be completed before live release.",
4201
5589
  "",
4202
5590
  "## Failure Modes",
4203
5591
  "",
4204
5592
  "- Auth service registration drifts from the generated Worker `SERVICE_ID`.",
4205
5593
  "- CloudControl metadata does not match the eventual Cloudflare project or route.",
4206
5594
  "- linked Topolo packages resolve a second React runtime and the browser shell renders blank instead of mounting the shared surfaces.",
4207
- "- Domain data is added without an organization-scoped backend boundary.",
5595
+ `- ${boundaryFailureText}`,
4208
5596
  "",
4209
5597
  "## Debugging",
4210
5598
  "",
@@ -4227,12 +5615,14 @@ function publicDocs(application, options) {
4227
5615
  const title = toTitle(application.name);
4228
5616
  const suiteLabel = options.audience === "personal" ? "personal" : "business";
4229
5617
  const tenantLabel = options.tenancy === "household" ? "household" : "organization";
5618
+ const apiRouteLabel = options.tenancy === "household" ? "authenticated personal and household routes" : "authenticated workspace routes";
5619
+ const dataModelFailure = options.tenancy === "household" ? "household-scoped data models are not implemented until domain work begins" : "organization-scoped data models are not implemented until domain work begins";
4230
5620
  return [
4231
5621
  "---",
4232
5622
  `title: "${escapeFrontmatter(title)}"`,
4233
5623
  `summary: "Public overview of ${escapeFrontmatter(title)} in the Topolo application suite."`,
4234
5624
  'owners: ["platform-engineering"]',
4235
- 'status: "planned"',
5625
+ 'status: "draft"',
4236
5626
  `last_verified: "${CURRENT_VERIFICATION_DATE}"`,
4237
5627
  "source_repos:",
4238
5628
  ` - "PlatformApplications/${application.appDir}"`,
@@ -4262,7 +5652,7 @@ function publicDocs(application, options) {
4262
5652
  "",
4263
5653
  "## API Reference",
4264
5654
  "",
4265
- options.api ? "The generated API baseline exposes authenticated workspace routes under `/api/*` and validates requests through Topolo Auth." : "This generated baseline is browser-only and does not expose a callable API service.",
5655
+ options.api ? `The generated API baseline exposes ${apiRouteLabel} under \`/api/*\` and validates requests through Topolo Auth.` : "This generated baseline is browser-only and does not expose a callable API service.",
4266
5656
  "",
4267
5657
  "## Auth and Permissions",
4268
5658
  "",
@@ -4274,12 +5664,12 @@ function publicDocs(application, options) {
4274
5664
  "",
4275
5665
  "## Deployments",
4276
5666
  "",
4277
- application.productionUrl ? `The planned production host is \`${application.productionUrl}\`.` : "The production host has not been assigned yet.",
5667
+ application.productionUrl ? `The planned production host is \`${application.productionUrl}\`. Staging mirrors the scaffold at \`https://${slug}.stg.topolo.us\`.` : "The production host has not been assigned yet.",
4278
5668
  "",
4279
5669
  "## Failure Modes",
4280
5670
  "",
4281
5671
  "- service registration and app routing can drift while the product is still planned",
4282
- "- organization-scoped data models are not implemented until domain work begins",
5672
+ `- ${dataModelFailure}`,
4283
5673
  "",
4284
5674
  "## Debugging",
4285
5675
  "",
@@ -4288,7 +5678,7 @@ function publicDocs(application, options) {
4288
5678
  "",
4289
5679
  "## Change Log / Verification",
4290
5680
  "",
4291
- `- Scaffold generated on ${CURRENT_VERIFICATION_DATE} from \`topolo apps scaffold\`.`,
5681
+ `- Scaffold generated on ${CURRENT_VERIFICATION_DATE} from \`${options.portfolio ? "topolo platform apps scaffold" : "topolo apps scaffold"}\`.`,
4292
5682
  ""
4293
5683
  ].join("\n");
4294
5684
  }
@@ -4297,8 +5687,17 @@ function serviceManifestJson(application, options) {
4297
5687
  const title = toTitle(application.name);
4298
5688
  const permissionPrefix = application.id.replace(/[^a-z0-9_]/g, "_");
4299
5689
  const resourceLabel = options.tenancy === "household" ? "household" : "workspace";
5690
+ const launcherSettings = options.tenancy === "household" ? {
5691
+ settings: {
5692
+ launcher: {
5693
+ supported_contexts: ["personal"],
5694
+ household_capable: true
5695
+ }
5696
+ }
5697
+ } : {};
4300
5698
  return `${json({
4301
5699
  serviceId: application.serviceId,
5700
+ ...launcherSettings,
4302
5701
  systems: [
4303
5702
  {
4304
5703
  systemId: `topolo-${slug}`,
@@ -4449,6 +5848,12 @@ function normalizeApplicationId(application) {
4449
5848
  "Application id must be kebab-case or snake_case using lowercase letters, numbers, hyphens, and underscores."
4450
5849
  );
4451
5850
  }
5851
+ function normalizeFirstPartyApplicationId(applicationId) {
5852
+ if (applicationId.startsWith("topolo_") && applicationId.length > "topolo_".length) {
5853
+ return applicationId.slice("topolo_".length);
5854
+ }
5855
+ return applicationId;
5856
+ }
4452
5857
  function toSlug(value) {
4453
5858
  return value.replace(/_/g, "-");
4454
5859
  }
@@ -4461,9 +5866,9 @@ function toTitle(value) {
4461
5866
  function json(value) {
4462
5867
  return JSON.stringify(value, null, 2);
4463
5868
  }
4464
- async function pathExists(path2) {
5869
+ async function pathExists(path4) {
4465
5870
  try {
4466
- await access(path2, constants.F_OK);
5871
+ await access(path4, constants.F_OK);
4467
5872
  return true;
4468
5873
  } catch {
4469
5874
  return false;
@@ -4491,7 +5896,6 @@ function escapeFrontmatter(value) {
4491
5896
  }
4492
5897
 
4493
5898
  // src/commands/app-scaffold-provision.ts
4494
- var DEFAULT_DEVELOPERS_ORIGIN = "https://developers.topolo.app";
4495
5899
  var DEVELOPERS_SERVICE_ID = "svc_topolo_developers";
4496
5900
  async function provisionApplicationScaffold(payload) {
4497
5901
  if (!payload.application.productionUrl) {
@@ -4504,7 +5908,8 @@ async function provisionApplicationScaffold(payload) {
4504
5908
  "Platform scaffold provisioning requires a service id. Re-run without --no-api or pass --service."
4505
5909
  );
4506
5910
  }
4507
- const credential = await refreshCredentialIfNeeded();
5911
+ const environment = selectedEnvironment();
5912
+ const credential = await refreshCredentialIfNeeded(environment);
4508
5913
  if (!credential) {
4509
5914
  throw new Error(
4510
5915
  "topolo apps scaffold --provision requires an authenticated Topolo session. Run `topolo auth login` first."
@@ -4518,15 +5923,16 @@ async function provisionApplicationScaffold(payload) {
4518
5923
  }
4519
5924
  const identity = await client.client.introspect();
4520
5925
  const config = await readConfig();
4521
- const serviceUrls = resolveServiceUrls(config.serviceUrls) ?? {};
4522
- const developersOrigin = sanitizeOrigin(serviceUrls.developers ?? DEFAULT_DEVELOPERS_ORIGIN);
5926
+ const profile = resolvedProfileConfig(config, environment);
5927
+ const developersOrigin = sanitizeOrigin(profile.serviceUrls?.developers ?? "https://developers.topolo.app");
5928
+ assertServiceUrlForEnvironment("developers", developersOrigin, environment);
4523
5929
  const endpoint = payload.target === "platform" ? "/api/developer-console/admin/first-party-app-scaffolds" : "/api/developer-console/apps/scaffold";
4524
5930
  const headers = new Headers({
4525
5931
  "Content-Type": "application/json",
4526
5932
  "X-Service-ID": DEVELOPERS_SERVICE_ID,
4527
5933
  "X-Topolo-Client": `topolo-cli/${CLI_VERSION}`
4528
5934
  });
4529
- const agentName = resolveAgentName(config.agentName);
5935
+ const agentName = resolveAgentName(profile.agentName);
4530
5936
  if (agentName) {
4531
5937
  headers.set("X-Topolo-Agent", agentName);
4532
5938
  }
@@ -4694,6 +6100,17 @@ function registerAppsCommand(program2) {
4694
6100
  fail(err, mode);
4695
6101
  }
4696
6102
  });
6103
+ apps.command("record-evidence <application>").description(
6104
+ "Stamp <repo>/.topolo/evidence.json with the latest CI gate results so the audit can flip verification-gates / observability-and-audit / canonical-docs from partial to met when the recorded run is fresh (< 24h)."
6105
+ ).option("--json", "Output JSON", true).option("--no-json", "Output human-readable summary").option("--app-root <path>", "Path to the application repo root (defaults to cwd)").option("--build", "Mark the latest build as successful").option("--typecheck", "Mark the latest typecheck as successful").option("--test", "Mark the latest test run as successful").option("--docs-validate", "Mark the latest TopoloDocs npm run validate as successful").option("--smoke <url>", "Mark the latest live smoke as successful against this URL").option("--commit <sha>", "Override the commit hash (defaults to git HEAD)").action((application, opts) => {
6106
+ const mode = resolveOutputMode(opts.json);
6107
+ try {
6108
+ const payload = recordApplicationEvidence(application, opts);
6109
+ emit(payload, mode, () => formatEvidence(payload));
6110
+ } catch (err) {
6111
+ fail(err, mode);
6112
+ }
6113
+ });
4697
6114
  apps.command("scaffold <application>").description(
4698
6115
  "Generate a third-party Topolo application scaffold for business or personal/household use."
4699
6116
  ).option("--json", "Output JSON").option("--no-json", "Output human-readable checklist").option("--app-dir <path>", "Application directory under PlatformApplications").option("--name <name>", "Application display/package source name").option("--package-name <name>", "package.json name").option("--production-url <url>", "Production browser URL").option("--service <id>", "Stable SDK/Auth service id for API-backed apps").option("--audience <audience>", "Developer app audience: business or personal").option("--tenancy <tenancy>", "Developer app tenancy: organization or household").option(
@@ -4783,6 +6200,78 @@ function applicationAuditPayload(application, options = {}) {
4783
6200
  if (!options.local) return report;
4784
6201
  return enrichApplicationAuditWithLocalEvidence(report, { repoRoot: options.repoRoot });
4785
6202
  }
6203
+ function recordApplicationEvidence(application, options = {}) {
6204
+ const appId = resolveApplication(application).id;
6205
+ const root = path2.resolve(options.appRoot ?? process.cwd());
6206
+ if (!existsSync3(root)) {
6207
+ throw new Error(`Application root does not exist: ${root}`);
6208
+ }
6209
+ let commit = options.commit;
6210
+ if (!commit) {
6211
+ try {
6212
+ commit = execSync("git rev-parse HEAD", { cwd: root, stdio: ["ignore", "pipe", "ignore"] }).toString().trim() || void 0;
6213
+ } catch {
6214
+ commit = void 0;
6215
+ }
6216
+ }
6217
+ const evidenceDir = path2.join(root, ".topolo");
6218
+ const evidencePath = path2.join(evidenceDir, "evidence.json");
6219
+ let before = null;
6220
+ if (existsSync3(evidencePath)) {
6221
+ try {
6222
+ const raw = JSON.parse(readFileSync2(evidencePath, "utf8"));
6223
+ if (raw && typeof raw === "object" && raw.version === 1) {
6224
+ before = raw;
6225
+ }
6226
+ } catch {
6227
+ }
6228
+ }
6229
+ const now = (/* @__PURE__ */ new Date()).toISOString();
6230
+ const after = before ? { ...before } : { version: 1, appId };
6231
+ after.version = 1;
6232
+ after.appId = appId;
6233
+ const updatedFields = [];
6234
+ const stamp = (ok) => ({ timestamp: now, ok, ...commit ? { commit } : {} });
6235
+ if (options.build) {
6236
+ after.lastBuild = stamp(true);
6237
+ updatedFields.push("lastBuild");
6238
+ }
6239
+ if (options.typecheck) {
6240
+ after.lastTypecheck = stamp(true);
6241
+ updatedFields.push("lastTypecheck");
6242
+ }
6243
+ if (options.test) {
6244
+ after.lastTest = stamp(true);
6245
+ updatedFields.push("lastTest");
6246
+ }
6247
+ if (options.docsValidate) {
6248
+ after.lastDocsValidate = { timestamp: now, ok: true };
6249
+ updatedFields.push("lastDocsValidate");
6250
+ }
6251
+ if (options.smoke) {
6252
+ after.lastSmoke = { timestamp: now, ok: true, url: options.smoke };
6253
+ updatedFields.push("lastSmoke");
6254
+ }
6255
+ if (updatedFields.length === 0) {
6256
+ throw new Error(
6257
+ "record-evidence: pass at least one of --build, --typecheck, --test, --docs-validate, --smoke <url>"
6258
+ );
6259
+ }
6260
+ mkdirSync(evidenceDir, { recursive: true });
6261
+ writeFileSync(evidencePath, JSON.stringify(after, null, 2) + "\n");
6262
+ return { appId, evidencePath, before, after, updatedFields };
6263
+ }
6264
+ function formatEvidence(result) {
6265
+ const lines = [];
6266
+ lines.push(`Stamped ${result.evidencePath}`);
6267
+ lines.push(` app: ${result.appId}`);
6268
+ for (const field of result.updatedFields) {
6269
+ const stamp = result.after[field];
6270
+ if (!stamp) continue;
6271
+ lines.push(` ${field}: ok at ${stamp.timestamp}${stamp.commit ? ` (${stamp.commit.slice(0, 7)})` : ""}`);
6272
+ }
6273
+ return lines.join("\n");
6274
+ }
4786
6275
  function evaluateApplicationAuditGate(payload, failOn) {
4787
6276
  const threshold = normalizeFailOn(failOn);
4788
6277
  const counts = {
@@ -5006,6 +6495,8 @@ function statusesAtOrWorse(threshold) {
5006
6495
  }
5007
6496
 
5008
6497
  // src/commands/platform.ts
6498
+ import { readdirSync as readdirSync2, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
6499
+ import path3 from "path";
5009
6500
  function registerPlatformCommand(program2) {
5010
6501
  const platform3 = program2.command("platform").description("Internal Topolo first-party platform and personal app commands");
5011
6502
  const platformApps = platform3.command("apps").description("Scaffold and operate first-party Topolo platform and personal applications");
@@ -5017,6 +6508,9 @@ function registerPlatformCommand(program2) {
5017
6508
  ).option("--container-app-id <appId>", "Container app id when --surface-model mobile_module is used").option("--write", "Write the generated scaffold files to disk").option("--repo-root <path>", "Directory where scaffold files should be written").option("--force", "Overwrite existing scaffold files when --write is set").option("--with-api", "Include the API/service baseline").option("--no-api", "Do not include API/service requirements").option("--no-browser", "Do not include browser-shell requirements").option(
5018
6509
  "--provision",
5019
6510
  "Provision the generated first-party app into Topolo Developers and Auth using the authenticated Topolo admin session"
6511
+ ).option(
6512
+ "--no-provision",
6513
+ "Skip live Developers/Auth registration when writing a first-party scaffold"
5020
6514
  ).option("--workflow-only", "Output only the generated GitHub Actions workflow in human mode").action(async (application, opts) => {
5021
6515
  const mode = opts.json === false || process.argv.includes("--no-json") ? "pretty" : resolveOutputMode(opts.json);
5022
6516
  try {
@@ -5041,11 +6535,13 @@ function registerPlatformCommand(program2) {
5041
6535
  if (opts.workflowOnly && !payload.ciWorkflow) {
5042
6536
  throw new Error("--workflow-only is only available for first-party scaffolds.");
5043
6537
  }
5044
- if (opts.provision && !opts.write) {
6538
+ const provisionRequested = process.argv.includes("--provision");
6539
+ const shouldProvision = Boolean(opts.write) && opts.provision !== false;
6540
+ if (provisionRequested && !opts.write) {
5045
6541
  throw new Error("--provision requires --write so remote first-party records match written scaffold files.");
5046
6542
  }
5047
6543
  const writeResult = opts.write ? await writeApplicationScaffold(payload, { repoRoot: opts.repoRoot, force: opts.force }) : null;
5048
- const provisionResult = opts.provision ? await provisionApplicationScaffold(payload) : null;
6544
+ const provisionResult = shouldProvision ? await provisionApplicationScaffold(payload) : null;
5049
6545
  const output = opts.workflowOnly ? { application: payload.application, ciWorkflowPath: payload.ciWorkflowPath, ciWorkflow: payload.ciWorkflow } : {
5050
6546
  ...payload,
5051
6547
  ...writeResult ? { write: writeResult } : {},
@@ -5060,6 +6556,95 @@ function registerPlatformCommand(program2) {
5060
6556
  fail(err, mode);
5061
6557
  }
5062
6558
  });
6559
+ const guard = platform3.command("guard").description("Run first-party platform guardrails");
6560
+ guard.command("staging-urls [root]").description("Fail when staging runtime source contains production Topolo URLs").option("--json", "Output JSON").option("--allow <pattern...>", "Additional regex allowlist patterns").action((root = process.cwd(), opts) => {
6561
+ const mode = opts.json ? "json" : "pretty";
6562
+ const result = scanStagingProductionUrls(root, opts.allow ?? []);
6563
+ if (mode === "json") {
6564
+ emit(result, "json", () => "");
6565
+ } else if (result.violations.length === 0) {
6566
+ emit(null, "pretty", () => `No staging production URL violations found in ${result.root}.`);
6567
+ } else {
6568
+ emit(
6569
+ null,
6570
+ "pretty",
6571
+ () => [
6572
+ `Found ${result.violations.length} staging production URL violation(s) in ${result.root}:`,
6573
+ ...result.violations.map(
6574
+ (violation) => `- ${violation.file}:${violation.line} ${violation.url}`
6575
+ )
6576
+ ].join("\n")
6577
+ );
6578
+ }
6579
+ if (result.violations.length > 0) process.exitCode = 1;
6580
+ });
6581
+ }
6582
+ var PRODUCTION_TOPOLO_URL_PATTERN = /(https?:\/\/[^\s'"`)>}]*topolo\.(?:app|io)[^\s'"`)<}]*)|(?:[a-z0-9-]+\.topolo\.app)/gi;
6583
+ var STAGING_TOPOLO_URL_PATTERN = /\.stg\.topolo\.us\b/;
6584
+ var RUNTIME_EXTENSIONS = /* @__PURE__ */ new Set([".cjs", ".html", ".js", ".jsx", ".json", ".jsonc", ".mjs", ".toml", ".ts", ".tsx"]);
6585
+ var SKIPPED_SCAN_DIRS = /* @__PURE__ */ new Set([
6586
+ ".git",
6587
+ ".nexus-widget-deploy",
6588
+ ".next",
6589
+ ".playwright-cli",
6590
+ ".playwright-mcp",
6591
+ ".wrangler",
6592
+ ".worktrees",
6593
+ "_staging-clones",
6594
+ "build",
6595
+ "coverage",
6596
+ "dist",
6597
+ "node_modules",
6598
+ "out",
6599
+ "output"
6600
+ ]);
6601
+ function scanStagingProductionUrls(rootInput, allowPatterns) {
6602
+ const root = path3.resolve(rootInput);
6603
+ const allowlist = allowPatterns.map((pattern) => new RegExp(pattern));
6604
+ const violations = [];
6605
+ function shouldSkipFile(filePath) {
6606
+ const basename = path3.basename(filePath);
6607
+ const relativePath = path3.relative(root, filePath);
6608
+ if (basename === "package-lock.json" || basename === "pnpm-lock.yaml" || basename === "yarn.lock") return true;
6609
+ if (basename === "topolo.cloudcontrol.json") return true;
6610
+ if (basename === "wrangler.toml" || basename === "wrangler.jsonc") return true;
6611
+ if (basename === "consumer-graph.json") return true;
6612
+ if (/^cloudcontrol-deployment-report-.+\.json$/.test(basename)) return true;
6613
+ if (basename.endsWith(".generated.ts")) return true;
6614
+ if (basename.endsWith(".test.ts") || basename.endsWith(".test.tsx") || basename.endsWith(".spec.ts")) return true;
6615
+ if (basename === ".env.production") return true;
6616
+ if (relativePath === path3.join("src", "environment.ts") || relativePath === path3.join("src", "environmentUrls.ts") || relativePath.endsWith(path3.join("src", "environment.ts")) || relativePath.endsWith(path3.join("src", "lib", "environment.ts")) || relativePath === path3.join("packages", "topolo-ui-kit", "src", "environmentUrls.ts")) return true;
6617
+ if (relativePath.includes(`${path3.sep}test${path3.sep}`) || relativePath.includes(`${path3.sep}tests${path3.sep}`) || relativePath.includes(`${path3.sep}e2e${path3.sep}`)) return true;
6618
+ if (relativePath.startsWith(`scripts${path3.sep}`) || relativePath.includes(`${path3.sep}scripts${path3.sep}`)) return true;
6619
+ return !RUNTIME_EXTENSIONS.has(path3.extname(filePath));
6620
+ }
6621
+ function scanFile(filePath) {
6622
+ if (shouldSkipFile(filePath)) return;
6623
+ const relativePath = path3.relative(root, filePath);
6624
+ const content = readFileSync3(filePath, "utf8");
6625
+ const lines = content.split(/\r?\n/);
6626
+ lines.forEach((line, index) => {
6627
+ const matches = [...line.matchAll(PRODUCTION_TOPOLO_URL_PATTERN)].map((match) => match[0]);
6628
+ for (const url of new Set(matches)) {
6629
+ const allowed = STAGING_TOPOLO_URL_PATTERN.test(line) || line.includes("PRODUCTION_") || line.includes("hostname ===") || line.includes(".split(") && line.includes(".join(") || allowlist.some((pattern) => pattern.test(`${relativePath}:${index + 1}:${line}`));
6630
+ if (!allowed) {
6631
+ violations.push({ file: relativePath, line: index + 1, url, text: line.trim() });
6632
+ }
6633
+ }
6634
+ });
6635
+ }
6636
+ function walk(dir) {
6637
+ for (const entry of readdirSync2(dir, { withFileTypes: true })) {
6638
+ const fullPath = path3.join(dir, entry.name);
6639
+ if (entry.isDirectory()) {
6640
+ if (!SKIPPED_SCAN_DIRS.has(entry.name)) walk(fullPath);
6641
+ continue;
6642
+ }
6643
+ if (entry.isFile() && statSync2(fullPath).size < 2e6) scanFile(fullPath);
6644
+ }
6645
+ }
6646
+ walk(root);
6647
+ return { root, violations };
5063
6648
  }
5064
6649
  function resolvePlatformPortfolio(value) {
5065
6650
  if (!value) return void 0;
@@ -5104,45 +6689,281 @@ function registerServicesCommand(program2) {
5104
6689
  program2.command("services").description("List every Topolo service the SDK can route to").option("--json", "Output JSON", true).option("--no-json", "Output human-readable table").action(async (opts) => {
5105
6690
  const mode = resolveOutputMode(opts.json);
5106
6691
  try {
6692
+ const environment = selectedEnvironment();
5107
6693
  const config = await readConfig();
5108
- const rows = buildRows(resolveServiceUrls(config.serviceUrls));
5109
- emit({ services: rows }, mode, () => formatRows2(rows));
6694
+ const rows = buildRows(config.serviceUrls, environment);
6695
+ emit({ environment, services: rows }, mode, () => formatRows2(rows, environment));
5110
6696
  } catch (err) {
5111
6697
  fail(err, mode);
5112
6698
  }
5113
6699
  });
5114
6700
  }
5115
- function buildRows(overrides) {
6701
+ function buildRows(overrides, environment = selectedEnvironment()) {
6702
+ const resolvedOverrides = resolveServiceUrls(overrides, environment);
5116
6703
  return Object.keys(DEFAULT_SERVICE_URLS).sort().map((id) => {
5117
6704
  const defaultUrl = DEFAULT_SERVICE_URLS[id];
5118
- const effectiveUrl = resolveServiceUrl(id, overrides);
6705
+ const effectiveUrl = resolveServiceUrl(id, resolvedOverrides);
5119
6706
  return {
5120
6707
  id,
5121
6708
  defaultUrl,
5122
6709
  effectiveUrl,
5123
- overridden: defaultUrl !== effectiveUrl
6710
+ overridden: defaultUrl !== effectiveUrl,
6711
+ environmentSafe: environment !== "staging" || isStagingUrl(effectiveUrl)
5124
6712
  };
5125
6713
  });
5126
6714
  }
5127
- function formatRows2(rows) {
6715
+ function formatRows2(rows, environment = selectedEnvironment()) {
5128
6716
  const idWidth = Math.max(3, ...rows.map((r) => r.id.length));
5129
6717
  const urlWidth = Math.max(7, ...rows.map((r) => r.effectiveUrl.length));
5130
6718
  const lines = [];
5131
- lines.push(`${"ID".padEnd(idWidth)} ${"URL".padEnd(urlWidth)} OVERRIDE`);
5132
- lines.push(`${"-".repeat(idWidth)} ${"-".repeat(urlWidth)} --------`);
6719
+ lines.push(`Environment: ${environment}`);
6720
+ lines.push("");
6721
+ lines.push(`${"ID".padEnd(idWidth)} ${"URL".padEnd(urlWidth)} OVERRIDE READY`);
6722
+ lines.push(`${"-".repeat(idWidth)} ${"-".repeat(urlWidth)} -------- -----`);
5133
6723
  for (const r of rows) {
5134
6724
  lines.push(
5135
- `${r.id.padEnd(idWidth)} ${r.effectiveUrl.padEnd(urlWidth)} ${r.overridden ? "yes" : ""}`
6725
+ `${r.id.padEnd(idWidth)} ${r.effectiveUrl.padEnd(urlWidth)} ${r.overridden ? "yes" : "".padEnd(8)} ${r.environmentSafe ? "yes" : "no"}`
5136
6726
  );
5137
6727
  }
5138
6728
  return lines.join("\n");
5139
6729
  }
6730
+ function isStagingUrl(value) {
6731
+ const url = value.toLowerCase();
6732
+ return url.includes(".stg.topolo.us") || url.includes("localhost") || url.includes("127.0.0.1") || url.includes("topolo-staging");
6733
+ }
6734
+
6735
+ // src/commands/seed.ts
6736
+ var DEFAULT_SEED_BASE_URL = "https://seed.stg.topolo.us";
6737
+ function seedBaseUrl() {
6738
+ return process.env.TOPOLO_SEED_URL || DEFAULT_SEED_BASE_URL;
6739
+ }
6740
+ async function authedFetch(path4, init = {}) {
6741
+ const credential = await refreshCredentialIfNeeded("staging");
6742
+ if (!credential) {
6743
+ throw new Error("No staging credential configured. Run `topolo auth login --env staging` or set TOPOLO_STAGING_API_KEY.");
6744
+ }
6745
+ const headers = new Headers(init.headers || {});
6746
+ if (credential.kind === "api_key") headers.set("X-Api-Key", credential.apiKey);
6747
+ else headers.set("Authorization", `Bearer ${credential.accessToken}`);
6748
+ headers.set("X-Topolo-Client", `topolo-cli/${CLI_VERSION}`);
6749
+ if (init.body && !headers.has("Content-Type")) headers.set("Content-Type", "application/json");
6750
+ const response = await fetch(`${seedBaseUrl()}${path4}`, { ...init, headers });
6751
+ const text = await response.text();
6752
+ let body;
6753
+ try {
6754
+ body = text ? JSON.parse(text) : {};
6755
+ } catch {
6756
+ body = { raw: text };
6757
+ }
6758
+ if (!response.ok) {
6759
+ throw new Error(`TopoloSeed ${response.status}: ${typeof body === "object" && body && "error" in body ? body.error : text}`);
6760
+ }
6761
+ return body;
6762
+ }
6763
+ function registerSeedCommands(program2) {
6764
+ const seed = program2.command("seed").description("Drive TopoloSeed (staging only): list packs, queue runs, check status, reset.");
6765
+ seed.command("list").description("List seed packs from the staging catalog").option("--json", "Output JSON", true).option("--no-json", "Output table").option("--scope <scope>", "Filter by scope (system | application | feature)").option("--app <appId>", "Filter by app id (e.g. topolo-auth)").action(async (opts) => {
6766
+ const mode = resolveOutputMode(opts.json);
6767
+ try {
6768
+ const params = new URLSearchParams();
6769
+ if (opts.scope) params.set("scope", opts.scope);
6770
+ if (opts.app) params.set("app_id", opts.app);
6771
+ const qs = params.toString();
6772
+ const body = await authedFetch(`/api/seed-packs${qs ? `?${qs}` : ""}`);
6773
+ emit({ packs: body.seed_packs }, mode, () => formatPackTable(body.seed_packs));
6774
+ } catch (err) {
6775
+ fail(err, mode);
6776
+ }
6777
+ });
6778
+ seed.command("coverage").description("Show per-app coverage from the live view").option("--json", "Output JSON", true).option("--no-json", "Output table").action(async (opts) => {
6779
+ const mode = resolveOutputMode(opts.json);
6780
+ try {
6781
+ const body = await authedFetch("/api/coverage");
6782
+ emit({ coverage: body.coverage }, mode, () => formatCoverageTable(body.coverage));
6783
+ } catch (err) {
6784
+ fail(err, mode);
6785
+ }
6786
+ });
6787
+ seed.command("run <packId>").description("Queue a seed run; the cron executor picks it up within ~1 minute").option("--json", "Output JSON", true).option("--no-json", "Pretty print").action(async (packId, opts) => {
6788
+ const mode = resolveOutputMode(opts.json);
6789
+ try {
6790
+ const body = await authedFetch("/api/runs", { method: "POST", body: JSON.stringify({ pack_id: packId }) });
6791
+ emit({ run: body.run }, mode, () => `Queued ${body.run?.run_id || "unknown"}`);
6792
+ } catch (err) {
6793
+ fail(err, mode);
6794
+ }
6795
+ });
6796
+ seed.command("status [runId]").description("Show recent runs, or a specific run if runId is provided").option("--json", "Output JSON", true).option("--no-json", "Output table").action(async (runId, opts) => {
6797
+ const mode = resolveOutputMode(opts.json);
6798
+ try {
6799
+ if (runId) {
6800
+ const body = await authedFetch(`/api/runs/${encodeURIComponent(runId)}`);
6801
+ emit({ run: body.run }, mode, () => formatRunDetail(body.run));
6802
+ } else {
6803
+ const body = await authedFetch("/api/runs");
6804
+ emit({ runs: body.runs }, mode, () => formatRunsTable(body.runs));
6805
+ }
6806
+ } catch (err) {
6807
+ fail(err, mode);
6808
+ }
6809
+ });
6810
+ seed.command("cancel <runId>").description("Cancel a queued or running seed run").option("--json", "Output JSON", true).action(async (runId, opts) => {
6811
+ const mode = resolveOutputMode(opts.json);
6812
+ try {
6813
+ const body = await authedFetch(`/api/runs/${encodeURIComponent(runId)}/cancel`, { method: "POST" });
6814
+ emit(body, mode, () => `Cancelled ${runId}`);
6815
+ } catch (err) {
6816
+ fail(err, mode);
6817
+ }
6818
+ });
6819
+ seed.command("retry <runId>").description("Retry a failed or cancelled run (re-queues failed steps)").option("--json", "Output JSON", true).action(async (runId, opts) => {
6820
+ const mode = resolveOutputMode(opts.json);
6821
+ try {
6822
+ const body = await authedFetch(`/api/runs/${encodeURIComponent(runId)}/retry`, { method: "POST" });
6823
+ emit(body, mode, () => `Retried ${runId}`);
6824
+ } catch (err) {
6825
+ fail(err, mode);
6826
+ }
6827
+ });
6828
+ const reset = seed.command("reset").description("Inspect or execute reset plans");
6829
+ reset.command("preview").description("Create a reset plan and return its confirmation phrase").option("--type <type>", "Reset type (soft_activity | app | cross_app)", "cross_app").option("--app <appId>", "Limit to a single target app").option("--feature <featureId>", "Limit to a feature").option("--no-preserve-identities", "Allow auth identities to be deleted").option("--json", "Output JSON", true).action(async (opts) => {
6830
+ const mode = resolveOutputMode(opts.json);
6831
+ try {
6832
+ const body = await authedFetch("/api/resets/preview", {
6833
+ method: "POST",
6834
+ body: JSON.stringify({
6835
+ reset_type: opts.type,
6836
+ target_scope: opts.app ? "application" : "system",
6837
+ target_app_id: opts.app || null,
6838
+ target_feature_id: opts.feature || null,
6839
+ preserve_auth_identities: opts.preserveIdentities !== false
6840
+ })
6841
+ });
6842
+ emit({ reset_plan: body.reset_plan }, mode, () => formatPlanPreview(body.reset_plan));
6843
+ } catch (err) {
6844
+ fail(err, mode);
6845
+ }
6846
+ });
6847
+ reset.command("execute <planId> <confirmation>").description("Execute a previously previewed reset plan (confirmation = the phrase from preview)").option("--dry-run", "Dispatch with dry_run=true to count rows without deleting").option("--json", "Output JSON", true).action(async (planId, confirmation, opts) => {
6848
+ const mode = resolveOutputMode(opts.json);
6849
+ try {
6850
+ const body = await authedFetch(`/api/resets/${encodeURIComponent(planId)}/execute`, {
6851
+ method: "POST",
6852
+ body: JSON.stringify({ confirmation, dry_run: opts.dryRun === true })
6853
+ });
6854
+ emit({ reset_plan: body.reset_plan }, mode, () => formatPlanResult(body.reset_plan));
6855
+ } catch (err) {
6856
+ fail(err, mode);
6857
+ }
6858
+ });
6859
+ seed.command("manifests").description("Show the cached __seed/manifest for each registered app").option("--refresh", "Force re-fetch all manifests now").option("--json", "Output JSON", true).action(async (opts) => {
6860
+ const mode = resolveOutputMode(opts.json);
6861
+ try {
6862
+ if (opts.refresh) {
6863
+ const refreshed = await authedFetch("/api/manifests/refresh", { method: "POST", body: JSON.stringify({}) });
6864
+ emit(refreshed, mode, () => "Refreshed all manifests");
6865
+ return;
6866
+ }
6867
+ const body = await authedFetch("/api/manifests");
6868
+ emit({ manifests: body.manifests }, mode, () => formatManifestsTable(body.manifests));
6869
+ } catch (err) {
6870
+ fail(err, mode);
6871
+ }
6872
+ });
6873
+ }
6874
+ function formatPackTable(packs) {
6875
+ if (packs.length === 0) return "(no packs)";
6876
+ const lines = [];
6877
+ lines.push("PACK_ID SCOPE APP NAME");
6878
+ for (const p of packs) {
6879
+ lines.push(`${String(p.pack_id || "").padEnd(32)} ${String(p.scope || "").padEnd(12)} ${String(p.app_id || "-").padEnd(19)} ${String(p.name || "")}`);
6880
+ }
6881
+ return lines.join("\n");
6882
+ }
6883
+ function formatCoverageTable(rows) {
6884
+ if (rows.length === 0) return "(no apps)";
6885
+ const lines = [];
6886
+ lines.push("APP COVERAGE D1 KV R2 QUEUES");
6887
+ for (const r of rows) {
6888
+ lines.push(`${String(r.app_id || "").padEnd(25)} ${String(r.coverage_status || "").padEnd(17)} ${String(r.d1_status || "").padEnd(13)} ${String(r.kv_status || "").padEnd(13)} ${String(r.r2_status || "").padEnd(13)} ${String(r.queue_status || "")}`);
6889
+ }
6890
+ return lines.join("\n");
6891
+ }
6892
+ function formatRunsTable(runs) {
6893
+ if (runs.length === 0) return "(no runs)";
6894
+ const lines = [];
6895
+ lines.push("RUN_ID PACK STATUS ATTEMPT");
6896
+ for (const r of runs) {
6897
+ lines.push(`${String(r.run_id || "").padEnd(40)} ${String(r.pack_id || "").padEnd(29)} ${String(r.status || "").padEnd(12)} ${String(r.attempt ?? 0)}`);
6898
+ }
6899
+ return lines.join("\n");
6900
+ }
6901
+ function formatRunDetail(run) {
6902
+ const steps = run.steps || [];
6903
+ const lines = [];
6904
+ lines.push(`run_id: ${run.run_id}`);
6905
+ lines.push(`pack: ${run.pack_id}`);
6906
+ lines.push(`status: ${run.status} (attempt ${run.attempt ?? 0})`);
6907
+ if (steps.length > 0) {
6908
+ lines.push("");
6909
+ lines.push("STEPS:");
6910
+ for (const s of steps) {
6911
+ lines.push(` [${s.status}] ${s.step_id} \u2192 ${s.app_id}${s.last_error ? ` (${s.last_error})` : ""}`);
6912
+ }
6913
+ }
6914
+ return lines.join("\n");
6915
+ }
6916
+ function formatPlanPreview(plan) {
6917
+ return [
6918
+ `reset_plan_id: ${plan.reset_plan_id}`,
6919
+ `confirmation: ${plan.confirmation_phrase}`,
6920
+ `estimated: ${plan.estimated_rows} rows / ${plan.estimated_objects} objects`,
6921
+ "",
6922
+ "To execute:",
6923
+ ` topolo seed reset execute ${plan.reset_plan_id} "${plan.confirmation_phrase}"`
6924
+ ].join("\n");
6925
+ }
6926
+ function formatPlanResult(plan) {
6927
+ const results = plan.results || [];
6928
+ const lines = [`reset_plan_id: ${plan.reset_plan_id}`, `status: ${plan.status}`, `total_deleted: ${plan.total_deleted ?? 0}`];
6929
+ if (results.length > 0) {
6930
+ lines.push("");
6931
+ lines.push("PER-APP:");
6932
+ for (const r of results) {
6933
+ lines.push(` [${r.status}] ${r.app_id}: ${r.rows_deleted ?? 0} rows${r.error ? ` (${r.error})` : ""}`);
6934
+ }
6935
+ }
6936
+ return lines.join("\n");
6937
+ }
6938
+ function formatManifestsTable(manifests) {
6939
+ if (manifests.length === 0) return "(no manifests cached)";
6940
+ const lines = [];
6941
+ lines.push("APP VERSION SOURCE FETCHED_AT ERROR");
6942
+ for (const m of manifests) {
6943
+ const fetched = m.fetched_at ? new Date(Number(m.fetched_at)).toISOString().replace("T", " ").slice(0, 19) : "";
6944
+ lines.push(`${String(m.app_id || "").padEnd(25)} ${String(m.contract_version ?? 0).padEnd(8)} ${String(m.source || "").padEnd(18)} ${fetched.padEnd(20)} ${String(m.fetch_error || "")}`);
6945
+ }
6946
+ return lines.join("\n");
6947
+ }
5140
6948
 
5141
6949
  // src/index.ts
5142
6950
  var program = new Command();
6951
+ var argv;
6952
+ try {
6953
+ argv = consumeEnvironmentArg(process.argv);
6954
+ } catch (err) {
6955
+ process.stderr.write(`Unexpected error: ${err instanceof Error ? err.message : String(err)}
6956
+ `);
6957
+ process.exit(1);
6958
+ }
5143
6959
  program.name("topolo").description(
5144
6960
  "Topolo CLI. Public commands are scoped to the authenticated organization or developer workspace, and first-party Topolo scaffolding lives under the internal `topolo platform` namespace."
5145
- ).version(CLI_VERSION);
6961
+ ).version(CLI_VERSION).option("--env <environment>", "Topolo environment: production or staging");
6962
+ program.hook("preAction", (thisCommand) => {
6963
+ const opts = thisCommand.opts();
6964
+ if (!opts.env) return;
6965
+ process.env.TOPOLO_ENV = normalizeEnvironment(opts.env);
6966
+ });
5146
6967
  registerAuthCommands(program);
5147
6968
  registerWhoamiCommand(program);
5148
6969
  registerCrmCommands(program);
@@ -5150,8 +6971,31 @@ registerApiCommand(program);
5150
6971
  registerAppsCommand(program);
5151
6972
  registerPlatformCommand(program);
5152
6973
  registerServicesCommand(program);
5153
- program.parseAsync(process.argv).catch((err) => {
6974
+ registerSeedCommands(program);
6975
+ program.parseAsync(argv).catch((err) => {
5154
6976
  process.stderr.write(`Unexpected error: ${err instanceof Error ? err.message : String(err)}
5155
6977
  `);
5156
6978
  process.exit(1);
5157
6979
  });
6980
+ function consumeEnvironmentArg(argv2) {
6981
+ const next = argv2.slice(0, 2);
6982
+ for (let i = 2; i < argv2.length; i += 1) {
6983
+ const arg = argv2[i];
6984
+ if (!arg) continue;
6985
+ if (arg === "--env") {
6986
+ const value = argv2[i + 1];
6987
+ if (!value || value.startsWith("-")) {
6988
+ throw new Error("--env requires production or staging.");
6989
+ }
6990
+ process.env.TOPOLO_ENV = normalizeEnvironment(value);
6991
+ i += 1;
6992
+ continue;
6993
+ }
6994
+ if (arg.startsWith("--env=")) {
6995
+ process.env.TOPOLO_ENV = normalizeEnvironment(arg.slice("--env=".length));
6996
+ continue;
6997
+ }
6998
+ next.push(arg);
6999
+ }
7000
+ return next;
7001
+ }