@topolo/cli 0.1.29 → 0.1.30

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 (2) hide show
  1. package/dist/index.js +399 -422
  2. package/package.json +13 -13
package/dist/index.js CHANGED
@@ -65,54 +65,53 @@ var TopoloHttpError = class extends TopoloSdkError {
65
65
  }
66
66
  };
67
67
  var SERVICES = {
68
- agent: "https://agent-api.topolo.app",
68
+ agent: "https://agent.topolo.app",
69
69
  auth: "https://auth.topolo.app",
70
70
  backup: "https://backup.topolo.app",
71
71
  books: "https://books.topolo.app",
72
72
  bugfix: "https://bugfix.topolo.app",
73
- bytes: "https://bytes-api.topolo.app",
73
+ bytes: "https://bytes.topolo.app",
74
74
  calendar: "https://calendar.topolo.app",
75
75
  capacity: "https://capacity.topolo.app",
76
76
  chat: "https://chat.topolo.app",
77
- commerce: "https://commerce-api.topolo.app",
77
+ commerce: "https://commerce.topolo.app",
78
78
  compose: "https://compose.topolo.app",
79
79
  consent: "https://consent.topolo.app",
80
- crm: "https://crm-api.topolo.app",
80
+ crm: "https://crm.topolo.app",
81
81
  developers: "https://developers.topolo.app",
82
82
  director: "https://director.topolo.app",
83
- feed: "https://feed-api.topolo.app",
83
+ feed: "https://feed.topolo.app",
84
84
  flow: "https://flow.topolo.app",
85
- forecast: "https://forecast-api.topolo.app",
85
+ forecast: "https://forecast.topolo.app",
86
86
  forms: "https://forms.topolo.app",
87
87
  home: "https://home.topolo.app",
88
88
  insights: "https://insights.topolo.app",
89
89
  inventory: "https://inventory.topolo.app",
90
- learn: "https://learn-api.topolo.app",
90
+ learn: "https://learn.topolo.app",
91
91
  localize: "https://localize.topolo.app",
92
- mail: "https://mail-api.topolo.app",
93
- mdm: "https://mdm-api.topolo.app",
92
+ mail: "https://mail.topolo.app",
93
+ mdm: "https://mdm.topolo.app",
94
94
  messages: "https://messages.topolo.app",
95
95
  nexus: "https://nexus-api.topolo.app",
96
96
  notify: "https://notify.topolo.app",
97
97
  observability_watch: "https://observability-watch.topolo.app",
98
- one: "https://one.topolo.app",
98
+ one: "https://www.topolo.app",
99
99
  p2p: "https://p2p.topolo.app",
100
100
  pay: "https://pay.topolo.app",
101
101
  people: "https://people.topolo.app",
102
- pulse: "https://pulse.topolo.app",
103
102
  "quro.api": "https://api.ol0.me",
104
103
  "quro.redirect": "https://ol0.me",
105
- roadmapper: "https://roadmapper-api.topolo.app",
104
+ roadmapper: "https://roadmapper.topolo.app",
106
105
  sign: "https://sign.topolo.app",
107
- social_studio: "https://studio-api.topolo.app",
108
- socialize: "https://socialize-api.topolo.app",
106
+ social_studio: "https://studio.topolo.app",
107
+ socialize: "https://socialize.topolo.app",
109
108
  spaces: "https://spaces.topolo.app",
110
109
  status: "https://status.topolo.app",
111
110
  success: "https://success.topolo.app",
112
111
  support: "https://support.topolo.app",
113
112
  survey: "https://survey.topolo.app",
114
113
  voice: "https://voice.topolo.app",
115
- web: "https://web-api.topolo.app"
114
+ web: "https://web.topolo.app"
116
115
  };
117
116
  var APPLICATIONS = {
118
117
  "admin": {
@@ -161,17 +160,15 @@ var APPLICATIONS = {
161
160
  "agent.topolo.app"
162
161
  ],
163
162
  "routes": [],
164
- "serviceId": null
163
+ "serviceId": "agent"
165
164
  },
166
165
  {
167
166
  "name": "topolo-agent-api",
168
167
  "kind": "worker",
169
- "apiUrl": "https://agent-api.topolo.app",
170
- "domains": [
171
- "agent-api.topolo.app"
172
- ],
168
+ "apiUrl": null,
169
+ "domains": [],
173
170
  "routes": [],
174
- "serviceId": "agent"
171
+ "serviceId": null
175
172
  }
176
173
  ]
177
174
  },
@@ -291,17 +288,15 @@ var APPLICATIONS = {
291
288
  "bytes.topolo.app"
292
289
  ],
293
290
  "routes": [],
294
- "serviceId": null
291
+ "serviceId": "bytes"
295
292
  },
296
293
  {
297
294
  "name": "topolo-bytes-api",
298
295
  "kind": "worker",
299
- "apiUrl": "https://bytes-api.topolo.app",
300
- "domains": [
301
- "bytes-api.topolo.app"
302
- ],
296
+ "apiUrl": null,
297
+ "domains": [],
303
298
  "routes": [],
304
- "serviceId": "bytes"
299
+ "serviceId": null
305
300
  }
306
301
  ]
307
302
  },
@@ -400,12 +395,10 @@ var APPLICATIONS = {
400
395
  {
401
396
  "name": "topolo-commerce-api",
402
397
  "kind": "worker",
403
- "apiUrl": "https://commerce-api.topolo.app",
404
- "domains": [
405
- "commerce-api.topolo.app"
406
- ],
398
+ "apiUrl": null,
399
+ "domains": [],
407
400
  "routes": [],
408
- "serviceId": "commerce"
401
+ "serviceId": null
409
402
  },
410
403
  {
411
404
  "name": "topolo-commerce-guest-web",
@@ -425,7 +418,7 @@ var APPLICATIONS = {
425
418
  "commerce.topolo.app"
426
419
  ],
427
420
  "routes": [],
428
- "serviceId": null
421
+ "serviceId": "commerce"
429
422
  },
430
423
  {
431
424
  "name": "topolo-commerce-station-web",
@@ -512,12 +505,10 @@ var APPLICATIONS = {
512
505
  {
513
506
  "name": "topolo-crm-api",
514
507
  "kind": "worker",
515
- "apiUrl": "https://crm-api.topolo.app",
516
- "domains": [
517
- "crm-api.topolo.app"
518
- ],
508
+ "apiUrl": null,
509
+ "domains": [],
519
510
  "routes": [],
520
- "serviceId": "crm"
511
+ "serviceId": null
521
512
  },
522
513
  {
523
514
  "name": "topolo-crm-app",
@@ -527,7 +518,7 @@ var APPLICATIONS = {
527
518
  "crm.topolo.app"
528
519
  ],
529
520
  "routes": [],
530
- "serviceId": null
521
+ "serviceId": "crm"
531
522
  }
532
523
  ]
533
524
  },
@@ -650,12 +641,10 @@ var APPLICATIONS = {
650
641
  {
651
642
  "name": "topolo-feed-api",
652
643
  "kind": "worker",
653
- "apiUrl": "https://feed-api.topolo.app",
654
- "domains": [
655
- "feed-api.topolo.app"
656
- ],
644
+ "apiUrl": null,
645
+ "domains": [],
657
646
  "routes": [],
658
- "serviceId": "feed"
647
+ "serviceId": null
659
648
  },
660
649
  {
661
650
  "name": "topolo-feed-web",
@@ -665,7 +654,7 @@ var APPLICATIONS = {
665
654
  "feed.topolo.app"
666
655
  ],
667
656
  "routes": [],
668
- "serviceId": null
657
+ "serviceId": "feed"
669
658
  }
670
659
  ]
671
660
  },
@@ -717,17 +706,15 @@ var APPLICATIONS = {
717
706
  "forecast.topolo.app"
718
707
  ],
719
708
  "routes": [],
720
- "serviceId": null
709
+ "serviceId": "forecast"
721
710
  },
722
711
  {
723
712
  "name": "topolo-forecast-api",
724
713
  "kind": "worker",
725
- "apiUrl": "https://forecast-api.topolo.app",
726
- "domains": [
727
- "forecast-api.topolo.app"
728
- ],
714
+ "apiUrl": null,
715
+ "domains": [],
729
716
  "routes": [],
730
- "serviceId": "forecast"
717
+ "serviceId": null
731
718
  }
732
719
  ]
733
720
  },
@@ -869,17 +856,15 @@ var APPLICATIONS = {
869
856
  "learn.topolo.app"
870
857
  ],
871
858
  "routes": [],
872
- "serviceId": null
859
+ "serviceId": "learn"
873
860
  },
874
861
  {
875
862
  "name": "topolo-learn-api",
876
863
  "kind": "worker",
877
- "apiUrl": "https://learn-api.topolo.app",
878
- "domains": [
879
- "learn-api.topolo.app"
880
- ],
864
+ "apiUrl": null,
865
+ "domains": [],
881
866
  "routes": [],
882
- "serviceId": "learn"
867
+ "serviceId": null
883
868
  }
884
869
  ]
885
870
  },
@@ -925,17 +910,15 @@ var APPLICATIONS = {
925
910
  "mail.topolo.app"
926
911
  ],
927
912
  "routes": [],
928
- "serviceId": null
913
+ "serviceId": "mail"
929
914
  },
930
915
  {
931
916
  "name": "topolo-mail-api",
932
917
  "kind": "worker",
933
- "apiUrl": "https://mail-api.topolo.app",
934
- "domains": [
935
- "mail-api.topolo.app"
936
- ],
918
+ "apiUrl": null,
919
+ "domains": [],
937
920
  "routes": [],
938
- "serviceId": "mail"
921
+ "serviceId": null
939
922
  }
940
923
  ]
941
924
  },
@@ -952,12 +935,10 @@ var APPLICATIONS = {
952
935
  {
953
936
  "name": "topolo-mdm-api",
954
937
  "kind": "worker",
955
- "apiUrl": "https://mdm-api.topolo.app",
956
- "domains": [
957
- "mdm-api.topolo.app"
958
- ],
938
+ "apiUrl": null,
939
+ "domains": [],
959
940
  "routes": [],
960
- "serviceId": "mdm"
941
+ "serviceId": null
961
942
  },
962
943
  {
963
944
  "name": "topolo-mdm-console",
@@ -967,7 +948,7 @@ var APPLICATIONS = {
967
948
  "mdm.topolo.app"
968
949
  ],
969
950
  "routes": [],
970
- "serviceId": null
951
+ "serviceId": "mdm"
971
952
  }
972
953
  ]
973
954
  },
@@ -1100,20 +1081,23 @@ var APPLICATIONS = {
1100
1081
  {
1101
1082
  "name": "topolo-one",
1102
1083
  "kind": "worker",
1103
- "apiUrl": null,
1104
- "domains": [],
1105
- "routes": [],
1084
+ "apiUrl": "https://one.topolo.app",
1085
+ "domains": [
1086
+ "one.topolo.app"
1087
+ ],
1088
+ "routes": [
1089
+ "one.topolo.app/*"
1090
+ ],
1106
1091
  "serviceId": null
1107
1092
  },
1108
1093
  {
1109
1094
  "name": "topolo-one-api",
1110
1095
  "kind": "worker",
1111
- "apiUrl": "https://one.topolo.app",
1096
+ "apiUrl": "https://www.topolo.app",
1112
1097
  "domains": [
1113
- "one.topolo.app"
1098
+ "www.topolo.app"
1114
1099
  ],
1115
1100
  "routes": [
1116
- "one.topolo.app/*",
1117
1101
  "www.topolo.app/*"
1118
1102
  ],
1119
1103
  "serviceId": "one"
@@ -1223,36 +1207,6 @@ var APPLICATIONS = {
1223
1207
  }
1224
1208
  ]
1225
1209
  },
1226
- "pulse": {
1227
- "id": "pulse",
1228
- "appDir": "TopoloPulse",
1229
- "name": "topolo-pulse",
1230
- "packageName": "topolo-pulse",
1231
- "productionUrl": "https://pulse.topolo.app",
1232
- "services": [
1233
- "pulse"
1234
- ],
1235
- "deployTargets": [
1236
- {
1237
- "name": "topolo-pulse",
1238
- "kind": "worker",
1239
- "apiUrl": "https://pulse.topolo.app",
1240
- "domains": [
1241
- "pulse.topolo.app"
1242
- ],
1243
- "routes": [],
1244
- "serviceId": "pulse"
1245
- },
1246
- {
1247
- "name": "topolo-pulse-api",
1248
- "kind": "worker",
1249
- "apiUrl": null,
1250
- "domains": [],
1251
- "routes": [],
1252
- "serviceId": null
1253
- }
1254
- ]
1255
- },
1256
1210
  "quro": {
1257
1211
  "id": "quro",
1258
1212
  "appDir": "TopoloQuro",
@@ -1318,17 +1272,15 @@ var APPLICATIONS = {
1318
1272
  "roadmapper.topolo.app"
1319
1273
  ],
1320
1274
  "routes": [],
1321
- "serviceId": null
1275
+ "serviceId": "roadmapper"
1322
1276
  },
1323
1277
  {
1324
1278
  "name": "topolo-roadmapper-api",
1325
1279
  "kind": "worker",
1326
- "apiUrl": "https://roadmapper-api.topolo.app",
1327
- "domains": [
1328
- "roadmapper-api.topolo.app"
1329
- ],
1280
+ "apiUrl": null,
1281
+ "domains": [],
1330
1282
  "routes": [],
1331
- "serviceId": "roadmapper"
1283
+ "serviceId": null
1332
1284
  }
1333
1285
  ]
1334
1286
  },
@@ -1380,17 +1332,15 @@ var APPLICATIONS = {
1380
1332
  "studio.topolo.app"
1381
1333
  ],
1382
1334
  "routes": [],
1383
- "serviceId": null
1335
+ "serviceId": "social_studio"
1384
1336
  },
1385
1337
  {
1386
1338
  "name": "topolo-social-studio-api",
1387
1339
  "kind": "worker",
1388
- "apiUrl": "https://studio-api.topolo.app",
1389
- "domains": [
1390
- "studio-api.topolo.app"
1391
- ],
1340
+ "apiUrl": null,
1341
+ "domains": [],
1392
1342
  "routes": [],
1393
- "serviceId": "social_studio"
1343
+ "serviceId": null
1394
1344
  },
1395
1345
  {
1396
1346
  "name": "topolo-social-studio-queue",
@@ -1420,17 +1370,15 @@ var APPLICATIONS = {
1420
1370
  "socialize.topolo.app"
1421
1371
  ],
1422
1372
  "routes": [],
1423
- "serviceId": null
1373
+ "serviceId": "socialize"
1424
1374
  },
1425
1375
  {
1426
1376
  "name": "topolo-socialize-api",
1427
1377
  "kind": "worker",
1428
- "apiUrl": "https://socialize-api.topolo.app",
1429
- "domains": [
1430
- "socialize-api.topolo.app"
1431
- ],
1378
+ "apiUrl": null,
1379
+ "domains": [],
1432
1380
  "routes": [],
1433
- "serviceId": "socialize"
1381
+ "serviceId": null
1434
1382
  },
1435
1383
  {
1436
1384
  "name": "topolo-socialize-scheduler",
@@ -1631,12 +1579,10 @@ var APPLICATIONS = {
1631
1579
  {
1632
1580
  "name": "topolo-web-api",
1633
1581
  "kind": "worker",
1634
- "apiUrl": "https://web-api.topolo.app",
1635
- "domains": [
1636
- "web-api.topolo.app"
1637
- ],
1582
+ "apiUrl": null,
1583
+ "domains": [],
1638
1584
  "routes": [],
1639
- "serviceId": "web"
1585
+ "serviceId": null
1640
1586
  },
1641
1587
  {
1642
1588
  "name": "topolo-web-runtime",
@@ -1659,12 +1605,12 @@ var APPLICATIONS = {
1659
1605
  "web.topolo.app"
1660
1606
  ],
1661
1607
  "routes": [],
1662
- "serviceId": null
1608
+ "serviceId": "web"
1663
1609
  }
1664
1610
  ]
1665
1611
  }
1666
1612
  };
1667
- var APPLICATION_REQUIREMENTS_VERSION = "2026-05-01.1";
1613
+ var APPLICATION_REQUIREMENTS_VERSION = "2026-06-07.1";
1668
1614
  var APPLICATION_REQUIREMENTS = [
1669
1615
  {
1670
1616
  id: "platform-metadata",
@@ -1720,16 +1666,20 @@ var APPLICATION_REQUIREMENTS = [
1720
1666
  summary: "Browser applications should feel like one platform and should not create isolated navigation or account surfaces.",
1721
1667
  appliesTo: ["browser"],
1722
1668
  evidence: [
1723
- "@topolo/ui-kit is used for the platform shell where the app has authenticated UI",
1724
- "The shared launcher/app switcher is reachable from authenticated app chrome",
1725
- "Account menu and sign-out behavior use TopoloAppShell or TopoloAccountMenu",
1669
+ "@topolo-io/app-shell is used for the platform shell where the app has authenticated UI",
1670
+ "@topolo-io/app-launcher keeps the shared launcher/app switcher reachable from authenticated app chrome",
1671
+ "Account menu and sign-out behavior use TopoloAppShell or the ui-kit TopoloAccountMenu primitive",
1672
+ "Repeated app body surfaces use shared TopoloAppLayout primitives instead of local stat cards, empty states, or status badges",
1673
+ "Local preview supports deterministic visual QA fixtures for overview, list, detail, settings, and empty/loading states",
1726
1674
  "Authenticated app body content stays inside the viewport on mobile and desktop widths"
1727
1675
  ],
1728
1676
  implementation: [
1729
- "Use TopoloAppShell for authenticated app layouts",
1730
- "Use TopoloAccountMenu when a partial-shell surface still needs account chrome",
1677
+ "Use @topolo-io/app-shell TopoloAppShell for authenticated app layouts",
1678
+ "Use the @topolo-io/ui-kit TopoloAccountMenu primitive when a partial-shell surface still needs account chrome",
1731
1679
  "Pass app-specific account actions as additive menu items instead of forking dropdowns",
1732
1680
  "Use shared design tokens and platform UI primitives before creating local equivalents",
1681
+ "Use TopoloAppMetricCard, TopoloAppEmptyState, TopoloAppStatusBadge, TopoloAppGrid, and TopoloAppSection for repeated app body patterns",
1682
+ "Keep visual QA fixtures available through visualQa=1 or __devpreview=1 so screenshot checks do not depend on live API state",
1733
1683
  "Keep mobile and desktop shell behavior aligned with the platform UI kit",
1734
1684
  "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"
1735
1685
  ]
@@ -1808,11 +1758,13 @@ var APPLICATION_REQUIREMENTS = [
1808
1758
  evidence: [
1809
1759
  "The app-owned API exposes GET /api/widget",
1810
1760
  "The endpoint returns TopoloWidgetApiResponse from @topolo/sdk",
1761
+ "Every returned widget includes snapshot.summary with app-owned operational prose",
1811
1762
  "The endpoint derives organization/user context from the app auth boundary"
1812
1763
  ],
1813
1764
  implementation: [
1814
1765
  "Import createTopoloWidgetResponse from @topolo/sdk in the app backend",
1815
1766
  "Return native app metrics or action widgets instead of generic launch-card fallbacks",
1767
+ "Populate snapshot.summary in the producing app for every widget instead of synthesizing summaries in TopoloOne",
1816
1768
  "Add a route-level test that validates the response with validateTopoloWidgetResponse"
1817
1769
  ]
1818
1770
  },
@@ -2105,6 +2057,8 @@ function firstString(...values) {
2105
2057
  var WRITE_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
2106
2058
  var TOPOLO_ACTION_ID_HEADER = "X-Topolo-Action-Id";
2107
2059
  var TOPOLO_ACTION_NAME_HEADER = "X-Topolo-Action-Name";
2060
+ var TOPOLO_RESOURCE_TYPE_HEADER = "X-Topolo-Resource-Type";
2061
+ var TOPOLO_RESOURCE_ID_HEADER = "X-Topolo-Resource-ID";
2108
2062
  var POSITIVE_CACHE_TTL_MS = 5 * 60 * 1e3;
2109
2063
  var NEGATIVE_CACHE_TTL_MS = 60 * 1e3;
2110
2064
  var CATALOG_CACHE_TTL_MS = 60 * 1e3;
@@ -2183,6 +2137,18 @@ var TopoloClient = class _TopoloClient {
2183
2137
  if (opts.headers) {
2184
2138
  for (const [k, v] of Object.entries(opts.headers)) headers.set(k, v);
2185
2139
  }
2140
+ if (opts.resource) {
2141
+ const resourceType = opts.resource.resourceType.trim();
2142
+ const resourceId = opts.resource.resourceId.trim();
2143
+ if (!resourceType || !resourceId) {
2144
+ throw new TopoloSdkError(
2145
+ "invalid_resource_context",
2146
+ "Both resource.resourceType and resource.resourceId are required when resource context is provided."
2147
+ );
2148
+ }
2149
+ headers.set(TOPOLO_RESOURCE_TYPE_HEADER, resourceType);
2150
+ headers.set(TOPOLO_RESOURCE_ID_HEADER, resourceId);
2151
+ }
2186
2152
  const controller = new AbortController();
2187
2153
  const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);
2188
2154
  const signal = opts.signal ? mergeSignals(opts.signal, controller.signal) : controller.signal;
@@ -2365,6 +2331,11 @@ var TopoloClient = class _TopoloClient {
2365
2331
  const serviceId = typeof s.service_id === "string" ? s.service_id : typeof s.id === "string" ? s.id : null;
2366
2332
  if (!serviceId) continue;
2367
2333
  const permissions = Array.isArray(s.granted_permissions) ? s.granted_permissions.filter((permission) => typeof permission === "string" && permission.trim().length > 0) : [];
2334
+ const catalogApiKeyResources = normalizeApiKeyResourceMetadataList(
2335
+ s.api_key_resources ?? s.apiKeyResources
2336
+ );
2337
+ const apiKeyResource = normalizeApiKeyResourceMetadata(s.api_key_resource ?? s.apiKeyResource) ?? catalogApiKeyResources[0] ?? null;
2338
+ const apiKeyResources = catalogApiKeyResources.length > 0 ? catalogApiKeyResources : apiKeyResource ? [apiKeyResource] : [];
2368
2339
  entries.push({
2369
2340
  serviceId,
2370
2341
  slug: typeof s.app_slug === "string" ? s.app_slug : null,
@@ -2375,7 +2346,9 @@ var TopoloClient = class _TopoloClient {
2375
2346
  status: typeof s.status === "string" ? s.status : null,
2376
2347
  surfaceKind: typeof s.surface_kind === "string" ? s.surface_kind : null,
2377
2348
  launchable: s.launchable === true,
2378
- permissions
2349
+ permissions,
2350
+ apiKeyResource,
2351
+ apiKeyResources
2379
2352
  });
2380
2353
  }
2381
2354
  this.serviceCatalogCache = {
@@ -2461,7 +2434,9 @@ var TopoloClient = class _TopoloClient {
2461
2434
  status: "bundled",
2462
2435
  surfaceKind: "application",
2463
2436
  launchable: Boolean(app.productionUrl),
2464
- permissions: []
2437
+ permissions: [],
2438
+ apiKeyResource: null,
2439
+ apiKeyResources: []
2465
2440
  }));
2466
2441
  }
2467
2442
  /**
@@ -2510,6 +2485,46 @@ function resolveConfiguredServiceId(slug, serviceIds) {
2510
2485
  }
2511
2486
  return null;
2512
2487
  }
2488
+ function normalizeApiKeyResourceMetadata(input2) {
2489
+ if (!isRecord(input2)) return null;
2490
+ const resourceType = readString(input2.resourceType ?? input2.resource_type);
2491
+ const label = readString(input2.label);
2492
+ const emptyLabel = readString(input2.emptyLabel ?? input2.empty_label);
2493
+ const resourceConcept = readString(input2.resourceConcept ?? input2.resource_concept);
2494
+ const canonicalResourceType = readString(input2.canonicalResourceType ?? input2.canonical_resource_type);
2495
+ const defaultEntitlement = readString(input2.defaultEntitlement ?? input2.default_entitlement);
2496
+ const exceptionPolicy = readString(input2.exceptionPolicy ?? input2.exception_policy);
2497
+ if (!resourceType || !label || !emptyLabel || resourceConcept !== "workspace" || canonicalResourceType !== "workspace" || defaultEntitlement !== "enterprise" || exceptionPolicy !== "app_or_organization") {
2498
+ return null;
2499
+ }
2500
+ return {
2501
+ resourceType,
2502
+ label,
2503
+ emptyLabel,
2504
+ resourceConcept,
2505
+ canonicalResourceType,
2506
+ defaultEntitlement,
2507
+ exceptionPolicy
2508
+ };
2509
+ }
2510
+ function normalizeApiKeyResourceMetadataList(input2) {
2511
+ if (!Array.isArray(input2)) return [];
2512
+ const resources = [];
2513
+ const seenTypes = /* @__PURE__ */ new Set();
2514
+ for (const item of input2) {
2515
+ const resource = normalizeApiKeyResourceMetadata(item);
2516
+ if (!resource || seenTypes.has(resource.resourceType)) continue;
2517
+ seenTypes.add(resource.resourceType);
2518
+ resources.push(resource);
2519
+ }
2520
+ return resources;
2521
+ }
2522
+ function readString(value) {
2523
+ return typeof value === "string" ? value.trim() : "";
2524
+ }
2525
+ function isRecord(value) {
2526
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2527
+ }
2513
2528
  function resolveConfiguredServiceUrl(slug, serviceUrls) {
2514
2529
  for (const serviceKey of serviceKeyCandidates2(slug)) {
2515
2530
  const configured = serviceUrls?.[serviceKey];
@@ -3498,7 +3513,7 @@ function registerApiCommand(program2) {
3498
3513
  ).option("-d, --data <json>", "JSON body (for POST/PUT/PATCH)").option(
3499
3514
  "-q, --query <kv...>",
3500
3515
  "Query params as key=value (repeatable), e.g. --query source=cli page=2"
3501
- ).option("--confirm", "Acknowledge a mutating request").option("--json", "Output JSON", true).option("--no-json", "Output human-readable text").action(
3516
+ ).option("--resource-type <type>", "Cataloged app-local workspace resource alias for resource-scoped API keys").option("--resource-id <id>", "Workspace resource id for resource-scoped API keys").option("--confirm", "Acknowledge a mutating request").option("--json", "Output JSON", true).option("--no-json", "Output human-readable text").action(
3502
3517
  async (service, method, path4, opts) => {
3503
3518
  const mode = resolveOutputMode(opts.json);
3504
3519
  try {
@@ -3529,6 +3544,7 @@ function registerApiCommand(program2) {
3529
3544
  }
3530
3545
  }
3531
3546
  const query = parseQueryPairs(opts.query);
3547
+ const resource = parseResourceOption(opts.resourceType, opts.resourceId);
3532
3548
  const topolo = await buildClient();
3533
3549
  if (!topolo) {
3534
3550
  throw new Error(
@@ -3541,6 +3557,7 @@ function registerApiCommand(program2) {
3541
3557
  path: path4,
3542
3558
  body,
3543
3559
  ...query ? { query } : {},
3560
+ ...resource ? { resource } : {},
3544
3561
  confirm: opts.confirm === true
3545
3562
  });
3546
3563
  emit(result, mode, () => JSON.stringify(result, null, 2));
@@ -3550,6 +3567,15 @@ function registerApiCommand(program2) {
3550
3567
  }
3551
3568
  );
3552
3569
  }
3570
+ function parseResourceOption(resourceType, resourceId) {
3571
+ const normalizedType = resourceType?.trim() || "";
3572
+ const normalizedId = resourceId?.trim() || "";
3573
+ if (!normalizedType && !normalizedId) return void 0;
3574
+ if (!normalizedType || !normalizedId) {
3575
+ throw new Error("--resource-type and --resource-id must be provided together.");
3576
+ }
3577
+ return { resourceType: normalizedType, resourceId: normalizedId };
3578
+ }
3553
3579
  function resolveConfiguredServiceUrl2(service, serviceUrls) {
3554
3580
  const normalized = service.trim().toLowerCase();
3555
3581
  const withoutTopolo = normalized.startsWith("topolo-") ? normalized.slice("topolo-".length) : normalized;
@@ -3595,7 +3621,7 @@ import {
3595
3621
  import path from "path";
3596
3622
  var EVIDENCE_FRESHNESS_MS = 36 * 60 * 60 * 1e3;
3597
3623
  var REQUIRED_AUTH_CLIENT_VERSION = "1.1.9";
3598
- var REQUIRED_UI_KIT_VERSION = "0.4.67";
3624
+ var REQUIRED_UI_KIT_VERSION = "0.4.196";
3599
3625
  function readEvidenceFile(appRoot) {
3600
3626
  const candidate = path.join(appRoot, ".topolo", "evidence.json");
3601
3627
  if (!isFile(candidate)) return null;
@@ -3871,7 +3897,7 @@ function enrichFinding(application, finding2, evidence) {
3871
3897
  nextActions: []
3872
3898
  });
3873
3899
  }
3874
- if (shellEvidence.length === 0 && hasDocumentationSurface(application, evidence)) {
3900
+ if (hasDocumentationSurface(application, evidence)) {
3875
3901
  return mergeFinding(finding2, {
3876
3902
  status: "met",
3877
3903
  reason: "This is a documentation surface rather than an authenticated app workspace, so the shared application shell requirement does not apply.",
@@ -3889,7 +3915,7 @@ function enrichFinding(application, finding2, evidence) {
3889
3915
  if (shellEvidence.includes(`dependency-version=@topolo-io/ui-kit<${REQUIRED_UI_KIT_VERSION}`)) {
3890
3916
  return mergeFinding(finding2, {
3891
3917
  status: "needs_review",
3892
- reason: `The app depends on @topolo-io/ui-kit below ${REQUIRED_UI_KIT_VERSION}; launcher catalog cache/dedupe fixes require the current shared kit.`,
3918
+ reason: `The app depends on @topolo-io/ui-kit below ${REQUIRED_UI_KIT_VERSION}; shared primitive fixes require the current kit.`,
3893
3919
  evidence: shellEvidence,
3894
3920
  nextActions: [
3895
3921
  `Update @topolo-io/ui-kit to ${REQUIRED_UI_KIT_VERSION}`,
@@ -3922,13 +3948,61 @@ function enrichFinding(application, finding2, evidence) {
3922
3948
  ]
3923
3949
  });
3924
3950
  }
3951
+ if (shellEvidence.includes("local=raw-symbol-icons")) {
3952
+ return mergeFinding(finding2, {
3953
+ status: "needs_review",
3954
+ reason: "Raw symbolic UI icons are visible locally. Product apps must use TopoloPlatformIcon, TopoloAppIcon, or the shared @topolo-io/ui-kit/icons entrypoint so nav, status, and provider icons stay consistent.",
3955
+ evidence: shellEvidence,
3956
+ nextActions: [
3957
+ "Replace raw nav/status/provider glyphs with TopoloPlatformIcon or TopoloAppIcon",
3958
+ "Use @topolo-io/ui-kit/icons for lucide icons instead of text symbols",
3959
+ "Keep app page content on TopoloAppLayout primitives where available"
3960
+ ]
3961
+ });
3962
+ }
3963
+ if (shellEvidence.includes("local=direct-lucide-react")) {
3964
+ return mergeFinding(finding2, {
3965
+ status: "needs_review",
3966
+ reason: "The app imports lucide-react directly. First-party browser apps should import icons through @topolo-io/ui-kit/icons so the fleet shares one icon library contract.",
3967
+ evidence: shellEvidence,
3968
+ nextActions: [
3969
+ "Change direct lucide-react imports to @topolo-io/ui-kit/icons",
3970
+ "Use TopoloPlatformIcon for recurring platform, provider, capability, and status icons",
3971
+ "Remove the app-local lucide-react dependency when nothing else needs it"
3972
+ ]
3973
+ });
3974
+ }
3975
+ if (shellEvidence.includes("local=missing-shared-page-primitives")) {
3976
+ return mergeFinding(finding2, {
3977
+ status: "needs_review",
3978
+ reason: "The app uses shared shell chrome, but app-owned page content still appears to rely on local layout primitives. Use TopoloAppLayout primitives for metrics, empty states, status badges, grids, and page sections so app bodies stay visually aligned.",
3979
+ evidence: shellEvidence,
3980
+ nextActions: [
3981
+ "Replace repeated local stat cards, empty states, status pills, and page section wrappers with TopoloAppLayout primitives",
3982
+ "Keep app-owned workspace wrappers constrained with min-width: 0 and shared shell spacing tokens",
3983
+ "Rerun visual QA at desktop and mobile widths after moving the page body primitives"
3984
+ ]
3985
+ });
3986
+ }
3987
+ if (shellEvidence.includes("local=missing-visual-qa-fixtures")) {
3988
+ return mergeFinding(finding2, {
3989
+ status: "needs_review",
3990
+ reason: "The app has a browser workspace but no deterministic visual QA mode was found. Staging visual checks need fixture-backed pages so layout, icons, empty states, and loading states can be reviewed without live API/CORS noise.",
3991
+ evidence: shellEvidence,
3992
+ nextActions: [
3993
+ "Add a visualQa=1 or __devpreview=1 mode for local preview builds",
3994
+ "Provide fixture responses for overview, list/table, detail, settings, and empty/loading surfaces",
3995
+ "Use Playwright screenshots against the fixture mode before staging rollout"
3996
+ ]
3997
+ });
3998
+ }
3925
3999
  const usesCanonicalAppShell = shellEvidence.includes("source=TopoloAppShell");
3926
4000
  const usesSharedAccountPrimitive = shellEvidence.includes("source=TopoloAccountMenu");
3927
4001
  const usesSharedSuite = usesCanonicalAppShell || usesSharedAccountPrimitive;
3928
4002
  if (!usesSharedSuite) {
3929
4003
  return mergeFinding(finding2, {
3930
4004
  status: "needs_review",
3931
- reason: "The app depends on @topolo-io/ui-kit, but no canonical TopoloAppShell or shared account primitive is visible locally.",
4005
+ reason: "The app depends on shared Topolo browser packages, but no canonical TopoloAppShell or shared account primitive is visible locally.",
3932
4006
  evidence: shellEvidence,
3933
4007
  nextActions: [
3934
4008
  "Use TopoloAppShell for authenticated first-party app layouts",
@@ -4278,12 +4352,30 @@ function sharedShellEvidence(evidence) {
4278
4352
  const deps = dependencyNames(evidence);
4279
4353
  const text = evidence.source.text;
4280
4354
  const items = [];
4355
+ if (deps.has("@topolo-io/app-shell")) items.push("dependency=@topolo-io/app-shell");
4281
4356
  if (deps.has("@topolo-io/ui-kit")) items.push("dependency=@topolo-io/ui-kit");
4282
4357
  if (packageDependencyVersions(evidence, "@topolo-io/ui-kit").some((version) => isStaleDependencyVersion(version, REQUIRED_UI_KIT_VERSION))) {
4283
4358
  items.push(`dependency-version=@topolo-io/ui-kit<${REQUIRED_UI_KIT_VERSION}`);
4284
4359
  }
4285
4360
  if (text.includes("@topolo-io/ui-kit")) items.push("source=@topolo-io/ui-kit");
4361
+ if (text.includes("@topolo-io/ui-kit/icons")) items.push("source=@topolo-io/ui-kit/icons");
4362
+ if (text.includes("TopoloPlatformIcon")) items.push("source=TopoloPlatformIcon");
4363
+ const usesSharedPagePrimitives = /\bTopoloApp(?:MetricCard|Toolbar|EmptyState|StatusBadge|Page|PageHeader|Section|Grid)\b/.test(text);
4364
+ if (/\bTopoloApp(?:MetricCard|Toolbar|EmptyState|StatusBadge|Page|PageHeader|Section|Grid)\b/.test(text)) {
4365
+ items.push("source=TopoloAppLayout");
4366
+ }
4367
+ if (text.includes("@topolo-io/app-shell")) items.push("source=@topolo-io/app-shell");
4286
4368
  if (text.includes("TopoloAppShell")) items.push("source=TopoloAppShell");
4369
+ const hasVisualQaMode = /visualQa|__devpreview|visual qa/i.test(text);
4370
+ const hasVisualQaFixtures = hasVisualQaMode && /fixture|visualQaResponse|visualQaApi|mock[A-Z]\w*|devpreview/i.test(text) && /fetch|useQuery|api/i.test(text);
4371
+ if (hasVisualQaFixtures) items.push("source=visual-qa-fixtures");
4372
+ if (/from\s+['"]lucide-react['"]/.test(text)) items.push("local=direct-lucide-react");
4373
+ if (/(?:icon|Icon)\s*:\s*['"][◈◇⬡⬢▣◉✉▷✦○◆✕⚙🔑💡⚠✓♫]/.test(text)) {
4374
+ items.push("local=raw-symbol-icons");
4375
+ }
4376
+ if (/(?:<span[^>]*>|<div[^>]*>)[^<]{0,16}[◈◇⬡⬢▣◉✉▷✦○◆✕⚙🔑💡⚠✓♫]/.test(text)) {
4377
+ items.push("local=raw-symbol-icons");
4378
+ }
4287
4379
  if (text.includes(["Topolo", "Workspace", "Shell"].join(""))) items.push("forbidden=workspace-shell");
4288
4380
  if (/\bTopoloShell\b/.test(text)) items.push("legacy=TopoloShellFrame");
4289
4381
  if (text.includes("TopoloAccountMenu")) items.push("source=TopoloAccountMenu");
@@ -4291,6 +4383,12 @@ function sharedShellEvidence(evidence) {
4291
4383
  if (/\baccountMenuOpen\b|\bsetAccountMenuOpen\b|\buserMenuOpen\b|\bUserMenu\b|user-menu/.test(text)) {
4292
4384
  items.push("local=account-menu");
4293
4385
  }
4386
+ if (deps.has("@topolo-io/app-shell") && !usesSharedPagePrimitives) {
4387
+ items.push("local=missing-shared-page-primitives");
4388
+ }
4389
+ if (hasLocalBrowserSurface(evidence) && !hasVisualQaFixtures) {
4390
+ items.push("local=missing-visual-qa-fixtures");
4391
+ }
4294
4392
  return unique(items);
4295
4393
  }
4296
4394
  function hasDocumentationSurface(application, evidence) {
@@ -4299,7 +4397,7 @@ function hasDocumentationSurface(application, evidence) {
4299
4397
  }
4300
4398
  function hasLocalBrowserSurface(evidence) {
4301
4399
  const deps = dependencyNames(evidence);
4302
- if (deps.has("react") || deps.has("react-dom") || deps.has("astro") || deps.has("@vitejs/plugin-react") || deps.has("@topolo-io/ui-kit")) {
4400
+ if (deps.has("react") || deps.has("react-dom") || deps.has("astro") || deps.has("@vitejs/plugin-react") || deps.has("@topolo-io/app-shell") || deps.has("@topolo-io/ui-kit")) {
4303
4401
  return true;
4304
4402
  }
4305
4403
  return evidence.source.files.some(
@@ -4743,8 +4841,11 @@ function scaffoldPackages(options) {
4743
4841
  }
4744
4842
  const packages = /* @__PURE__ */ new Set();
4745
4843
  if (options.browser) {
4746
- packages.add("@topolo-io/ui-kit");
4844
+ packages.add("@topolo-io/app-funnel");
4845
+ packages.add("@topolo-io/app-launcher");
4846
+ packages.add("@topolo-io/app-shell");
4747
4847
  packages.add("@topolo-io/auth-client");
4848
+ packages.add("@topolo-io/ui-kit");
4748
4849
  }
4749
4850
  if (options.api) {
4750
4851
  packages.add("@topolo-io/worker-runtime");
@@ -5031,8 +5132,8 @@ function scaffoldFiles(options) {
5031
5132
  path: `${browserBase}/src/App.tsx`,
5032
5133
  purpose: "Authenticated browser surface.",
5033
5134
  requiredSignals: [
5034
- "imports shared TopoloAppShell from @topolo-io/ui-kit",
5035
- "uses @topolo-io/auth-client for app-origin auth flows",
5135
+ "bootstraps routes, auth provider, and shared shell through @topolo-io/app-shell",
5136
+ "uses @topolo-io/auth-client through the app-shell wrapper for app-origin auth flows",
5036
5137
  "keeps launcher and account-menu behavior on the shared platform surface",
5037
5138
  "passes app-specific account actions through accountMenuItems or TopoloAccountMenu"
5038
5139
  ],
@@ -5248,10 +5349,14 @@ function packageJson(application, target, options) {
5248
5349
  devDependencies["@vitejs/plugin-react"] = "^4.7.0";
5249
5350
  if (target === "platform") {
5250
5351
  dependencies["@topolo-io/auth-client"] = topoloPackageVersion("@topolo-io/auth-client", target);
5352
+ dependencies["@topolo-io/app-funnel"] = topoloPackageVersion("@topolo-io/app-funnel", target);
5353
+ dependencies["@topolo-io/app-launcher"] = topoloPackageVersion("@topolo-io/app-launcher", target);
5354
+ dependencies["@topolo-io/app-shell"] = topoloPackageVersion("@topolo-io/app-shell", target);
5251
5355
  dependencies["@topolo-io/design-tokens"] = topoloPackageVersion("@topolo-io/design-tokens", target);
5252
5356
  dependencies["@topolo-io/onboarding"] = topoloPackageVersion("@topolo-io/onboarding", target);
5253
5357
  dependencies["@topolo-io/tour"] = topoloPackageVersion("@topolo-io/tour", target);
5254
5358
  dependencies["@topolo-io/ui-kit"] = topoloPackageVersion("@topolo-io/ui-kit", target);
5359
+ dependencies["react-router-dom"] = "^7.6.2";
5255
5360
  } else {
5256
5361
  dependencies["@topolo/login"] = topoloPackageVersion("@topolo/login", target);
5257
5362
  }
@@ -5295,6 +5400,9 @@ function topoloPackageVersion(packageName, target) {
5295
5400
  const platformPackageFolders = {
5296
5401
  "@topolo/sdk": "topolo-sdk",
5297
5402
  "@topolo/login": "topolo-login",
5403
+ "@topolo-io/app-funnel": "topolo-app-funnel",
5404
+ "@topolo-io/app-launcher": "topolo-app-launcher",
5405
+ "@topolo-io/app-shell": "topolo-app-shell",
5298
5406
  "@topolo-io/auth-client": "topolo-auth-client",
5299
5407
  "@topolo-io/auth-middleware": "topolo-auth-middleware",
5300
5408
  "@topolo-io/worker-runtime": "topolo-worker-runtime",
@@ -5380,7 +5488,10 @@ function readme(application, target, options) {
5380
5488
  lines.push(
5381
5489
  "## Shared Topolo Surface",
5382
5490
  "",
5383
- "- `@topolo-io/ui-kit` provides the shared shell, launcher, account menu, command palette, and bug reporter.",
5491
+ "- `@topolo-io/app-funnel` provides the generated public application funnel/landing surface.",
5492
+ "- `@topolo-io/app-shell` provides the generated route/auth bootstrap, product shell, login, callback, and loading surfaces.",
5493
+ "- `@topolo-io/app-launcher` provides shared app switching and handoff helpers.",
5494
+ "- `@topolo-io/ui-kit` provides primitive account, command palette, icon, transition, and bug reporter UI.",
5384
5495
  "- `@topolo-io/auth-client` owns browser auth, callback exchange, token refresh, and logout.",
5385
5496
  "- 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.",
5386
5497
  "- `vite.config.ts` includes the standard React plugin plus React runtime dedupe so linked Topolo packages resolve one browser React runtime."
@@ -5802,13 +5913,13 @@ function platformAppTsx(application, options) {
5802
5913
  const tagline = isPersonal ? "Topolo personal app" : "Topolo workspace";
5803
5914
  const workspaceTitle = isPersonal ? "Personal app" : "Workspace";
5804
5915
  const shellLabel = isPersonal ? "personal app shell" : "workspace shell";
5805
- const signedSurfaceLabel = isPersonal ? "personal app" : "workspace";
5806
5916
  const baselineHeading = isPersonal ? "Shared personal baseline" : "Shared workspace baseline";
5807
5917
  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.";
5808
5918
  return [
5809
5919
  "import { Component, useCallback, useEffect, useMemo, useState, type ErrorInfo, type ReactNode } from 'react';",
5810
- "import { createBrowserAuthSessionController, type AuthUser } from '@topolo-io/auth-client';",
5811
- "import { LandingPage, LoginPage, TopoloAppShell, TopoloAuthFlowScreen, TopoloLoadingScreen, setupBugReporterErrorCapture, type TopoloAppShellUser } from '@topolo-io/ui-kit';",
5920
+ "import { LandingPage } from '@topolo-io/app-funnel';",
5921
+ "import { LoginPage, TopoloAppLoadingScaffold, TopoloAppRoot, TopoloAuthFlowScreen, createTopoloAuth, useAuth, type AppRoute as TopoloAppRoute } from '@topolo-io/app-shell';",
5922
+ "import { setupBugReporterErrorCapture } from '@topolo-io/ui-kit';",
5812
5923
  "import { GettingStarted, type OnboardingGuideStep } from '@topolo-io/onboarding';",
5813
5924
  "import { IntroTour } from '@topolo-io/tour';",
5814
5925
  "import { SCAFFOLD_APP_ID, SCAFFOLD_APP_NAME, SCAFFOLD_SERVICE_SLUG, getBrowserAuthBaseUrl, resolveAppServiceId } from './service-identity';",
@@ -5817,11 +5928,6 @@ function platformAppTsx(application, options) {
5817
5928
  "// can attach the last few errors to any submitted bug report.",
5818
5929
  "setupBugReporterErrorCapture();",
5819
5930
  "",
5820
- "type NavItem = 'home' | 'getting-started';",
5821
- "type WorkspaceView = 'workspace' | 'getting-started';",
5822
- "type AppRoute = { kind: 'root' } | { kind: 'login' } | { kind: 'callback' };",
5823
- "type BrowserAuthModule = ReturnType<typeof createBrowserAuthSessionController>;",
5824
- "",
5825
5931
  "const APP_ID = SCAFFOLD_APP_ID;",
5826
5932
  "const APP_NAME = SCAFFOLD_APP_NAME;",
5827
5933
  "const AUTH_URL = getBrowserAuthBaseUrl();",
@@ -5843,69 +5949,17 @@ function platformAppTsx(application, options) {
5843
5949
  " authenticated_home_path: '/',",
5844
5950
  "};",
5845
5951
  "",
5846
- "let activeAuthServiceId = '';",
5847
- "let activeAuthModule: BrowserAuthModule | null = null;",
5848
- "",
5849
- "function getAuthModule(serviceId: string): BrowserAuthModule {",
5850
- " const normalizedServiceId = serviceId.trim();",
5851
- " if (!normalizedServiceId) throw new Error(`Missing Auth service id for ${SCAFFOLD_SERVICE_SLUG}`);",
5852
- " if (!activeAuthModule || activeAuthServiceId !== normalizedServiceId) {",
5853
- " activeAuthServiceId = normalizedServiceId;",
5854
- " activeAuthModule = createBrowserAuthSessionController({",
5855
- " authServiceUrl: AUTH_URL,",
5856
- " serviceId: normalizedServiceId,",
5857
- " devSessionKey: `${normalizedServiceId}_session`,",
5858
- " devSessionStorageEnabled: import.meta.env.DEV,",
5859
- " callbackPath: '/auth/callback',",
5860
- " debug: import.meta.env.DEV,",
5861
- " });",
5862
- " }",
5863
- " return activeAuthModule;",
5864
- "}",
5865
- "",
5866
- "function getAccessToken(): string | null {",
5867
- " return activeAuthModule?.accessTokenMemory.get() ?? activeAuthModule?.accessTokenSession.get() ?? null;",
5868
- "}",
5869
- "",
5870
- "function toShellUser(user: AuthUser | null): TopoloAppShellUser | null {",
5871
- " if (!user) return null;",
5872
- " return {",
5873
- " id: user.id,",
5874
- " name: user.name ?? user.email,",
5875
- " email: user.email,",
5876
- " role: user.role,",
5877
- " organizationName: user.organization?.name ?? user.orgSlug ?? null,",
5878
- " organizationId: user.organization?.id ?? user.orgId ?? null,",
5879
- " };",
5880
- "}",
5881
- "",
5882
5952
  "export default function App() {",
5883
5953
  " return (",
5884
5954
  " <AppErrorBoundary>",
5885
- " <AppRoot />",
5955
+ " <CanonicalAppRoot />",
5886
5956
  " </AppErrorBoundary>",
5887
5957
  " );",
5888
5958
  "}",
5889
5959
  "",
5890
- "function AppRoot() {",
5891
- " const route = getRoute();",
5892
- " const [user, setUser] = useState<AuthUser | null>(null);",
5960
+ "function CanonicalAppRoot() {",
5893
5961
  " const [serviceId, setServiceId] = useState('');",
5894
5962
  " const [serviceIdentityError, setServiceIdentityError] = useState('');",
5895
- " const [loading, setLoading] = useState(true);",
5896
- " const [authError, setAuthError] = useState('');",
5897
- " const [loginAttempted, setLoginAttempted] = useState(false);",
5898
- " const [view, setView] = useState<WorkspaceView>('workspace');",
5899
- " const auth = useMemo(() => serviceId ? getAuthModule(serviceId) : null, [serviceId]);",
5900
- " const openWorkspace = useCallback(() => setView('workspace'), []);",
5901
- " const openGettingStarted = useCallback(() => {",
5902
- " setView('getting-started');",
5903
- " if (typeof window !== 'undefined') window.scrollTo({ top: 0, behavior: 'smooth' });",
5904
- " }, []);",
5905
- " const handleNavSelect = useCallback((id: NavItem) => {",
5906
- " if (id === 'getting-started') openGettingStarted();",
5907
- " else openWorkspace();",
5908
- " }, [openGettingStarted, openWorkspace]);",
5909
5963
  "",
5910
5964
  " useEffect(() => {",
5911
5965
  " let active = true;",
@@ -5917,229 +5971,139 @@ function platformAppTsx(application, options) {
5917
5971
  " .catch((error) => {",
5918
5972
  " if (!active) return;",
5919
5973
  " setServiceIdentityError(errorMessage(error));",
5920
- " setLoading(false);",
5921
5974
  " });",
5922
5975
  " return () => {",
5923
5976
  " active = false;",
5924
5977
  " };",
5925
5978
  " }, []);",
5926
5979
  "",
5927
- " useEffect(() => {",
5928
- " if (!auth) return;",
5929
- " let active = true;",
5930
- " const syncAuth = async () => {",
5931
- " const publicRoute = route.kind === 'root' || route.kind === 'login';",
5932
- " if (publicRoute) setLoading(false);",
5933
- " else setLoading(true);",
5934
- " setAuthError('');",
5935
- "",
5936
- " if (route.kind === 'callback') {",
5937
- " const result = await auth.authClient.handleAuthCallbackAsync(window.location.href);",
5938
- " if (!active) return;",
5939
- " if (!result.authenticated) {",
5940
- " setAuthError(result.error || 'Authentication failed.');",
5941
- " setLoading(false);",
5942
- " return;",
5943
- " }",
5944
- " setUser(result.user ?? null);",
5945
- " window.location.replace('/');",
5946
- " return;",
5947
- " }",
5948
- "",
5949
- " const nextUser = await auth.authClient.initialize({",
5950
- " cookieRefresh: publicRoute ? 'background' : 'blocking',",
5951
- " });",
5952
- " if (!active) return;",
5953
- " setUser(nextUser);",
5954
- " setLoading(false);",
5955
- " };",
5956
- "",
5957
- " void syncAuth().catch((error) => {",
5958
- " if (!active) return;",
5959
- " setAuthError(errorMessage(error));",
5960
- " setLoading(false);",
5961
- " });",
5962
- " return () => {",
5963
- " active = false;",
5964
- " };",
5965
- " }, [auth, route.kind]);",
5966
- "",
5967
- " useEffect(() => {",
5968
- " if (!auth) return;",
5969
- " const handleSessionUpdated = () => {",
5970
- " const nextUser = auth.authClient.getHydratedCurrentUser() || auth.authClient.getTokenIdentity();",
5971
- " if (nextUser) setUser(nextUser);",
5972
- " };",
5973
- " window.addEventListener('auth:session-updated', handleSessionUpdated);",
5974
- " return () => window.removeEventListener('auth:session-updated', handleSessionUpdated);",
5975
- " }, [auth]);",
5980
+ " const auth = useMemo(() => serviceId ? createTopoloAuth({",
5981
+ " authServiceUrl: AUTH_URL,",
5982
+ " serviceId,",
5983
+ " devSessionKey: `${serviceId}_session`,",
5984
+ " callbackPath: '/auth/callback',",
5985
+ " sessionStorageRestoreEnabled: import.meta.env.DEV,",
5986
+ " debug: import.meta.env.DEV,",
5987
+ " }) : null, [serviceId]);",
5976
5988
  "",
5977
- " useEffect(() => {",
5978
- " if (!loading && user && route.kind === 'login') {",
5979
- " window.location.replace('/');",
5980
- " }",
5981
- " }, [loading, route.kind, user]);",
5982
- "",
5983
- " const shellUser = useMemo(() => toShellUser(user), [user]);",
5984
- " const activeServiceId = serviceId || SCAFFOLD_SERVICE_SLUG;",
5985
- "",
5986
- " if (!auth) {",
5987
- " if (serviceIdentityError) {",
5988
- " return (",
5989
- " <TopoloAuthFlowScreen",
5990
- " appName={APP_NAME}",
5991
- " appId={APP_ID}",
5992
- " serviceId={SCAFFOLD_SERVICE_SLUG}",
5993
- ' status="error"',
5994
- ' title="Service identity unavailable"',
5995
- " message={serviceIdentityError}",
5996
- ' actionLabel="Retry"',
5997
- " onAction={() => window.location.reload()}",
5998
- " />",
5999
- " );",
6000
- " }",
6001
- " return (",
6002
- " <TopoloLoadingScreen",
6003
- " appId={APP_ID}",
6004
- " serviceId={SCAFFOLD_SERVICE_SLUG}",
6005
- " appName={APP_NAME}",
6006
- " subtitle={`Loading ${APP_NAME}...`}",
6007
- " />",
6008
- " );",
6009
- " }",
6010
- "",
6011
- " if (route.kind === 'callback') {",
6012
- " if (loading) {",
6013
- " return (",
6014
- " <TopoloLoadingScreen",
6015
- " appId={APP_ID}",
6016
- " serviceId={activeServiceId}",
6017
- " appName={APP_NAME}",
6018
- ' subtitle="Completing secure sign-in..."',
6019
- " />",
6020
- " );",
6021
- " }",
5989
+ " const routes = useMemo<TopoloAppRoute[]>(() => serviceId ? [",
5990
+ " {",
5991
+ " path: '/workspace',",
5992
+ " nav: { id: 'home', label: '" + escapeSingleQuoted(workspaceTitle) + "' },",
5993
+ " loading: { shell: 'topbar', content: 'workspace', title: '" + escapeSingleQuoted(workspaceTitle) + "' },",
5994
+ " element: <ScaffoldWorkspace />,",
5995
+ " },",
5996
+ " {",
5997
+ " path: '/getting-started',",
5998
+ " nav: { id: 'getting-started', label: 'Getting started' },",
5999
+ " loading: { shell: 'topbar', content: 'card-grid', title: 'Getting started' },",
6000
+ " element: <ScaffoldGettingStartedRoute serviceId={serviceId} />,",
6001
+ " },",
6002
+ " ] : [], [serviceId]);",
6022
6003
  "",
6004
+ " if (serviceIdentityError) {",
6023
6005
  " return (",
6024
6006
  " <TopoloAuthFlowScreen",
6025
6007
  " appName={APP_NAME}",
6026
6008
  " appId={APP_ID}",
6027
- " serviceId={activeServiceId}",
6009
+ " serviceId={SCAFFOLD_SERVICE_SLUG}",
6028
6010
  ' status="error"',
6029
- ' title="Authentication failed"',
6030
- " message={authError || `We could not complete sign-in for ${APP_NAME}.`}",
6031
- ' actionLabel="Back to sign in"',
6032
- " onAction={() => window.location.replace('/login')}",
6011
+ ' title="Service identity unavailable"',
6012
+ " message={serviceIdentityError}",
6013
+ ' actionLabel="Retry"',
6014
+ " onAction={() => window.location.reload()}",
6033
6015
  " />",
6034
6016
  " );",
6035
6017
  " }",
6036
6018
  "",
6037
- " if (loading) {",
6019
+ " if (!auth) {",
6038
6020
  " return (",
6039
- " <TopoloLoadingScreen",
6021
+ " <TopoloAppLoadingScaffold",
6040
6022
  " appId={APP_ID}",
6041
- " serviceId={activeServiceId}",
6023
+ " serviceId={SCAFFOLD_SERVICE_SLUG}",
6042
6024
  " appName={APP_NAME}",
6025
+ " layout={{ shell: 'topbar', content: 'workspace' }}",
6043
6026
  " subtitle={`Loading ${APP_NAME}...`}",
6044
6027
  " />",
6045
6028
  " );",
6046
6029
  " }",
6047
6030
  "",
6048
- " if (!user) {",
6049
- " if (route.kind === 'login') {",
6050
- " return (",
6031
+ " return (",
6032
+ " <TopoloAppRoot",
6033
+ " appName={APP_NAME}",
6034
+ " serviceId={serviceId}",
6035
+ " auth={auth}",
6036
+ " routes={routes}",
6037
+ ' home="/workspace"',
6038
+ " publicLanding={",
6039
+ " <LandingPage",
6040
+ " serviceId={serviceId}",
6041
+ " authApiUrl={AUTH_URL}",
6042
+ " config={LANDING_CONFIG}",
6043
+ " onCTAClick={() => window.location.assign('/login')}",
6044
+ " />",
6045
+ " }",
6046
+ " loginElement={",
6051
6047
  " <LoginPage",
6052
- " serviceId={activeServiceId}",
6048
+ " serviceId={serviceId}",
6053
6049
  " authApiUrl={AUTH_URL}",
6054
6050
  " config={LOGIN_CONFIG}",
6055
- " isLoading={loginAttempted}",
6056
6051
  " onLogin={async (provider) => {",
6057
- " setLoginAttempted(true);",
6058
- " try {",
6059
- " await auth.authClient.login(provider || 'topolo');",
6060
- " } catch {",
6061
- " setLoginAttempted(false);",
6062
- " }",
6052
+ " await auth.login(provider || 'topolo');",
6063
6053
  " }}",
6064
6054
  " onPasswordLogin={async (request) => {",
6065
- " const result = await auth.authClient.loginWithPassword(request);",
6066
- " if (!result.authenticated) {",
6067
- " return result;",
6068
- " }",
6069
- " setUser(result.user ?? null);",
6070
- " window.location.replace('/');",
6055
+ " const result = await auth.loginWithPassword(request);",
6056
+ " if (!result.authenticated) return result;",
6057
+ " window.dispatchEvent(new CustomEvent('auth:session-updated', { detail: { tokens: result.tokens, user: result.user } }));",
6071
6058
  " return undefined;",
6072
6059
  " }}",
6073
6060
  " />",
6074
- " );",
6075
- " }",
6076
- "",
6077
- " return (",
6078
- " <LandingPage",
6079
- " serviceId={activeServiceId}",
6080
- " authApiUrl={AUTH_URL}",
6081
- " config={LANDING_CONFIG}",
6082
- " onCTAClick={() => window.location.assign('/login')}",
6083
- " />",
6084
- " );",
6085
- " }",
6086
- "",
6087
- " if (route.kind === 'login') {",
6088
- " return (",
6089
- " <TopoloLoadingScreen",
6090
- " appId={APP_ID}",
6091
- " serviceId={activeServiceId}",
6092
- " appName={APP_NAME}",
6093
- ` subtitle="Opening your ${escapeDoubleQuoted(signedSurfaceLabel)}..."`,
6094
- " />",
6095
- " );",
6096
- " }",
6097
- "",
6098
- " return (",
6099
- " <TopoloAppShell<NavItem>",
6100
- " appId={APP_ID}",
6101
- " serviceId={activeServiceId}",
6102
- " appName={APP_NAME}",
6103
- " user={shellUser}",
6104
- " orgId={user.activeContextType === 'organization' ? user.activeContextId ?? user.orgId ?? null : null}",
6105
- " activeContextType={user.activeContextType}",
6106
- " activeContextId={user.activeContextId}",
6107
- " activeNavItemId={view === 'getting-started' ? 'getting-started' : 'home'}",
6108
- ` navItems={[{ id: 'home', label: '${escapeSingleQuoted(workspaceTitle)}' }, { id: 'getting-started', label: 'Getting started' }]}`,
6109
- ' navigationMode="topbar"',
6110
- " onNavItemSelect={handleNavSelect}",
6111
- ` title={view === 'getting-started' ? 'Getting started' : '${escapeSingleQuoted(workspaceTitle)}'}`,
6112
- " authUrl={AUTH_URL}",
6113
- " getAccessToken={getAccessToken}",
6114
- " onSignOut={async () => {",
6115
- " await auth.authClient.logout();",
6116
- " window.location.assign('/login');",
6117
- " }}",
6118
- " commandPlaceholder={`Search ${APP_NAME}`}",
6119
- " bugReporterAppName={APP_NAME}",
6120
- " enableCommandPalette",
6121
- " enableBugReporter",
6122
- " >",
6123
- " {view === 'getting-started' ? (",
6124
- " <ScaffoldGettingStarted",
6125
- " serviceId={activeServiceId}",
6126
- " organizationId={user.organization?.id ?? user.orgId ?? ''}",
6127
- " role={user.role}",
6128
- " onOpenWorkspace={openWorkspace}",
6061
+ " }",
6062
+ " loadingFallback={({ loading }) => (",
6063
+ " <TopoloAppLoadingScaffold",
6064
+ " appId={APP_ID}",
6065
+ " serviceId={serviceId}",
6066
+ " appName={APP_NAME}",
6067
+ " layout={{ shell: loading?.shell || 'topbar', content: loading?.content || 'workspace' }}",
6068
+ " title={loading?.title}",
6069
+ " subtitle={`Loading ${APP_NAME}...`}",
6129
6070
  " />",
6130
- " ) : (",
6131
- ' <div className="topolo-starter-workspace">',
6132
- ' <section className="topolo-starter-card" data-tour="workspace-card">',
6133
- ` <h2>${baselineHeading}</h2>`,
6134
- " <p>",
6135
- ` ${baselineDescription}`,
6136
- " </p>",
6137
- " </section>",
6138
- " </div>",
6139
6071
  " )}",
6072
+ " shell={{",
6073
+ " appId: APP_ID,",
6074
+ " authUrl: AUTH_URL,",
6075
+ " navigationMode: 'topbar',",
6076
+ " title: APP_NAME,",
6077
+ " commandPlaceholder: `Search ${APP_NAME}`,",
6078
+ " bugReporterAppName: APP_NAME,",
6079
+ " enableCommandPalette: true,",
6080
+ " enableBugReporter: true,",
6081
+ " }}",
6082
+ " />",
6083
+ " );",
6084
+ "}",
6085
+ "",
6086
+ "function ScaffoldWorkspace() {",
6087
+ " const { user } = useAuth();",
6088
+ " const openWorkspace = useCallback(() => {",
6089
+ " window.history.pushState({}, '', '/workspace');",
6090
+ " window.dispatchEvent(new PopStateEvent('popstate'));",
6091
+ " }, []);",
6092
+ " const openGettingStarted = useCallback(() => {",
6093
+ " window.history.pushState({}, '', '/getting-started');",
6094
+ " window.dispatchEvent(new PopStateEvent('popstate'));",
6095
+ " }, []);",
6096
+ " return (",
6097
+ ' <div className="topolo-starter-workspace">',
6098
+ ' <section className="topolo-starter-card" data-tour="workspace-card">',
6099
+ ` <h2>${baselineHeading}</h2>`,
6100
+ " <p>",
6101
+ ` ${baselineDescription}`,
6102
+ " </p>",
6103
+ " </section>",
6140
6104
  " <IntroTour",
6141
6105
  " appName={APP_NAME}",
6142
- " role={user.role}",
6106
+ " role={user?.role}",
6143
6107
  " onOpenGettingStarted={openGettingStarted}",
6144
6108
  " extraSteps={[",
6145
6109
  " {",
@@ -6152,7 +6116,20 @@ function platformAppTsx(application, options) {
6152
6116
  " },",
6153
6117
  " ]}",
6154
6118
  " />",
6155
- " </TopoloAppShell>",
6119
+ " </div>",
6120
+ " );",
6121
+ "}",
6122
+ "",
6123
+ "function ScaffoldGettingStartedRoute({ serviceId }: { serviceId: string }) {",
6124
+ " const { user, tokens } = useAuth();",
6125
+ " return (",
6126
+ " <ScaffoldGettingStarted",
6127
+ " serviceId={serviceId}",
6128
+ " organizationId={String((user as any)?.organization?.id ?? user?.orgId ?? '')}",
6129
+ " role={user?.role}",
6130
+ " getAccessToken={() => tokens?.accessToken ?? null}",
6131
+ " onOpenWorkspace={() => window.location.assign('/workspace')}",
6132
+ " />",
6156
6133
  " );",
6157
6134
  "}",
6158
6135
  "",
@@ -6160,15 +6137,16 @@ function platformAppTsx(application, options) {
6160
6137
  " serviceId: string;",
6161
6138
  " organizationId: string;",
6162
6139
  " role?: string;",
6140
+ " getAccessToken: () => string | null;",
6163
6141
  " onOpenWorkspace: () => void;",
6164
6142
  "}) {",
6165
- " const { serviceId, organizationId, role, onOpenWorkspace } = props;",
6143
+ " const { serviceId, organizationId, role, getAccessToken, onOpenWorkspace } = props;",
6166
6144
  " const steps: OnboardingGuideStep[] = [",
6167
6145
  " {",
6168
6146
  " id: 'explore-workspace',",
6169
6147
  " title: 'Explore your workspace',",
6170
6148
  " description:",
6171
- " 'Your app renders inside the shared Topolo shell \u2014 launcher, command palette, and account menu are already wired in.',",
6149
+ " 'Your workspace lives in the Topolo shell \u2014 the launcher, command palette, and account menu are all one click away.',",
6172
6150
  " actionLabel: 'Open workspace',",
6173
6151
  " onAction: onOpenWorkspace,",
6174
6152
  " },",
@@ -6181,9 +6159,9 @@ function platformAppTsx(application, options) {
6181
6159
  " },",
6182
6160
  " {",
6183
6161
  " id: 'build-feature',",
6184
- " title: 'Build your first feature',",
6162
+ " title: 'Make it your own',",
6185
6163
  " description:",
6186
- " 'Replace the starter card with your product surface, and gate write actions with service scopes before you ship.',",
6164
+ " 'Open the account menu to adjust your settings and theme \u2014 your preferences stay in sync across every Topolo app.',",
6187
6165
  " },",
6188
6166
  " ];",
6189
6167
  " return (",
@@ -6192,7 +6170,7 @@ function platformAppTsx(application, options) {
6192
6170
  " authApiBaseUrl={`${AUTH_URL}/api`}",
6193
6171
  " organizationId={organizationId}",
6194
6172
  " serviceId={serviceId}",
6195
- " getAccessToken={() => getAccessToken()}",
6173
+ " getAccessToken={getAccessToken}",
6196
6174
  " role={role || undefined}",
6197
6175
  " steps={steps}",
6198
6176
  " eyebrow={`Welcome to ${APP_NAME}`}",
@@ -6208,13 +6186,6 @@ function platformAppTsx(application, options) {
6208
6186
  " );",
6209
6187
  "}",
6210
6188
  "",
6211
- "function getRoute(): AppRoute {",
6212
- " const pathname = window.location.pathname.replace(/\\/+$/, '') || '/';",
6213
- " if (pathname === '/login') return { kind: 'login' };",
6214
- " if (pathname === '/auth/callback') return { kind: 'callback' };",
6215
- " return { kind: 'root' };",
6216
- "}",
6217
- "",
6218
6189
  "function errorMessage(error: unknown): string {",
6219
6190
  " if (error instanceof Error && error.message) return error.message;",
6220
6191
  " return 'Unexpected authentication error.';",
@@ -6761,6 +6732,9 @@ function platformWorkerTs(application, options) {
6761
6732
  " return [",
6762
6733
  " {",
6763
6734
  " type: 'quick-actions',",
6735
+ " snapshot: {",
6736
+ " summary: `${SCAFFOLD_APP_NAME} is ready for workspace operations and exposes health checks for operators.`,",
6737
+ " },",
6764
6738
  " actions: [",
6765
6739
  ` { id: 'open-workspace', label: '${escapeSingleQuoted(openActionLabel)}', link: '/', primary: true },`,
6766
6740
  " { id: 'view-health', label: 'View health', link: '/api/health' },",
@@ -6872,6 +6846,9 @@ function developerWorkerTs(application) {
6872
6846
  " return [",
6873
6847
  " {",
6874
6848
  " type: 'quick-actions',",
6849
+ " snapshot: {",
6850
+ " summary: `${SERVICE_NAME} is ready for workspace operations and exposes health checks for operators.`,",
6851
+ " },",
6875
6852
  " actions: [",
6876
6853
  " { id: 'open-app', label: `Open ${SERVICE_NAME}`, link: '/', primary: true },",
6877
6854
  " { id: 'view-health', label: 'View health', link: '/api/health' },",
@@ -7438,7 +7415,7 @@ function internalDocs(application, options) {
7438
7415
  "",
7439
7416
  "## Architecture",
7440
7417
  "",
7441
- "The generated browser surface in `apps/web` uses `@topolo-io/ui-kit` for the shared landing page, shared login page, auth callback state, and shared signed shell. `apps/web/vite.config.ts` includes the standard React plugin plus React runtime dedupe so linked Topolo packages resolve a single browser React runtime.",
7418
+ "The generated browser surface in `apps/web` uses `@topolo-io/app-funnel` for the shared public landing page, `@topolo-io/app-shell` for shared login, auth callback state, and signed shell, `@topolo-io/app-launcher` for app switching, and `@topolo-io/ui-kit` for shared primitives. `apps/web/vite.config.ts` includes the standard React plugin plus React runtime dedupe so linked Topolo packages resolve a single browser React runtime.",
7442
7419
  options.api ? `The generated app keeps browser service identity in \`${appPath}/apps/web/src/service-identity.ts\` and API runtime service identity in \`${appPath}/apps/api/src/service-identity.ts\`. Runtime code reads the provisioned Auth service id from the API Worker \`SERVICE_ID\` binding and exposes it to the browser through the same-origin service-identity route forwarded by the web Worker.` : `The generated app centralizes browser service identity in \`${appPath}/apps/web/src/service-identity.ts\` and reads Auth identity from the app runtime instead of calling Auth for slug lookups.`,
7443
7420
  "",
7444
7421
  "## Runtime Surfaces",
@@ -7843,9 +7820,6 @@ function escapeHtml(value) {
7843
7820
  function escapeSingleQuoted(value) {
7844
7821
  return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
7845
7822
  }
7846
- function escapeDoubleQuoted(value) {
7847
- return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
7848
- }
7849
7823
  function escapeFrontmatter(value) {
7850
7824
  return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
7851
7825
  }
@@ -7959,17 +7933,17 @@ async function provisionApplicationScaffold(payload) {
7959
7933
  developersOrigin,
7960
7934
  developersApp: {
7961
7935
  id: appId,
7962
- slug: readString(app.slug),
7963
- listingStatus: readString(app.listingStatus),
7964
- organizationId: readString(app.organizationId)
7936
+ slug: readString2(app.slug),
7937
+ listingStatus: readString2(app.listingStatus),
7938
+ organizationId: readString2(app.organizationId)
7965
7939
  },
7966
7940
  authService: service ? {
7967
7941
  id: readRequiredString(
7968
7942
  service.id,
7969
7943
  "Auth registration did not return a service id."
7970
7944
  ),
7971
- status: readString(service.status),
7972
- baseUrl: readString(service.base_url) ?? readString(service.baseUrl)
7945
+ status: readString2(service.status),
7946
+ baseUrl: readString2(service.base_url) ?? readString2(service.baseUrl)
7973
7947
  } : null,
7974
7948
  authConfig: authConfig ? {
7975
7949
  login: authConfig.login === true,
@@ -8035,7 +8009,7 @@ async function resolveServiceIdBySlug(authOrigin, slug) {
8035
8009
  function pickRecord(value) {
8036
8010
  return value && typeof value === "object" ? value : null;
8037
8011
  }
8038
- function readString(value) {
8012
+ function readString2(value) {
8039
8013
  if (typeof value !== "string") {
8040
8014
  return null;
8041
8015
  }
@@ -8043,7 +8017,7 @@ function readString(value) {
8043
8017
  return trimmed ? trimmed : null;
8044
8018
  }
8045
8019
  function readRequiredString(value, message) {
8046
- const resolved = readString(value);
8020
+ const resolved = readString2(value);
8047
8021
  if (!resolved) {
8048
8022
  throw new Error(message);
8049
8023
  }
@@ -8818,7 +8792,9 @@ function buildRows(catalog, overrides, environment = selectedEnvironment()) {
8818
8792
  effectiveUrl,
8819
8793
  overridden: Boolean(configuredUrl && configuredUrl !== defaultUrl),
8820
8794
  environmentSafe: environment !== "staging" || !effectiveUrl || isStagingUrl(effectiveUrl),
8821
- permissions: [...entry.permissions]
8795
+ permissions: [...entry.permissions],
8796
+ apiKeyResource: entry.apiKeyResource,
8797
+ apiKeyResources: [...entry.apiKeyResources]
8822
8798
  };
8823
8799
  }).sort((left, right) => left.id.localeCompare(right.id));
8824
8800
  }
@@ -8828,12 +8804,13 @@ function formatRows2(rows, environment = selectedEnvironment()) {
8828
8804
  const lines = [];
8829
8805
  lines.push(`Environment: ${environment}`);
8830
8806
  lines.push("");
8831
- lines.push(`${"ID".padEnd(idWidth)} ${"URL".padEnd(urlWidth)} OVERRIDE READY`);
8832
- lines.push(`${"-".repeat(idWidth)} ${"-".repeat(urlWidth)} -------- -----`);
8807
+ lines.push(`${"ID".padEnd(idWidth)} ${"URL".padEnd(urlWidth)} OVERRIDE READY API KEY RESOURCE`);
8808
+ lines.push(`${"-".repeat(idWidth)} ${"-".repeat(urlWidth)} -------- ----- ----------------`);
8833
8809
  for (const r of rows) {
8834
8810
  const effectiveUrl = r.effectiveUrl ?? "-";
8811
+ const apiKeyResource = r.apiKeyResources.length > 0 ? r.apiKeyResources.map((resource) => `${resource.resourceType}:${resource.canonicalResourceType}`).join(",") : "-";
8835
8812
  lines.push(
8836
- `${r.id.padEnd(idWidth)} ${effectiveUrl.padEnd(urlWidth)} ${r.overridden ? "yes" : "".padEnd(8)} ${r.environmentSafe ? "yes" : "no"}`
8813
+ `${r.id.padEnd(idWidth)} ${effectiveUrl.padEnd(urlWidth)} ${r.overridden ? "yes" : "".padEnd(8)} ${r.environmentSafe ? "yes" : "no"} ${apiKeyResource}`
8837
8814
  );
8838
8815
  }
8839
8816
  return lines.join("\n");