@topolo/cli 0.1.28 → 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 +401 -455
  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
92
  mail: "https://mail.topolo.app",
93
- mdm: "https://mdm-api.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
  },
@@ -950,12 +935,10 @@ var APPLICATIONS = {
950
935
  {
951
936
  "name": "topolo-mdm-api",
952
937
  "kind": "worker",
953
- "apiUrl": "https://mdm-api.topolo.app",
954
- "domains": [
955
- "mdm-api.topolo.app"
956
- ],
938
+ "apiUrl": null,
939
+ "domains": [],
957
940
  "routes": [],
958
- "serviceId": "mdm"
941
+ "serviceId": null
959
942
  },
960
943
  {
961
944
  "name": "topolo-mdm-console",
@@ -965,7 +948,7 @@ var APPLICATIONS = {
965
948
  "mdm.topolo.app"
966
949
  ],
967
950
  "routes": [],
968
- "serviceId": null
951
+ "serviceId": "mdm"
969
952
  }
970
953
  ]
971
954
  },
@@ -1098,20 +1081,23 @@ var APPLICATIONS = {
1098
1081
  {
1099
1082
  "name": "topolo-one",
1100
1083
  "kind": "worker",
1101
- "apiUrl": null,
1102
- "domains": [],
1103
- "routes": [],
1084
+ "apiUrl": "https://one.topolo.app",
1085
+ "domains": [
1086
+ "one.topolo.app"
1087
+ ],
1088
+ "routes": [
1089
+ "one.topolo.app/*"
1090
+ ],
1104
1091
  "serviceId": null
1105
1092
  },
1106
1093
  {
1107
1094
  "name": "topolo-one-api",
1108
1095
  "kind": "worker",
1109
- "apiUrl": "https://one.topolo.app",
1096
+ "apiUrl": "https://www.topolo.app",
1110
1097
  "domains": [
1111
- "one.topolo.app"
1098
+ "www.topolo.app"
1112
1099
  ],
1113
1100
  "routes": [
1114
- "one.topolo.app/*",
1115
1101
  "www.topolo.app/*"
1116
1102
  ],
1117
1103
  "serviceId": "one"
@@ -1221,36 +1207,6 @@ var APPLICATIONS = {
1221
1207
  }
1222
1208
  ]
1223
1209
  },
1224
- "pulse": {
1225
- "id": "pulse",
1226
- "appDir": "TopoloPulse",
1227
- "name": "topolo-pulse",
1228
- "packageName": "topolo-pulse",
1229
- "productionUrl": "https://pulse.topolo.app",
1230
- "services": [
1231
- "pulse"
1232
- ],
1233
- "deployTargets": [
1234
- {
1235
- "name": "topolo-pulse",
1236
- "kind": "worker",
1237
- "apiUrl": "https://pulse.topolo.app",
1238
- "domains": [
1239
- "pulse.topolo.app"
1240
- ],
1241
- "routes": [],
1242
- "serviceId": "pulse"
1243
- },
1244
- {
1245
- "name": "topolo-pulse-api",
1246
- "kind": "worker",
1247
- "apiUrl": null,
1248
- "domains": [],
1249
- "routes": [],
1250
- "serviceId": null
1251
- }
1252
- ]
1253
- },
1254
1210
  "quro": {
1255
1211
  "id": "quro",
1256
1212
  "appDir": "TopoloQuro",
@@ -1316,17 +1272,15 @@ var APPLICATIONS = {
1316
1272
  "roadmapper.topolo.app"
1317
1273
  ],
1318
1274
  "routes": [],
1319
- "serviceId": null
1275
+ "serviceId": "roadmapper"
1320
1276
  },
1321
1277
  {
1322
1278
  "name": "topolo-roadmapper-api",
1323
1279
  "kind": "worker",
1324
- "apiUrl": "https://roadmapper-api.topolo.app",
1325
- "domains": [
1326
- "roadmapper-api.topolo.app"
1327
- ],
1280
+ "apiUrl": null,
1281
+ "domains": [],
1328
1282
  "routes": [],
1329
- "serviceId": "roadmapper"
1283
+ "serviceId": null
1330
1284
  }
1331
1285
  ]
1332
1286
  },
@@ -1378,17 +1332,15 @@ var APPLICATIONS = {
1378
1332
  "studio.topolo.app"
1379
1333
  ],
1380
1334
  "routes": [],
1381
- "serviceId": null
1335
+ "serviceId": "social_studio"
1382
1336
  },
1383
1337
  {
1384
1338
  "name": "topolo-social-studio-api",
1385
1339
  "kind": "worker",
1386
- "apiUrl": "https://studio-api.topolo.app",
1387
- "domains": [
1388
- "studio-api.topolo.app"
1389
- ],
1340
+ "apiUrl": null,
1341
+ "domains": [],
1390
1342
  "routes": [],
1391
- "serviceId": "social_studio"
1343
+ "serviceId": null
1392
1344
  },
1393
1345
  {
1394
1346
  "name": "topolo-social-studio-queue",
@@ -1418,17 +1370,15 @@ var APPLICATIONS = {
1418
1370
  "socialize.topolo.app"
1419
1371
  ],
1420
1372
  "routes": [],
1421
- "serviceId": null
1373
+ "serviceId": "socialize"
1422
1374
  },
1423
1375
  {
1424
1376
  "name": "topolo-socialize-api",
1425
1377
  "kind": "worker",
1426
- "apiUrl": "https://socialize-api.topolo.app",
1427
- "domains": [
1428
- "socialize-api.topolo.app"
1429
- ],
1378
+ "apiUrl": null,
1379
+ "domains": [],
1430
1380
  "routes": [],
1431
- "serviceId": "socialize"
1381
+ "serviceId": null
1432
1382
  },
1433
1383
  {
1434
1384
  "name": "topolo-socialize-scheduler",
@@ -1629,12 +1579,10 @@ var APPLICATIONS = {
1629
1579
  {
1630
1580
  "name": "topolo-web-api",
1631
1581
  "kind": "worker",
1632
- "apiUrl": "https://web-api.topolo.app",
1633
- "domains": [
1634
- "web-api.topolo.app"
1635
- ],
1582
+ "apiUrl": null,
1583
+ "domains": [],
1636
1584
  "routes": [],
1637
- "serviceId": "web"
1585
+ "serviceId": null
1638
1586
  },
1639
1587
  {
1640
1588
  "name": "topolo-web-runtime",
@@ -1657,12 +1605,12 @@ var APPLICATIONS = {
1657
1605
  "web.topolo.app"
1658
1606
  ],
1659
1607
  "routes": [],
1660
- "serviceId": null
1608
+ "serviceId": "web"
1661
1609
  }
1662
1610
  ]
1663
1611
  }
1664
1612
  };
1665
- var APPLICATION_REQUIREMENTS_VERSION = "2026-05-01.1";
1613
+ var APPLICATION_REQUIREMENTS_VERSION = "2026-06-07.1";
1666
1614
  var APPLICATION_REQUIREMENTS = [
1667
1615
  {
1668
1616
  id: "platform-metadata",
@@ -1718,16 +1666,20 @@ var APPLICATION_REQUIREMENTS = [
1718
1666
  summary: "Browser applications should feel like one platform and should not create isolated navigation or account surfaces.",
1719
1667
  appliesTo: ["browser"],
1720
1668
  evidence: [
1721
- "@topolo/ui-kit is used for the platform shell where the app has authenticated UI",
1722
- "The shared launcher/app switcher is reachable from authenticated app chrome",
1723
- "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",
1724
1674
  "Authenticated app body content stays inside the viewport on mobile and desktop widths"
1725
1675
  ],
1726
1676
  implementation: [
1727
- "Use TopoloAppShell for authenticated app layouts",
1728
- "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",
1729
1679
  "Pass app-specific account actions as additive menu items instead of forking dropdowns",
1730
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",
1731
1683
  "Keep mobile and desktop shell behavior aligned with the platform UI kit",
1732
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"
1733
1685
  ]
@@ -1806,11 +1758,13 @@ var APPLICATION_REQUIREMENTS = [
1806
1758
  evidence: [
1807
1759
  "The app-owned API exposes GET /api/widget",
1808
1760
  "The endpoint returns TopoloWidgetApiResponse from @topolo/sdk",
1761
+ "Every returned widget includes snapshot.summary with app-owned operational prose",
1809
1762
  "The endpoint derives organization/user context from the app auth boundary"
1810
1763
  ],
1811
1764
  implementation: [
1812
1765
  "Import createTopoloWidgetResponse from @topolo/sdk in the app backend",
1813
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",
1814
1768
  "Add a route-level test that validates the response with validateTopoloWidgetResponse"
1815
1769
  ]
1816
1770
  },
@@ -2052,10 +2006,7 @@ function resolveCatalogServiceUrl(entry, options = {}) {
2052
2006
  if (override) return override;
2053
2007
  const apiBaseUrl = firstString(entry.apiBaseUrl, entry.api_base_url);
2054
2008
  if (apiBaseUrl) return apiBaseUrl;
2055
- const bundledUrl = firstBundledServiceUrl(keys);
2056
- const liveUrl = firstString(entry.baseUrl);
2057
- if (bundledUrl && (!liveUrl || sameUrlEnvironment(bundledUrl, liveUrl))) return bundledUrl;
2058
- return liveUrl ?? bundledUrl ?? null;
2009
+ return null;
2059
2010
  }
2060
2011
  function resolveServiceUrl(service, overrides) {
2061
2012
  const override = resolveServiceUrlOverride([service], overrides);
@@ -2097,37 +2048,17 @@ function resolveServiceUrlOverride(keys, overrides) {
2097
2048
  }
2098
2049
  return null;
2099
2050
  }
2100
- function firstBundledServiceUrl(keys) {
2101
- for (const key of keys) {
2102
- const url = DEFAULT_SERVICE_URLS[key];
2103
- if (url) return url;
2104
- }
2105
- return null;
2106
- }
2107
2051
  function firstString(...values) {
2108
2052
  for (const value of values) {
2109
2053
  if (typeof value === "string" && value.trim()) return value.trim();
2110
2054
  }
2111
2055
  return null;
2112
2056
  }
2113
- function sameUrlEnvironment(left, right) {
2114
- return urlEnvironment(left) === urlEnvironment(right);
2115
- }
2116
- function urlEnvironment(value) {
2117
- try {
2118
- const url = new URL(value);
2119
- const host = url.hostname.toLowerCase();
2120
- if (host === "localhost" || host === "127.0.0.1") return "local";
2121
- if (host.endsWith(".stg.topolo.us") || host.includes("topolo-staging")) return "staging";
2122
- if (host.endsWith(".topolo.app") || host.endsWith(".topolo.us")) return "production";
2123
- return "external";
2124
- } catch {
2125
- return "external";
2126
- }
2127
- }
2128
2057
  var WRITE_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
2129
2058
  var TOPOLO_ACTION_ID_HEADER = "X-Topolo-Action-Id";
2130
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";
2131
2062
  var POSITIVE_CACHE_TTL_MS = 5 * 60 * 1e3;
2132
2063
  var NEGATIVE_CACHE_TTL_MS = 60 * 1e3;
2133
2064
  var CATALOG_CACHE_TTL_MS = 60 * 1e3;
@@ -2206,6 +2137,18 @@ var TopoloClient = class _TopoloClient {
2206
2137
  if (opts.headers) {
2207
2138
  for (const [k, v] of Object.entries(opts.headers)) headers.set(k, v);
2208
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
+ }
2209
2152
  const controller = new AbortController();
2210
2153
  const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);
2211
2154
  const signal = opts.signal ? mergeSignals(opts.signal, controller.signal) : controller.signal;
@@ -2295,7 +2238,7 @@ var TopoloClient = class _TopoloClient {
2295
2238
  async resolveLiveCallableService(serviceKey) {
2296
2239
  const catalog = await this.listServices();
2297
2240
  const match = catalog.find((entry) => catalogEntryMatches(entry, serviceKey));
2298
- if (!match || !isCallableCatalogEntry(match)) return null;
2241
+ if (!match) return null;
2299
2242
  const baseUrl = resolveCatalogServiceUrl(match, {
2300
2243
  serviceKey,
2301
2244
  overrides: this.serviceUrls
@@ -2388,18 +2331,24 @@ var TopoloClient = class _TopoloClient {
2388
2331
  const serviceId = typeof s.service_id === "string" ? s.service_id : typeof s.id === "string" ? s.id : null;
2389
2332
  if (!serviceId) continue;
2390
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] : [];
2391
2339
  entries.push({
2392
2340
  serviceId,
2393
2341
  slug: typeof s.app_slug === "string" ? s.app_slug : null,
2394
2342
  name: typeof s.name === "string" ? s.name : null,
2395
2343
  description: typeof s.description === "string" ? s.description : null,
2396
- baseUrl: typeof s.base_url === "string" ? s.base_url : null,
2397
2344
  apiBaseUrl: typeof s.api_base_url === "string" ? s.api_base_url : typeof s.apiBaseUrl === "string" ? s.apiBaseUrl : null,
2398
2345
  launchUrl: typeof s.launch_url === "string" ? s.launch_url : typeof s.launchUrl === "string" ? s.launchUrl : null,
2399
2346
  status: typeof s.status === "string" ? s.status : null,
2400
2347
  surfaceKind: typeof s.surface_kind === "string" ? s.surface_kind : null,
2401
2348
  launchable: s.launchable === true,
2402
- permissions
2349
+ permissions,
2350
+ apiKeyResource,
2351
+ apiKeyResources
2403
2352
  });
2404
2353
  }
2405
2354
  this.serviceCatalogCache = {
@@ -2480,13 +2429,14 @@ var TopoloClient = class _TopoloClient {
2480
2429
  slug: app.id,
2481
2430
  name: app.name ?? app.id,
2482
2431
  description: null,
2483
- baseUrl: app.productionUrl ?? null,
2484
2432
  apiBaseUrl: null,
2485
2433
  launchUrl: app.productionUrl ?? null,
2486
2434
  status: "bundled",
2487
2435
  surfaceKind: "application",
2488
2436
  launchable: Boolean(app.productionUrl),
2489
- permissions: []
2437
+ permissions: [],
2438
+ apiKeyResource: null,
2439
+ apiKeyResources: []
2490
2440
  }));
2491
2441
  }
2492
2442
  /**
@@ -2535,6 +2485,46 @@ function resolveConfiguredServiceId(slug, serviceIds) {
2535
2485
  }
2536
2486
  return null;
2537
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
+ }
2538
2528
  function resolveConfiguredServiceUrl(slug, serviceUrls) {
2539
2529
  for (const serviceKey of serviceKeyCandidates2(slug)) {
2540
2530
  const configured = serviceUrls?.[serviceKey];
@@ -2573,13 +2563,6 @@ function catalogEntryLookupKeys(entry) {
2573
2563
  }
2574
2564
  return [...keys];
2575
2565
  }
2576
- function isCallableCatalogEntry(entry) {
2577
- const kind = normalizeLookupKey(entry.surfaceKind || "");
2578
- if (kind === "api" || kind === "technical_service" || kind === "service") return true;
2579
- return Boolean(
2580
- (entry.baseUrl || entry.apiBaseUrl) && catalogEntryLookupKeys(entry).some((key) => Object.prototype.hasOwnProperty.call(DEFAULT_SERVICE_URLS, key))
2581
- );
2582
- }
2583
2566
  function normalizeActionCatalogEntry(item) {
2584
2567
  if (!item || typeof item !== "object" || Array.isArray(item)) return null;
2585
2568
  const a = item;
@@ -3530,7 +3513,7 @@ function registerApiCommand(program2) {
3530
3513
  ).option("-d, --data <json>", "JSON body (for POST/PUT/PATCH)").option(
3531
3514
  "-q, --query <kv...>",
3532
3515
  "Query params as key=value (repeatable), e.g. --query source=cli page=2"
3533
- ).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(
3534
3517
  async (service, method, path4, opts) => {
3535
3518
  const mode = resolveOutputMode(opts.json);
3536
3519
  try {
@@ -3561,6 +3544,7 @@ function registerApiCommand(program2) {
3561
3544
  }
3562
3545
  }
3563
3546
  const query = parseQueryPairs(opts.query);
3547
+ const resource = parseResourceOption(opts.resourceType, opts.resourceId);
3564
3548
  const topolo = await buildClient();
3565
3549
  if (!topolo) {
3566
3550
  throw new Error(
@@ -3573,6 +3557,7 @@ function registerApiCommand(program2) {
3573
3557
  path: path4,
3574
3558
  body,
3575
3559
  ...query ? { query } : {},
3560
+ ...resource ? { resource } : {},
3576
3561
  confirm: opts.confirm === true
3577
3562
  });
3578
3563
  emit(result, mode, () => JSON.stringify(result, null, 2));
@@ -3582,6 +3567,15 @@ function registerApiCommand(program2) {
3582
3567
  }
3583
3568
  );
3584
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
+ }
3585
3579
  function resolveConfiguredServiceUrl2(service, serviceUrls) {
3586
3580
  const normalized = service.trim().toLowerCase();
3587
3581
  const withoutTopolo = normalized.startsWith("topolo-") ? normalized.slice("topolo-".length) : normalized;
@@ -3627,7 +3621,7 @@ import {
3627
3621
  import path from "path";
3628
3622
  var EVIDENCE_FRESHNESS_MS = 36 * 60 * 60 * 1e3;
3629
3623
  var REQUIRED_AUTH_CLIENT_VERSION = "1.1.9";
3630
- var REQUIRED_UI_KIT_VERSION = "0.4.67";
3624
+ var REQUIRED_UI_KIT_VERSION = "0.4.196";
3631
3625
  function readEvidenceFile(appRoot) {
3632
3626
  const candidate = path.join(appRoot, ".topolo", "evidence.json");
3633
3627
  if (!isFile(candidate)) return null;
@@ -3903,7 +3897,7 @@ function enrichFinding(application, finding2, evidence) {
3903
3897
  nextActions: []
3904
3898
  });
3905
3899
  }
3906
- if (shellEvidence.length === 0 && hasDocumentationSurface(application, evidence)) {
3900
+ if (hasDocumentationSurface(application, evidence)) {
3907
3901
  return mergeFinding(finding2, {
3908
3902
  status: "met",
3909
3903
  reason: "This is a documentation surface rather than an authenticated app workspace, so the shared application shell requirement does not apply.",
@@ -3921,7 +3915,7 @@ function enrichFinding(application, finding2, evidence) {
3921
3915
  if (shellEvidence.includes(`dependency-version=@topolo-io/ui-kit<${REQUIRED_UI_KIT_VERSION}`)) {
3922
3916
  return mergeFinding(finding2, {
3923
3917
  status: "needs_review",
3924
- 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.`,
3925
3919
  evidence: shellEvidence,
3926
3920
  nextActions: [
3927
3921
  `Update @topolo-io/ui-kit to ${REQUIRED_UI_KIT_VERSION}`,
@@ -3954,13 +3948,61 @@ function enrichFinding(application, finding2, evidence) {
3954
3948
  ]
3955
3949
  });
3956
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
+ }
3957
3999
  const usesCanonicalAppShell = shellEvidence.includes("source=TopoloAppShell");
3958
4000
  const usesSharedAccountPrimitive = shellEvidence.includes("source=TopoloAccountMenu");
3959
4001
  const usesSharedSuite = usesCanonicalAppShell || usesSharedAccountPrimitive;
3960
4002
  if (!usesSharedSuite) {
3961
4003
  return mergeFinding(finding2, {
3962
4004
  status: "needs_review",
3963
- 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.",
3964
4006
  evidence: shellEvidence,
3965
4007
  nextActions: [
3966
4008
  "Use TopoloAppShell for authenticated first-party app layouts",
@@ -4310,12 +4352,30 @@ function sharedShellEvidence(evidence) {
4310
4352
  const deps = dependencyNames(evidence);
4311
4353
  const text = evidence.source.text;
4312
4354
  const items = [];
4355
+ if (deps.has("@topolo-io/app-shell")) items.push("dependency=@topolo-io/app-shell");
4313
4356
  if (deps.has("@topolo-io/ui-kit")) items.push("dependency=@topolo-io/ui-kit");
4314
4357
  if (packageDependencyVersions(evidence, "@topolo-io/ui-kit").some((version) => isStaleDependencyVersion(version, REQUIRED_UI_KIT_VERSION))) {
4315
4358
  items.push(`dependency-version=@topolo-io/ui-kit<${REQUIRED_UI_KIT_VERSION}`);
4316
4359
  }
4317
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");
4318
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
+ }
4319
4379
  if (text.includes(["Topolo", "Workspace", "Shell"].join(""))) items.push("forbidden=workspace-shell");
4320
4380
  if (/\bTopoloShell\b/.test(text)) items.push("legacy=TopoloShellFrame");
4321
4381
  if (text.includes("TopoloAccountMenu")) items.push("source=TopoloAccountMenu");
@@ -4323,6 +4383,12 @@ function sharedShellEvidence(evidence) {
4323
4383
  if (/\baccountMenuOpen\b|\bsetAccountMenuOpen\b|\buserMenuOpen\b|\bUserMenu\b|user-menu/.test(text)) {
4324
4384
  items.push("local=account-menu");
4325
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
+ }
4326
4392
  return unique(items);
4327
4393
  }
4328
4394
  function hasDocumentationSurface(application, evidence) {
@@ -4331,7 +4397,7 @@ function hasDocumentationSurface(application, evidence) {
4331
4397
  }
4332
4398
  function hasLocalBrowserSurface(evidence) {
4333
4399
  const deps = dependencyNames(evidence);
4334
- 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")) {
4335
4401
  return true;
4336
4402
  }
4337
4403
  return evidence.source.files.some(
@@ -4775,8 +4841,11 @@ function scaffoldPackages(options) {
4775
4841
  }
4776
4842
  const packages = /* @__PURE__ */ new Set();
4777
4843
  if (options.browser) {
4778
- 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");
4779
4847
  packages.add("@topolo-io/auth-client");
4848
+ packages.add("@topolo-io/ui-kit");
4780
4849
  }
4781
4850
  if (options.api) {
4782
4851
  packages.add("@topolo-io/worker-runtime");
@@ -5063,8 +5132,8 @@ function scaffoldFiles(options) {
5063
5132
  path: `${browserBase}/src/App.tsx`,
5064
5133
  purpose: "Authenticated browser surface.",
5065
5134
  requiredSignals: [
5066
- "imports shared TopoloAppShell from @topolo-io/ui-kit",
5067
- "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",
5068
5137
  "keeps launcher and account-menu behavior on the shared platform surface",
5069
5138
  "passes app-specific account actions through accountMenuItems or TopoloAccountMenu"
5070
5139
  ],
@@ -5280,10 +5349,14 @@ function packageJson(application, target, options) {
5280
5349
  devDependencies["@vitejs/plugin-react"] = "^4.7.0";
5281
5350
  if (target === "platform") {
5282
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);
5283
5355
  dependencies["@topolo-io/design-tokens"] = topoloPackageVersion("@topolo-io/design-tokens", target);
5284
5356
  dependencies["@topolo-io/onboarding"] = topoloPackageVersion("@topolo-io/onboarding", target);
5285
5357
  dependencies["@topolo-io/tour"] = topoloPackageVersion("@topolo-io/tour", target);
5286
5358
  dependencies["@topolo-io/ui-kit"] = topoloPackageVersion("@topolo-io/ui-kit", target);
5359
+ dependencies["react-router-dom"] = "^7.6.2";
5287
5360
  } else {
5288
5361
  dependencies["@topolo/login"] = topoloPackageVersion("@topolo/login", target);
5289
5362
  }
@@ -5327,6 +5400,9 @@ function topoloPackageVersion(packageName, target) {
5327
5400
  const platformPackageFolders = {
5328
5401
  "@topolo/sdk": "topolo-sdk",
5329
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",
5330
5406
  "@topolo-io/auth-client": "topolo-auth-client",
5331
5407
  "@topolo-io/auth-middleware": "topolo-auth-middleware",
5332
5408
  "@topolo-io/worker-runtime": "topolo-worker-runtime",
@@ -5412,7 +5488,10 @@ function readme(application, target, options) {
5412
5488
  lines.push(
5413
5489
  "## Shared Topolo Surface",
5414
5490
  "",
5415
- "- `@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.",
5416
5495
  "- `@topolo-io/auth-client` owns browser auth, callback exchange, token refresh, and logout.",
5417
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.",
5418
5497
  "- `vite.config.ts` includes the standard React plugin plus React runtime dedupe so linked Topolo packages resolve one browser React runtime."
@@ -5834,13 +5913,13 @@ function platformAppTsx(application, options) {
5834
5913
  const tagline = isPersonal ? "Topolo personal app" : "Topolo workspace";
5835
5914
  const workspaceTitle = isPersonal ? "Personal app" : "Workspace";
5836
5915
  const shellLabel = isPersonal ? "personal app shell" : "workspace shell";
5837
- const signedSurfaceLabel = isPersonal ? "personal app" : "workspace";
5838
5916
  const baselineHeading = isPersonal ? "Shared personal baseline" : "Shared workspace baseline";
5839
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.";
5840
5918
  return [
5841
5919
  "import { Component, useCallback, useEffect, useMemo, useState, type ErrorInfo, type ReactNode } from 'react';",
5842
- "import { createBrowserAuthSessionController, type AuthUser } from '@topolo-io/auth-client';",
5843
- "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';",
5844
5923
  "import { GettingStarted, type OnboardingGuideStep } from '@topolo-io/onboarding';",
5845
5924
  "import { IntroTour } from '@topolo-io/tour';",
5846
5925
  "import { SCAFFOLD_APP_ID, SCAFFOLD_APP_NAME, SCAFFOLD_SERVICE_SLUG, getBrowserAuthBaseUrl, resolveAppServiceId } from './service-identity';",
@@ -5849,11 +5928,6 @@ function platformAppTsx(application, options) {
5849
5928
  "// can attach the last few errors to any submitted bug report.",
5850
5929
  "setupBugReporterErrorCapture();",
5851
5930
  "",
5852
- "type NavItem = 'home' | 'getting-started';",
5853
- "type WorkspaceView = 'workspace' | 'getting-started';",
5854
- "type AppRoute = { kind: 'root' } | { kind: 'login' } | { kind: 'callback' };",
5855
- "type BrowserAuthModule = ReturnType<typeof createBrowserAuthSessionController>;",
5856
- "",
5857
5931
  "const APP_ID = SCAFFOLD_APP_ID;",
5858
5932
  "const APP_NAME = SCAFFOLD_APP_NAME;",
5859
5933
  "const AUTH_URL = getBrowserAuthBaseUrl();",
@@ -5875,69 +5949,17 @@ function platformAppTsx(application, options) {
5875
5949
  " authenticated_home_path: '/',",
5876
5950
  "};",
5877
5951
  "",
5878
- "let activeAuthServiceId = '';",
5879
- "let activeAuthModule: BrowserAuthModule | null = null;",
5880
- "",
5881
- "function getAuthModule(serviceId: string): BrowserAuthModule {",
5882
- " const normalizedServiceId = serviceId.trim();",
5883
- " if (!normalizedServiceId) throw new Error(`Missing Auth service id for ${SCAFFOLD_SERVICE_SLUG}`);",
5884
- " if (!activeAuthModule || activeAuthServiceId !== normalizedServiceId) {",
5885
- " activeAuthServiceId = normalizedServiceId;",
5886
- " activeAuthModule = createBrowserAuthSessionController({",
5887
- " authServiceUrl: AUTH_URL,",
5888
- " serviceId: normalizedServiceId,",
5889
- " devSessionKey: `${normalizedServiceId}_session`,",
5890
- " devSessionStorageEnabled: import.meta.env.DEV,",
5891
- " callbackPath: '/auth/callback',",
5892
- " debug: import.meta.env.DEV,",
5893
- " });",
5894
- " }",
5895
- " return activeAuthModule;",
5896
- "}",
5897
- "",
5898
- "function getAccessToken(): string | null {",
5899
- " return activeAuthModule?.accessTokenMemory.get() ?? activeAuthModule?.accessTokenSession.get() ?? null;",
5900
- "}",
5901
- "",
5902
- "function toShellUser(user: AuthUser | null): TopoloAppShellUser | null {",
5903
- " if (!user) return null;",
5904
- " return {",
5905
- " id: user.id,",
5906
- " name: user.name ?? user.email,",
5907
- " email: user.email,",
5908
- " role: user.role,",
5909
- " organizationName: user.organization?.name ?? user.orgSlug ?? null,",
5910
- " organizationId: user.organization?.id ?? user.orgId ?? null,",
5911
- " };",
5912
- "}",
5913
- "",
5914
5952
  "export default function App() {",
5915
5953
  " return (",
5916
5954
  " <AppErrorBoundary>",
5917
- " <AppRoot />",
5955
+ " <CanonicalAppRoot />",
5918
5956
  " </AppErrorBoundary>",
5919
5957
  " );",
5920
5958
  "}",
5921
5959
  "",
5922
- "function AppRoot() {",
5923
- " const route = getRoute();",
5924
- " const [user, setUser] = useState<AuthUser | null>(null);",
5960
+ "function CanonicalAppRoot() {",
5925
5961
  " const [serviceId, setServiceId] = useState('');",
5926
5962
  " const [serviceIdentityError, setServiceIdentityError] = useState('');",
5927
- " const [loading, setLoading] = useState(true);",
5928
- " const [authError, setAuthError] = useState('');",
5929
- " const [loginAttempted, setLoginAttempted] = useState(false);",
5930
- " const [view, setView] = useState<WorkspaceView>('workspace');",
5931
- " const auth = useMemo(() => serviceId ? getAuthModule(serviceId) : null, [serviceId]);",
5932
- " const openWorkspace = useCallback(() => setView('workspace'), []);",
5933
- " const openGettingStarted = useCallback(() => {",
5934
- " setView('getting-started');",
5935
- " if (typeof window !== 'undefined') window.scrollTo({ top: 0, behavior: 'smooth' });",
5936
- " }, []);",
5937
- " const handleNavSelect = useCallback((id: NavItem) => {",
5938
- " if (id === 'getting-started') openGettingStarted();",
5939
- " else openWorkspace();",
5940
- " }, [openGettingStarted, openWorkspace]);",
5941
5963
  "",
5942
5964
  " useEffect(() => {",
5943
5965
  " let active = true;",
@@ -5949,229 +5971,139 @@ function platformAppTsx(application, options) {
5949
5971
  " .catch((error) => {",
5950
5972
  " if (!active) return;",
5951
5973
  " setServiceIdentityError(errorMessage(error));",
5952
- " setLoading(false);",
5953
5974
  " });",
5954
5975
  " return () => {",
5955
5976
  " active = false;",
5956
5977
  " };",
5957
5978
  " }, []);",
5958
5979
  "",
5959
- " useEffect(() => {",
5960
- " if (!auth) return;",
5961
- " let active = true;",
5962
- " const syncAuth = async () => {",
5963
- " const publicRoute = route.kind === 'root' || route.kind === 'login';",
5964
- " if (publicRoute) setLoading(false);",
5965
- " else setLoading(true);",
5966
- " setAuthError('');",
5967
- "",
5968
- " if (route.kind === 'callback') {",
5969
- " const result = await auth.authClient.handleAuthCallbackAsync(window.location.href);",
5970
- " if (!active) return;",
5971
- " if (!result.authenticated) {",
5972
- " setAuthError(result.error || 'Authentication failed.');",
5973
- " setLoading(false);",
5974
- " return;",
5975
- " }",
5976
- " setUser(result.user ?? null);",
5977
- " window.location.replace('/');",
5978
- " return;",
5979
- " }",
5980
- "",
5981
- " const nextUser = await auth.authClient.initialize({",
5982
- " cookieRefresh: publicRoute ? 'background' : 'blocking',",
5983
- " });",
5984
- " if (!active) return;",
5985
- " setUser(nextUser);",
5986
- " setLoading(false);",
5987
- " };",
5988
- "",
5989
- " void syncAuth().catch((error) => {",
5990
- " if (!active) return;",
5991
- " setAuthError(errorMessage(error));",
5992
- " setLoading(false);",
5993
- " });",
5994
- " return () => {",
5995
- " active = false;",
5996
- " };",
5997
- " }, [auth, route.kind]);",
5998
- "",
5999
- " useEffect(() => {",
6000
- " if (!auth) return;",
6001
- " const handleSessionUpdated = () => {",
6002
- " const nextUser = auth.authClient.getHydratedCurrentUser() || auth.authClient.getTokenIdentity();",
6003
- " if (nextUser) setUser(nextUser);",
6004
- " };",
6005
- " window.addEventListener('auth:session-updated', handleSessionUpdated);",
6006
- " return () => window.removeEventListener('auth:session-updated', handleSessionUpdated);",
6007
- " }, [auth]);",
6008
- "",
6009
- " useEffect(() => {",
6010
- " if (!loading && user && route.kind === 'login') {",
6011
- " window.location.replace('/');",
6012
- " }",
6013
- " }, [loading, route.kind, user]);",
6014
- "",
6015
- " const shellUser = useMemo(() => toShellUser(user), [user]);",
6016
- " const activeServiceId = serviceId || SCAFFOLD_SERVICE_SLUG;",
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]);",
6017
5988
  "",
6018
- " if (!auth) {",
6019
- " if (serviceIdentityError) {",
6020
- " return (",
6021
- " <TopoloAuthFlowScreen",
6022
- " appName={APP_NAME}",
6023
- " appId={APP_ID}",
6024
- " serviceId={SCAFFOLD_SERVICE_SLUG}",
6025
- ' status="error"',
6026
- ' title="Service identity unavailable"',
6027
- " message={serviceIdentityError}",
6028
- ' actionLabel="Retry"',
6029
- " onAction={() => window.location.reload()}",
6030
- " />",
6031
- " );",
6032
- " }",
6033
- " return (",
6034
- " <TopoloLoadingScreen",
6035
- " appId={APP_ID}",
6036
- " serviceId={SCAFFOLD_SERVICE_SLUG}",
6037
- " appName={APP_NAME}",
6038
- " subtitle={`Loading ${APP_NAME}...`}",
6039
- " />",
6040
- " );",
6041
- " }",
6042
- "",
6043
- " if (route.kind === 'callback') {",
6044
- " if (loading) {",
6045
- " return (",
6046
- " <TopoloLoadingScreen",
6047
- " appId={APP_ID}",
6048
- " serviceId={activeServiceId}",
6049
- " appName={APP_NAME}",
6050
- ' subtitle="Completing secure sign-in..."',
6051
- " />",
6052
- " );",
6053
- " }",
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]);",
6054
6003
  "",
6004
+ " if (serviceIdentityError) {",
6055
6005
  " return (",
6056
6006
  " <TopoloAuthFlowScreen",
6057
6007
  " appName={APP_NAME}",
6058
6008
  " appId={APP_ID}",
6059
- " serviceId={activeServiceId}",
6009
+ " serviceId={SCAFFOLD_SERVICE_SLUG}",
6060
6010
  ' status="error"',
6061
- ' title="Authentication failed"',
6062
- " message={authError || `We could not complete sign-in for ${APP_NAME}.`}",
6063
- ' actionLabel="Back to sign in"',
6064
- " onAction={() => window.location.replace('/login')}",
6011
+ ' title="Service identity unavailable"',
6012
+ " message={serviceIdentityError}",
6013
+ ' actionLabel="Retry"',
6014
+ " onAction={() => window.location.reload()}",
6065
6015
  " />",
6066
6016
  " );",
6067
6017
  " }",
6068
6018
  "",
6069
- " if (loading) {",
6019
+ " if (!auth) {",
6070
6020
  " return (",
6071
- " <TopoloLoadingScreen",
6021
+ " <TopoloAppLoadingScaffold",
6072
6022
  " appId={APP_ID}",
6073
- " serviceId={activeServiceId}",
6023
+ " serviceId={SCAFFOLD_SERVICE_SLUG}",
6074
6024
  " appName={APP_NAME}",
6025
+ " layout={{ shell: 'topbar', content: 'workspace' }}",
6075
6026
  " subtitle={`Loading ${APP_NAME}...`}",
6076
6027
  " />",
6077
6028
  " );",
6078
6029
  " }",
6079
6030
  "",
6080
- " if (!user) {",
6081
- " if (route.kind === 'login') {",
6082
- " 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={",
6083
6047
  " <LoginPage",
6084
- " serviceId={activeServiceId}",
6048
+ " serviceId={serviceId}",
6085
6049
  " authApiUrl={AUTH_URL}",
6086
6050
  " config={LOGIN_CONFIG}",
6087
- " isLoading={loginAttempted}",
6088
6051
  " onLogin={async (provider) => {",
6089
- " setLoginAttempted(true);",
6090
- " try {",
6091
- " await auth.authClient.login(provider || 'topolo');",
6092
- " } catch {",
6093
- " setLoginAttempted(false);",
6094
- " }",
6052
+ " await auth.login(provider || 'topolo');",
6095
6053
  " }}",
6096
6054
  " onPasswordLogin={async (request) => {",
6097
- " const result = await auth.authClient.loginWithPassword(request);",
6098
- " if (!result.authenticated) {",
6099
- " return result;",
6100
- " }",
6101
- " setUser(result.user ?? null);",
6102
- " 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 } }));",
6103
6058
  " return undefined;",
6104
6059
  " }}",
6105
6060
  " />",
6106
- " );",
6107
- " }",
6108
- "",
6109
- " return (",
6110
- " <LandingPage",
6111
- " serviceId={activeServiceId}",
6112
- " authApiUrl={AUTH_URL}",
6113
- " config={LANDING_CONFIG}",
6114
- " onCTAClick={() => window.location.assign('/login')}",
6115
- " />",
6116
- " );",
6117
- " }",
6118
- "",
6119
- " if (route.kind === 'login') {",
6120
- " return (",
6121
- " <TopoloLoadingScreen",
6122
- " appId={APP_ID}",
6123
- " serviceId={activeServiceId}",
6124
- " appName={APP_NAME}",
6125
- ` subtitle="Opening your ${escapeDoubleQuoted(signedSurfaceLabel)}..."`,
6126
- " />",
6127
- " );",
6128
- " }",
6129
- "",
6130
- " return (",
6131
- " <TopoloAppShell<NavItem>",
6132
- " appId={APP_ID}",
6133
- " serviceId={activeServiceId}",
6134
- " appName={APP_NAME}",
6135
- " user={shellUser}",
6136
- " orgId={user.activeContextType === 'organization' ? user.activeContextId ?? user.orgId ?? null : null}",
6137
- " activeContextType={user.activeContextType}",
6138
- " activeContextId={user.activeContextId}",
6139
- " activeNavItemId={view === 'getting-started' ? 'getting-started' : 'home'}",
6140
- ` navItems={[{ id: 'home', label: '${escapeSingleQuoted(workspaceTitle)}' }, { id: 'getting-started', label: 'Getting started' }]}`,
6141
- ' navigationMode="topbar"',
6142
- " onNavItemSelect={handleNavSelect}",
6143
- ` title={view === 'getting-started' ? 'Getting started' : '${escapeSingleQuoted(workspaceTitle)}'}`,
6144
- " authUrl={AUTH_URL}",
6145
- " getAccessToken={getAccessToken}",
6146
- " onSignOut={async () => {",
6147
- " await auth.authClient.logout();",
6148
- " window.location.assign('/login');",
6149
- " }}",
6150
- " commandPlaceholder={`Search ${APP_NAME}`}",
6151
- " bugReporterAppName={APP_NAME}",
6152
- " enableCommandPalette",
6153
- " enableBugReporter",
6154
- " >",
6155
- " {view === 'getting-started' ? (",
6156
- " <ScaffoldGettingStarted",
6157
- " serviceId={activeServiceId}",
6158
- " organizationId={user.organization?.id ?? user.orgId ?? ''}",
6159
- " role={user.role}",
6160
- " 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}...`}",
6161
6070
  " />",
6162
- " ) : (",
6163
- ' <div className="topolo-starter-workspace">',
6164
- ' <section className="topolo-starter-card" data-tour="workspace-card">',
6165
- ` <h2>${baselineHeading}</h2>`,
6166
- " <p>",
6167
- ` ${baselineDescription}`,
6168
- " </p>",
6169
- " </section>",
6170
- " </div>",
6171
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>",
6172
6104
  " <IntroTour",
6173
6105
  " appName={APP_NAME}",
6174
- " role={user.role}",
6106
+ " role={user?.role}",
6175
6107
  " onOpenGettingStarted={openGettingStarted}",
6176
6108
  " extraSteps={[",
6177
6109
  " {",
@@ -6184,7 +6116,20 @@ function platformAppTsx(application, options) {
6184
6116
  " },",
6185
6117
  " ]}",
6186
6118
  " />",
6187
- " </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
+ " />",
6188
6133
  " );",
6189
6134
  "}",
6190
6135
  "",
@@ -6192,15 +6137,16 @@ function platformAppTsx(application, options) {
6192
6137
  " serviceId: string;",
6193
6138
  " organizationId: string;",
6194
6139
  " role?: string;",
6140
+ " getAccessToken: () => string | null;",
6195
6141
  " onOpenWorkspace: () => void;",
6196
6142
  "}) {",
6197
- " const { serviceId, organizationId, role, onOpenWorkspace } = props;",
6143
+ " const { serviceId, organizationId, role, getAccessToken, onOpenWorkspace } = props;",
6198
6144
  " const steps: OnboardingGuideStep[] = [",
6199
6145
  " {",
6200
6146
  " id: 'explore-workspace',",
6201
6147
  " title: 'Explore your workspace',",
6202
6148
  " description:",
6203
- " '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.',",
6204
6150
  " actionLabel: 'Open workspace',",
6205
6151
  " onAction: onOpenWorkspace,",
6206
6152
  " },",
@@ -6213,9 +6159,9 @@ function platformAppTsx(application, options) {
6213
6159
  " },",
6214
6160
  " {",
6215
6161
  " id: 'build-feature',",
6216
- " title: 'Build your first feature',",
6162
+ " title: 'Make it your own',",
6217
6163
  " description:",
6218
- " '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.',",
6219
6165
  " },",
6220
6166
  " ];",
6221
6167
  " return (",
@@ -6224,7 +6170,7 @@ function platformAppTsx(application, options) {
6224
6170
  " authApiBaseUrl={`${AUTH_URL}/api`}",
6225
6171
  " organizationId={organizationId}",
6226
6172
  " serviceId={serviceId}",
6227
- " getAccessToken={() => getAccessToken()}",
6173
+ " getAccessToken={getAccessToken}",
6228
6174
  " role={role || undefined}",
6229
6175
  " steps={steps}",
6230
6176
  " eyebrow={`Welcome to ${APP_NAME}`}",
@@ -6240,13 +6186,6 @@ function platformAppTsx(application, options) {
6240
6186
  " );",
6241
6187
  "}",
6242
6188
  "",
6243
- "function getRoute(): AppRoute {",
6244
- " const pathname = window.location.pathname.replace(/\\/+$/, '') || '/';",
6245
- " if (pathname === '/login') return { kind: 'login' };",
6246
- " if (pathname === '/auth/callback') return { kind: 'callback' };",
6247
- " return { kind: 'root' };",
6248
- "}",
6249
- "",
6250
6189
  "function errorMessage(error: unknown): string {",
6251
6190
  " if (error instanceof Error && error.message) return error.message;",
6252
6191
  " return 'Unexpected authentication error.';",
@@ -6793,6 +6732,9 @@ function platformWorkerTs(application, options) {
6793
6732
  " return [",
6794
6733
  " {",
6795
6734
  " type: 'quick-actions',",
6735
+ " snapshot: {",
6736
+ " summary: `${SCAFFOLD_APP_NAME} is ready for workspace operations and exposes health checks for operators.`,",
6737
+ " },",
6796
6738
  " actions: [",
6797
6739
  ` { id: 'open-workspace', label: '${escapeSingleQuoted(openActionLabel)}', link: '/', primary: true },`,
6798
6740
  " { id: 'view-health', label: 'View health', link: '/api/health' },",
@@ -6904,6 +6846,9 @@ function developerWorkerTs(application) {
6904
6846
  " return [",
6905
6847
  " {",
6906
6848
  " type: 'quick-actions',",
6849
+ " snapshot: {",
6850
+ " summary: `${SERVICE_NAME} is ready for workspace operations and exposes health checks for operators.`,",
6851
+ " },",
6907
6852
  " actions: [",
6908
6853
  " { id: 'open-app', label: `Open ${SERVICE_NAME}`, link: '/', primary: true },",
6909
6854
  " { id: 'view-health', label: 'View health', link: '/api/health' },",
@@ -7470,7 +7415,7 @@ function internalDocs(application, options) {
7470
7415
  "",
7471
7416
  "## Architecture",
7472
7417
  "",
7473
- "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.",
7474
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.`,
7475
7420
  "",
7476
7421
  "## Runtime Surfaces",
@@ -7875,9 +7820,6 @@ function escapeHtml(value) {
7875
7820
  function escapeSingleQuoted(value) {
7876
7821
  return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
7877
7822
  }
7878
- function escapeDoubleQuoted(value) {
7879
- return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
7880
- }
7881
7823
  function escapeFrontmatter(value) {
7882
7824
  return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
7883
7825
  }
@@ -7991,17 +7933,17 @@ async function provisionApplicationScaffold(payload) {
7991
7933
  developersOrigin,
7992
7934
  developersApp: {
7993
7935
  id: appId,
7994
- slug: readString(app.slug),
7995
- listingStatus: readString(app.listingStatus),
7996
- organizationId: readString(app.organizationId)
7936
+ slug: readString2(app.slug),
7937
+ listingStatus: readString2(app.listingStatus),
7938
+ organizationId: readString2(app.organizationId)
7997
7939
  },
7998
7940
  authService: service ? {
7999
7941
  id: readRequiredString(
8000
7942
  service.id,
8001
7943
  "Auth registration did not return a service id."
8002
7944
  ),
8003
- status: readString(service.status),
8004
- baseUrl: readString(service.base_url) ?? readString(service.baseUrl)
7945
+ status: readString2(service.status),
7946
+ baseUrl: readString2(service.base_url) ?? readString2(service.baseUrl)
8005
7947
  } : null,
8006
7948
  authConfig: authConfig ? {
8007
7949
  login: authConfig.login === true,
@@ -8067,7 +8009,7 @@ async function resolveServiceIdBySlug(authOrigin, slug) {
8067
8009
  function pickRecord(value) {
8068
8010
  return value && typeof value === "object" ? value : null;
8069
8011
  }
8070
- function readString(value) {
8012
+ function readString2(value) {
8071
8013
  if (typeof value !== "string") {
8072
8014
  return null;
8073
8015
  }
@@ -8075,7 +8017,7 @@ function readString(value) {
8075
8017
  return trimmed ? trimmed : null;
8076
8018
  }
8077
8019
  function readRequiredString(value, message) {
8078
- const resolved = readString(value);
8020
+ const resolved = readString2(value);
8079
8021
  if (!resolved) {
8080
8022
  throw new Error(message);
8081
8023
  }
@@ -8252,7 +8194,7 @@ function applicationRowsFromCatalog(catalog) {
8252
8194
  name: entry.name ?? entry.slug ?? entry.serviceId,
8253
8195
  appDir: "-",
8254
8196
  packageName: null,
8255
- productionUrl: entry.baseUrl,
8197
+ productionUrl: entry.launchUrl ?? null,
8256
8198
  services: [displayCatalogId(entry)],
8257
8199
  deployTargets: 0
8258
8200
  })).sort((left, right) => left.id.localeCompare(right.id));
@@ -8849,22 +8791,26 @@ function buildRows(catalog, overrides, environment = selectedEnvironment()) {
8849
8791
  defaultUrl,
8850
8792
  effectiveUrl,
8851
8793
  overridden: Boolean(configuredUrl && configuredUrl !== defaultUrl),
8852
- environmentSafe: environment !== "staging" || isStagingUrl(effectiveUrl),
8853
- permissions: [...entry.permissions]
8794
+ environmentSafe: environment !== "staging" || !effectiveUrl || isStagingUrl(effectiveUrl),
8795
+ permissions: [...entry.permissions],
8796
+ apiKeyResource: entry.apiKeyResource,
8797
+ apiKeyResources: [...entry.apiKeyResources]
8854
8798
  };
8855
8799
  }).sort((left, right) => left.id.localeCompare(right.id));
8856
8800
  }
8857
8801
  function formatRows2(rows, environment = selectedEnvironment()) {
8858
8802
  const idWidth = Math.max(3, ...rows.map((r) => r.id.length));
8859
- const urlWidth = Math.max(7, ...rows.map((r) => r.effectiveUrl.length));
8803
+ const urlWidth = Math.max(7, ...rows.map((r) => (r.effectiveUrl ?? "-").length));
8860
8804
  const lines = [];
8861
8805
  lines.push(`Environment: ${environment}`);
8862
8806
  lines.push("");
8863
- lines.push(`${"ID".padEnd(idWidth)} ${"URL".padEnd(urlWidth)} OVERRIDE READY`);
8864
- 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)} -------- ----- ----------------`);
8865
8809
  for (const r of rows) {
8810
+ const effectiveUrl = r.effectiveUrl ?? "-";
8811
+ const apiKeyResource = r.apiKeyResources.length > 0 ? r.apiKeyResources.map((resource) => `${resource.resourceType}:${resource.canonicalResourceType}`).join(",") : "-";
8866
8812
  lines.push(
8867
- `${r.id.padEnd(idWidth)} ${r.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}`
8868
8814
  );
8869
8815
  }
8870
8816
  return lines.join("\n");
@@ -8885,7 +8831,7 @@ function displayServiceId(entry) {
8885
8831
  return serviceIdCandidates(entry.slug || entry.serviceId)[0] || entry.serviceId;
8886
8832
  }
8887
8833
  function defaultServiceUrl(id, entry) {
8888
- return resolveCatalogServiceUrl(entry, { serviceKey: id }) ?? DEFAULT_SERVICE_URLS[id] ?? "";
8834
+ return resolveCatalogServiceUrl(entry, { serviceKey: id });
8889
8835
  }
8890
8836
  function configuredServiceUrl(id, overrides) {
8891
8837
  if (!overrides) return null;