@topolo/cli 0.5.2 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -61,13 +61,13 @@ var TopoloHttpError = class extends TopoloSdkError {
61
61
  body;
62
62
  service;
63
63
  path;
64
- constructor(service, path6, status, body, message) {
65
- super("http_error", message ?? `HTTP ${status} from ${service}${path6}`);
64
+ constructor(service, path8, status, body, message) {
65
+ super("http_error", message ?? `HTTP ${status} from ${service}${path8}`);
66
66
  this.name = "TopoloHttpError";
67
67
  this.status = status;
68
68
  this.body = body;
69
69
  this.service = service;
70
- this.path = path6;
70
+ this.path = path8;
71
71
  }
72
72
  };
73
73
  var APP_API_URLS = {
@@ -137,7 +137,9 @@ var APPLICATIONS = {
137
137
  "domains": [
138
138
  "admin.topolo.app"
139
139
  ],
140
- "routes": [],
140
+ "routes": [
141
+ "admin.topolo.app"
142
+ ],
141
143
  "appId": null
142
144
  },
143
145
  {
@@ -221,7 +223,9 @@ var APPLICATIONS = {
221
223
  "domains": [
222
224
  "backup.topolo.app"
223
225
  ],
224
- "routes": [],
226
+ "routes": [
227
+ "backup.topolo.app"
228
+ ],
225
229
  "appId": "backup"
226
230
  }
227
231
  ]
@@ -325,7 +329,9 @@ var APPLICATIONS = {
325
329
  "domains": [
326
330
  "bytes.topolo.app"
327
331
  ],
328
- "routes": [],
332
+ "routes": [
333
+ "bytes.topolo.app"
334
+ ],
329
335
  "appId": "bytes"
330
336
  },
331
337
  {
@@ -475,7 +481,9 @@ var APPLICATIONS = {
475
481
  "domains": [
476
482
  "guest.topolo.app"
477
483
  ],
478
- "routes": [],
484
+ "routes": [
485
+ "guest.topolo.app"
486
+ ],
479
487
  "appId": null
480
488
  },
481
489
  {
@@ -485,7 +493,9 @@ var APPLICATIONS = {
485
493
  "domains": [
486
494
  "commerce.topolo.app"
487
495
  ],
488
- "routes": [],
496
+ "routes": [
497
+ "commerce.topolo.app"
498
+ ],
489
499
  "appId": "commerce"
490
500
  },
491
501
  {
@@ -495,7 +505,9 @@ var APPLICATIONS = {
495
505
  "domains": [
496
506
  "station-commerce.topolo.app"
497
507
  ],
498
- "routes": [],
508
+ "routes": [
509
+ "station-commerce.topolo.app"
510
+ ],
499
511
  "appId": null
500
512
  }
501
513
  ]
@@ -673,7 +685,9 @@ var APPLICATIONS = {
673
685
  "domains": [
674
686
  "docs.topolo.app"
675
687
  ],
676
- "routes": [],
688
+ "routes": [
689
+ "docs.topolo.app"
690
+ ],
677
691
  "appId": null
678
692
  },
679
693
  {
@@ -703,7 +717,9 @@ var APPLICATIONS = {
703
717
  "domains": [
704
718
  "feed-analytics-api.topolo.app"
705
719
  ],
706
- "routes": [],
720
+ "routes": [
721
+ "feed-analytics-api.topolo.app"
722
+ ],
707
723
  "appId": null
708
724
  },
709
725
  {
@@ -713,7 +729,9 @@ var APPLICATIONS = {
713
729
  "domains": [
714
730
  "feed-api.topolo.app"
715
731
  ],
716
- "routes": [],
732
+ "routes": [
733
+ "feed-api.topolo.app"
734
+ ],
717
735
  "appId": null
718
736
  },
719
737
  {
@@ -723,7 +741,9 @@ var APPLICATIONS = {
723
741
  "domains": [
724
742
  "feed.topolo.app"
725
743
  ],
726
- "routes": [],
744
+ "routes": [
745
+ "feed.topolo.app"
746
+ ],
727
747
  "appId": "feed"
728
748
  }
729
749
  ]
@@ -835,7 +855,9 @@ var APPLICATIONS = {
835
855
  "domains": [
836
856
  "home.topolo.app"
837
857
  ],
838
- "routes": [],
858
+ "routes": [
859
+ "home.topolo.app"
860
+ ],
839
861
  "appId": "home"
840
862
  },
841
863
  {
@@ -925,7 +947,9 @@ var APPLICATIONS = {
925
947
  "domains": [
926
948
  "learn.topolo.app"
927
949
  ],
928
- "routes": [],
950
+ "routes": [
951
+ "learn.topolo.app"
952
+ ],
929
953
  "appId": "learn"
930
954
  },
931
955
  {
@@ -1017,7 +1041,9 @@ var APPLICATIONS = {
1017
1041
  "domains": [
1018
1042
  "mdm.topolo.app"
1019
1043
  ],
1020
- "routes": [],
1044
+ "routes": [
1045
+ "mdm.topolo.app"
1046
+ ],
1021
1047
  "appId": "mdm"
1022
1048
  }
1023
1049
  ]
@@ -1039,7 +1065,9 @@ var APPLICATIONS = {
1039
1065
  "domains": [
1040
1066
  "messages.topolo.app"
1041
1067
  ],
1042
- "routes": [],
1068
+ "routes": [
1069
+ "messages.topolo.app"
1070
+ ],
1043
1071
  "appId": "messages"
1044
1072
  },
1045
1073
  {
@@ -1207,7 +1235,9 @@ var APPLICATIONS = {
1207
1235
  "domains": [
1208
1236
  "p2p.topolo.app"
1209
1237
  ],
1210
- "routes": [],
1238
+ "routes": [
1239
+ "p2p.topolo.app"
1240
+ ],
1211
1241
  "appId": "p2p"
1212
1242
  },
1213
1243
  {
@@ -1404,7 +1434,9 @@ var APPLICATIONS = {
1404
1434
  "domains": [
1405
1435
  "studio.topolo.app"
1406
1436
  ],
1407
- "routes": [],
1437
+ "routes": [
1438
+ "studio.topolo.app"
1439
+ ],
1408
1440
  "appId": "social_studio"
1409
1441
  },
1410
1442
  {
@@ -1442,7 +1474,9 @@ var APPLICATIONS = {
1442
1474
  "domains": [
1443
1475
  "socialize.topolo.app"
1444
1476
  ],
1445
- "routes": [],
1477
+ "routes": [
1478
+ "socialize.topolo.app"
1479
+ ],
1446
1480
  "appId": "socialize"
1447
1481
  },
1448
1482
  {
@@ -1452,7 +1486,9 @@ var APPLICATIONS = {
1452
1486
  "domains": [
1453
1487
  "socialize-api.topolo.app"
1454
1488
  ],
1455
- "routes": [],
1489
+ "routes": [
1490
+ "socialize-api.topolo.app"
1491
+ ],
1456
1492
  "appId": null
1457
1493
  },
1458
1494
  {
@@ -1512,7 +1548,9 @@ var APPLICATIONS = {
1512
1548
  "domains": [
1513
1549
  "status.topolo.app"
1514
1550
  ],
1515
- "routes": [],
1551
+ "routes": [
1552
+ "status.topolo.app"
1553
+ ],
1516
1554
  "appId": "status"
1517
1555
  }
1518
1556
  ]
@@ -1628,7 +1666,9 @@ var APPLICATIONS = {
1628
1666
  "domains": [
1629
1667
  "voice.topolo.app"
1630
1668
  ],
1631
- "routes": [],
1669
+ "routes": [
1670
+ "voice.topolo.app"
1671
+ ],
1632
1672
  "appId": "voice"
1633
1673
  },
1634
1674
  {
@@ -1679,7 +1719,9 @@ var APPLICATIONS = {
1679
1719
  "domains": [
1680
1720
  "web.topolo.app"
1681
1721
  ],
1682
- "routes": [],
1722
+ "routes": [
1723
+ "web.topolo.app"
1724
+ ],
1683
1725
  "appId": "web"
1684
1726
  }
1685
1727
  ]
@@ -2176,17 +2218,17 @@ function validateTopoloActionInput(schema, input3) {
2176
2218
  validateNode(schema, input3, "$", schema, errors, 0);
2177
2219
  return { valid: errors.length === 0, errors };
2178
2220
  }
2179
- function inspectSchema(schema, path6, root, depth) {
2180
- if (depth > 128) return { path: path6, code: "schema_depth", message: "Schema recursion exceeded 128 levels." };
2221
+ function inspectSchema(schema, path8, root, depth) {
2222
+ if (depth > 128) return { path: path8, code: "schema_depth", message: "Schema recursion exceeded 128 levels." };
2181
2223
  const unsupportedKeyword = Object.keys(schema).find((keyword) => !SUPPORTED_SCHEMA_KEYWORDS.has(keyword));
2182
2224
  if (unsupportedKeyword) {
2183
- return { path: path6, code: "unsupported_schema_keyword", message: `Action schema uses unsupported keyword ${unsupportedKeyword}.` };
2225
+ return { path: path8, code: "unsupported_schema_keyword", message: `Action schema uses unsupported keyword ${unsupportedKeyword}.` };
2184
2226
  }
2185
2227
  if (typeof schema.format === "string" && !["uri", "email", "date-time"].includes(schema.format)) {
2186
- return { path: path6, code: "unsupported_schema_format", message: `Action schema uses unsupported format ${schema.format}.` };
2228
+ return { path: path8, code: "unsupported_schema_format", message: `Action schema uses unsupported format ${schema.format}.` };
2187
2229
  }
2188
2230
  if (typeof schema.$ref === "string" && !resolveLocalReference(root, schema.$ref)) {
2189
- return { path: path6, code: "unsupported_schema_ref", message: `Cannot resolve schema reference ${schema.$ref}.` };
2231
+ return { path: path8, code: "unsupported_schema_ref", message: `Cannot resolve schema reference ${schema.$ref}.` };
2190
2232
  }
2191
2233
  const children = [];
2192
2234
  if (isRecord(schema.properties)) children.push(...Object.entries(schema.properties));
@@ -2202,7 +2244,7 @@ function inspectSchema(schema, path6, root, depth) {
2202
2244
  );
2203
2245
  for (const [key, child] of children) {
2204
2246
  if (!isRecord(child)) continue;
2205
- const issue = inspectSchema(child, `${path6}.${key}`, root, depth + 1);
2247
+ const issue = inspectSchema(child, `${path8}.${key}`, root, depth + 1);
2206
2248
  if (issue) return issue;
2207
2249
  }
2208
2250
  return null;
@@ -2210,129 +2252,129 @@ function inspectSchema(schema, path6, root, depth) {
2210
2252
  function arraySchemaEntries(keyword, value) {
2211
2253
  return Array.isArray(value) ? value.map((candidate, index) => [`${keyword}[${index}]`, candidate]) : [];
2212
2254
  }
2213
- function validateNode(schema, value, path6, root, errors, depth) {
2255
+ function validateNode(schema, value, path8, root, errors, depth) {
2214
2256
  if (depth > 128) {
2215
- errors.push({ path: path6, code: "schema_depth", message: "Schema recursion exceeded 128 levels." });
2257
+ errors.push({ path: path8, code: "schema_depth", message: "Schema recursion exceeded 128 levels." });
2216
2258
  return;
2217
2259
  }
2218
2260
  if (typeof schema.$ref === "string") {
2219
2261
  const resolved = resolveLocalReference(root, schema.$ref);
2220
2262
  if (!resolved) {
2221
- errors.push({ path: path6, code: "unsupported_schema_ref", message: `Cannot resolve schema reference ${schema.$ref}.` });
2263
+ errors.push({ path: path8, code: "unsupported_schema_ref", message: `Cannot resolve schema reference ${schema.$ref}.` });
2222
2264
  return;
2223
2265
  }
2224
- validateNode(resolved, value, path6, root, errors, depth + 1);
2266
+ validateNode(resolved, value, path8, root, errors, depth + 1);
2225
2267
  }
2226
2268
  if (Array.isArray(schema.allOf)) {
2227
2269
  for (const candidate of schema.allOf) {
2228
- if (isRecord(candidate)) validateNode(candidate, value, path6, root, errors, depth + 1);
2270
+ if (isRecord(candidate)) validateNode(candidate, value, path8, root, errors, depth + 1);
2229
2271
  }
2230
2272
  }
2231
2273
  if (Array.isArray(schema.anyOf) && !schema.anyOf.some((candidate) => candidateMatches(candidate, value, root, depth))) {
2232
- errors.push({ path: path6, code: "any_of", message: "Value does not match any allowed schema." });
2274
+ errors.push({ path: path8, code: "any_of", message: "Value does not match any allowed schema." });
2233
2275
  return;
2234
2276
  }
2235
2277
  if (Array.isArray(schema.oneOf)) {
2236
2278
  const matches = schema.oneOf.filter((candidate) => candidateMatches(candidate, value, root, depth)).length;
2237
2279
  if (matches !== 1) {
2238
- errors.push({ path: path6, code: "one_of", message: "Value must match exactly one allowed schema." });
2280
+ errors.push({ path: path8, code: "one_of", message: "Value must match exactly one allowed schema." });
2239
2281
  return;
2240
2282
  }
2241
2283
  }
2242
2284
  if (value === null && schema.nullable === true) return;
2243
2285
  if (Object.prototype.hasOwnProperty.call(schema, "const") && !sameJsonValue(value, schema.const)) {
2244
- errors.push({ path: path6, code: "const", message: `Value must equal ${JSON.stringify(schema.const)}.` });
2286
+ errors.push({ path: path8, code: "const", message: `Value must equal ${JSON.stringify(schema.const)}.` });
2245
2287
  return;
2246
2288
  }
2247
2289
  if (Array.isArray(schema.enum) && !schema.enum.some((candidate) => sameJsonValue(value, candidate))) {
2248
- errors.push({ path: path6, code: "enum", message: `Value must be one of ${schema.enum.map((item) => JSON.stringify(item)).join(", ")}.` });
2290
+ errors.push({ path: path8, code: "enum", message: `Value must be one of ${schema.enum.map((item) => JSON.stringify(item)).join(", ")}.` });
2249
2291
  return;
2250
2292
  }
2251
2293
  if (!matchesType(schema.type, value)) {
2252
- errors.push({ path: path6, code: "type", message: `Expected ${String(schema.type)}.` });
2294
+ errors.push({ path: path8, code: "type", message: `Expected ${String(schema.type)}.` });
2253
2295
  return;
2254
2296
  }
2255
- if (typeof value === "string") validateString(schema, value, path6, errors);
2256
- if (typeof value === "number") validateNumber(schema, value, path6, errors);
2257
- if (Array.isArray(value)) validateArray(schema, value, path6, root, errors, depth);
2258
- if (isRecord(value)) validateObject(schema, value, path6, root, errors, depth);
2297
+ if (typeof value === "string") validateString(schema, value, path8, errors);
2298
+ if (typeof value === "number") validateNumber(schema, value, path8, errors);
2299
+ if (Array.isArray(value)) validateArray(schema, value, path8, root, errors, depth);
2300
+ if (isRecord(value)) validateObject(schema, value, path8, root, errors, depth);
2259
2301
  }
2260
- function validateObject(schema, value, path6, root, errors, depth) {
2302
+ function validateObject(schema, value, path8, root, errors, depth) {
2261
2303
  if (typeof schema.minProperties === "number" && Object.keys(value).length < schema.minProperties) {
2262
- errors.push({ path: path6, code: "min_properties", message: `Expected at least ${schema.minProperties} properties.` });
2304
+ errors.push({ path: path8, code: "min_properties", message: `Expected at least ${schema.minProperties} properties.` });
2263
2305
  }
2264
2306
  if (typeof schema.maxProperties === "number" && Object.keys(value).length > schema.maxProperties) {
2265
- errors.push({ path: path6, code: "max_properties", message: `Expected at most ${schema.maxProperties} properties.` });
2307
+ errors.push({ path: path8, code: "max_properties", message: `Expected at most ${schema.maxProperties} properties.` });
2266
2308
  }
2267
2309
  const properties = isRecord(schema.properties) ? schema.properties : {};
2268
2310
  const required = Array.isArray(schema.required) ? schema.required.map(String) : [];
2269
2311
  for (const key of required) {
2270
2312
  if (!Object.prototype.hasOwnProperty.call(value, key)) {
2271
- errors.push({ path: `${path6}.${key}`, code: "required", message: "Required property is missing." });
2313
+ errors.push({ path: `${path8}.${key}`, code: "required", message: "Required property is missing." });
2272
2314
  }
2273
2315
  }
2274
2316
  if (isRecord(schema.propertyNames)) {
2275
2317
  for (const key of Object.keys(value)) {
2276
- validateNode(schema.propertyNames, key, `${path6}.${key}`, root, errors, depth + 1);
2318
+ validateNode(schema.propertyNames, key, `${path8}.${key}`, root, errors, depth + 1);
2277
2319
  }
2278
2320
  }
2279
2321
  for (const [key, child] of Object.entries(value)) {
2280
2322
  const childSchema = properties[key];
2281
2323
  if (isRecord(childSchema)) {
2282
- validateNode(childSchema, child, `${path6}.${key}`, root, errors, depth + 1);
2324
+ validateNode(childSchema, child, `${path8}.${key}`, root, errors, depth + 1);
2283
2325
  } else if (schema.additionalProperties === false) {
2284
- errors.push({ path: `${path6}.${key}`, code: "additional_property", message: "Property is not declared by the action schema." });
2326
+ errors.push({ path: `${path8}.${key}`, code: "additional_property", message: "Property is not declared by the action schema." });
2285
2327
  } else if (isRecord(schema.additionalProperties)) {
2286
- validateNode(schema.additionalProperties, child, `${path6}.${key}`, root, errors, depth + 1);
2328
+ validateNode(schema.additionalProperties, child, `${path8}.${key}`, root, errors, depth + 1);
2287
2329
  }
2288
2330
  }
2289
2331
  }
2290
- function validateArray(schema, value, path6, root, errors, depth) {
2332
+ function validateArray(schema, value, path8, root, errors, depth) {
2291
2333
  if (typeof schema.minItems === "number" && value.length < schema.minItems) {
2292
- errors.push({ path: path6, code: "min_items", message: `Expected at least ${schema.minItems} items.` });
2334
+ errors.push({ path: path8, code: "min_items", message: `Expected at least ${schema.minItems} items.` });
2293
2335
  }
2294
2336
  if (typeof schema.maxItems === "number" && value.length > schema.maxItems) {
2295
- errors.push({ path: path6, code: "max_items", message: `Expected at most ${schema.maxItems} items.` });
2337
+ errors.push({ path: path8, code: "max_items", message: `Expected at most ${schema.maxItems} items.` });
2296
2338
  }
2297
2339
  if (schema.uniqueItems === true) {
2298
- const unique2 = new Set(value.map((item) => JSON.stringify(item)));
2299
- if (unique2.size !== value.length) errors.push({ path: path6, code: "unique_items", message: "Expected every item to be unique." });
2340
+ const unique4 = new Set(value.map((item) => JSON.stringify(item)));
2341
+ if (unique4.size !== value.length) errors.push({ path: path8, code: "unique_items", message: "Expected every item to be unique." });
2300
2342
  }
2301
2343
  if (isRecord(schema.items)) {
2302
- value.forEach((item, index) => validateNode(schema.items, item, `${path6}[${index}]`, root, errors, depth + 1));
2344
+ value.forEach((item, index) => validateNode(schema.items, item, `${path8}[${index}]`, root, errors, depth + 1));
2303
2345
  }
2304
2346
  }
2305
- function validateString(schema, value, path6, errors) {
2347
+ function validateString(schema, value, path8, errors) {
2306
2348
  if (typeof schema.minLength === "number" && value.length < schema.minLength) {
2307
- errors.push({ path: path6, code: "min_length", message: `Expected at least ${schema.minLength} characters.` });
2349
+ errors.push({ path: path8, code: "min_length", message: `Expected at least ${schema.minLength} characters.` });
2308
2350
  }
2309
2351
  if (typeof schema.maxLength === "number" && value.length > schema.maxLength) {
2310
- errors.push({ path: path6, code: "max_length", message: `Expected at most ${schema.maxLength} characters.` });
2352
+ errors.push({ path: path8, code: "max_length", message: `Expected at most ${schema.maxLength} characters.` });
2311
2353
  }
2312
2354
  if (typeof schema.pattern === "string") {
2313
2355
  try {
2314
- if (!new RegExp(schema.pattern).test(value)) errors.push({ path: path6, code: "pattern", message: "Value does not match the required pattern." });
2356
+ if (!new RegExp(schema.pattern).test(value)) errors.push({ path: path8, code: "pattern", message: "Value does not match the required pattern." });
2315
2357
  } catch {
2316
- errors.push({ path: path6, code: "invalid_schema_pattern", message: "Action schema contains an invalid pattern." });
2358
+ errors.push({ path: path8, code: "invalid_schema_pattern", message: "Action schema contains an invalid pattern." });
2317
2359
  }
2318
2360
  }
2319
- if (schema.format === "uri" && !isUrl(value)) errors.push({ path: path6, code: "format_uri", message: "Expected an absolute URL." });
2320
- if (schema.format === "email" && !/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(value)) errors.push({ path: path6, code: "format_email", message: "Expected an email address." });
2321
- if (schema.format === "date-time" && !isDateTime(value)) errors.push({ path: path6, code: "format_date_time", message: "Expected an ISO-8601 date-time." });
2361
+ if (schema.format === "uri" && !isUrl(value)) errors.push({ path: path8, code: "format_uri", message: "Expected an absolute URL." });
2362
+ if (schema.format === "email" && !/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(value)) errors.push({ path: path8, code: "format_email", message: "Expected an email address." });
2363
+ if (schema.format === "date-time" && !isDateTime(value)) errors.push({ path: path8, code: "format_date_time", message: "Expected an ISO-8601 date-time." });
2322
2364
  }
2323
- function validateNumber(schema, value, path6, errors) {
2324
- if (typeof schema.minimum === "number" && value < schema.minimum) errors.push({ path: path6, code: "minimum", message: `Expected a value of at least ${schema.minimum}.` });
2325
- if (typeof schema.maximum === "number" && value > schema.maximum) errors.push({ path: path6, code: "maximum", message: `Expected a value of at most ${schema.maximum}.` });
2365
+ function validateNumber(schema, value, path8, errors) {
2366
+ if (typeof schema.minimum === "number" && value < schema.minimum) errors.push({ path: path8, code: "minimum", message: `Expected a value of at least ${schema.minimum}.` });
2367
+ if (typeof schema.maximum === "number" && value > schema.maximum) errors.push({ path: path8, code: "maximum", message: `Expected a value of at most ${schema.maximum}.` });
2326
2368
  const exclusiveMinimum = typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : schema.exclusiveMinimum === true && typeof schema.minimum === "number" ? schema.minimum : null;
2327
2369
  const exclusiveMaximum = typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : schema.exclusiveMaximum === true && typeof schema.maximum === "number" ? schema.maximum : null;
2328
2370
  if (exclusiveMinimum !== null && value <= exclusiveMinimum) {
2329
- errors.push({ path: path6, code: "exclusive_minimum", message: `Expected a value greater than ${exclusiveMinimum}.` });
2371
+ errors.push({ path: path8, code: "exclusive_minimum", message: `Expected a value greater than ${exclusiveMinimum}.` });
2330
2372
  }
2331
2373
  if (exclusiveMaximum !== null && value >= exclusiveMaximum) {
2332
- errors.push({ path: path6, code: "exclusive_maximum", message: `Expected a value less than ${exclusiveMaximum}.` });
2374
+ errors.push({ path: path8, code: "exclusive_maximum", message: `Expected a value less than ${exclusiveMaximum}.` });
2333
2375
  }
2334
2376
  if (typeof schema.multipleOf === "number" && !isMultipleOf(value, schema.multipleOf)) {
2335
- errors.push({ path: path6, code: "multiple_of", message: `Expected a multiple of ${schema.multipleOf}.` });
2377
+ errors.push({ path: path8, code: "multiple_of", message: `Expected a multiple of ${schema.multipleOf}.` });
2336
2378
  }
2337
2379
  }
2338
2380
  function candidateMatches(candidate, value, root, depth) {
@@ -2843,14 +2885,14 @@ var TopoloClient = class _TopoloClient {
2843
2885
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
2844
2886
  throw new TopoloSdkError("action_catalog_summary_unavailable", "Action catalog summary is unavailable.");
2845
2887
  }
2846
- const record2 = raw;
2847
- if (record2.algorithm !== "sha256-v1" || typeof record2.digest !== "string" || !/^[a-f0-9]{64}$/.test(record2.digest) || !Number.isSafeInteger(record2.actionCount) || Number(record2.actionCount) < 0) {
2888
+ const record4 = raw;
2889
+ if (record4.algorithm !== "sha256-v1" || typeof record4.digest !== "string" || !/^[a-f0-9]{64}$/.test(record4.digest) || !Number.isSafeInteger(record4.actionCount) || Number(record4.actionCount) < 0) {
2848
2890
  throw new TopoloSdkError("action_catalog_summary_unavailable", "Action catalog summary is invalid.");
2849
2891
  }
2850
2892
  return {
2851
2893
  algorithm: "sha256-v1",
2852
- digest: record2.digest,
2853
- actionCount: Number(record2.actionCount)
2894
+ digest: record4.digest,
2895
+ actionCount: Number(record4.actionCount)
2854
2896
  };
2855
2897
  }
2856
2898
  /** @deprecated Prefer searchActions() so callers retain the continuation cursor. */
@@ -3225,8 +3267,8 @@ function normalizeActionCatalogEntry(item) {
3225
3267
  const name = pickActionString(a.name);
3226
3268
  const appId = pickActionString(a.app_id) ?? pickActionString(a.appId);
3227
3269
  const method = pickActionString(a.method)?.toUpperCase();
3228
- const path6 = pickActionString(a.path);
3229
- if (!actionId || !name || !appId || !method || !path6 || !isHttpMethod(method)) {
3270
+ const path8 = pickActionString(a.path);
3271
+ if (!actionId || !name || !appId || !method || !path8 || !isHttpMethod(method)) {
3230
3272
  return null;
3231
3273
  }
3232
3274
  return {
@@ -3241,7 +3283,7 @@ function normalizeActionCatalogEntry(item) {
3241
3283
  appSlug: pickActionString(a.app_slug) ?? pickActionString(a.appSlug) ?? null,
3242
3284
  appName: pickActionString(a.app_name) ?? pickActionString(a.appName) ?? null,
3243
3285
  method,
3244
- path: path6,
3286
+ path: path8,
3245
3287
  permissionName: pickActionString(a.permission_name) ?? pickActionString(a.permissionName) ?? "",
3246
3288
  requiredPermission: pickActionString(a.required_permission) ?? pickActionString(a.requiredPermission) ?? "",
3247
3289
  authorizationMode: pickAuthorizationMode(a.authorization_mode ?? a.authorizationMode),
@@ -3277,7 +3319,7 @@ function stringArray(value) {
3277
3319
  }
3278
3320
  function mapActionInput(action, input3) {
3279
3321
  const remaining = { ...input3 || {} };
3280
- const path6 = action.path.replace(/\{([a-zA-Z0-9_]+)\}/g, (_match, key) => {
3322
+ const path8 = action.path.replace(/\{([a-zA-Z0-9_]+)\}/g, (_match, key) => {
3281
3323
  const value = remaining[key];
3282
3324
  if (value === void 0 || value === null || value === "") {
3283
3325
  throw new TopoloSdkError(
@@ -3294,14 +3336,14 @@ function mapActionInput(action, input3) {
3294
3336
  delete remaining.body;
3295
3337
  if (action.method === "GET") {
3296
3338
  return {
3297
- path: path6,
3339
+ path: path8,
3298
3340
  query: objectToQuery({ ...remaining, ...explicitQuery })
3299
3341
  };
3300
3342
  }
3301
3343
  const query = objectToQuery(explicitQuery);
3302
3344
  const body = explicitBody !== void 0 ? explicitBody : remaining;
3303
3345
  return {
3304
- path: path6,
3346
+ path: path8,
3305
3347
  ...query ? { query } : {},
3306
3348
  body
3307
3349
  };
@@ -3361,7 +3403,6 @@ var CrmModule = class {
3361
3403
  constructor(client) {
3362
3404
  this.client = client;
3363
3405
  }
3364
- client;
3365
3406
  async listContacts(options = {}) {
3366
3407
  const res = await this.client.request({
3367
3408
  service: "crm",
@@ -3407,7 +3448,6 @@ var IdentityModule = class {
3407
3448
  constructor(client) {
3408
3449
  this.client = client;
3409
3450
  }
3410
- client;
3411
3451
  /** Returns the resolved user, organization, and permission set for the
3412
3452
  * current credential. Equivalent to `TopoloClient.introspect()` — exposed
3413
3453
  * on this module for symmetry with the other domain modules. */
@@ -3471,8 +3511,8 @@ var TopoloOAuth = class {
3471
3511
  if (params.clientSecret) body.set("client_secret", params.clientSecret);
3472
3512
  return this.#postForm("/api/developer-oauth/token", body);
3473
3513
  }
3474
- async #postForm(path6, body) {
3475
- const url = new URL(path6, this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`);
3514
+ async #postForm(path8, body) {
3515
+ const url = new URL(path8, this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`);
3476
3516
  const res = await this.fetchImpl(url.toString(), {
3477
3517
  method: "POST",
3478
3518
  headers: {
@@ -3488,7 +3528,7 @@ var TopoloOAuth = class {
3488
3528
  const err = parsed;
3489
3529
  throw new TopoloAuthError(err.error_description || err.error, err.error);
3490
3530
  }
3491
- throw new TopoloHttpError("auth", path6, res.status, parsed);
3531
+ throw new TopoloHttpError("auth", path8, res.status, parsed);
3492
3532
  }
3493
3533
  return parsed;
3494
3534
  }
@@ -3526,13 +3566,13 @@ var TopoloHttpError2 = class extends TopoloSdkError2 {
3526
3566
  body;
3527
3567
  service;
3528
3568
  path;
3529
- constructor(service, path6, status, body, message) {
3530
- super("http_error", message ?? `HTTP ${status} from ${service}${path6}`);
3569
+ constructor(service, path8, status, body, message) {
3570
+ super("http_error", message ?? `HTTP ${status} from ${service}${path8}`);
3531
3571
  this.name = "TopoloHttpError";
3532
3572
  this.status = status;
3533
3573
  this.body = body;
3534
3574
  this.service = service;
3535
- this.path = path6;
3575
+ this.path = path8;
3536
3576
  }
3537
3577
  };
3538
3578
  var APP_API_URLS2 = {
@@ -3663,8 +3703,8 @@ var TopoloOAuth2 = class {
3663
3703
  if (params.clientSecret) body.set("client_secret", params.clientSecret);
3664
3704
  return this.#postForm("/api/developer-oauth/token", body);
3665
3705
  }
3666
- async #postForm(path6, body) {
3667
- const url = new URL(path6, this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`);
3706
+ async #postForm(path8, body) {
3707
+ const url = new URL(path8, this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`);
3668
3708
  const res = await this.fetchImpl(url.toString(), {
3669
3709
  method: "POST",
3670
3710
  headers: {
@@ -3680,7 +3720,7 @@ var TopoloOAuth2 = class {
3680
3720
  const err = parsed;
3681
3721
  throw new TopoloAuthError2(err.error_description || err.error, err.error);
3682
3722
  }
3683
- throw new TopoloHttpError2("auth", path6, res.status, parsed);
3723
+ throw new TopoloHttpError2("auth", path8, res.status, parsed);
3684
3724
  }
3685
3725
  return parsed;
3686
3726
  }
@@ -3710,22 +3750,22 @@ function configPath() {
3710
3750
  return join(base, "topolo", "config.json");
3711
3751
  }
3712
3752
  async function readConfig() {
3713
- const path6 = configPath();
3714
- if (!existsSync(path6)) return {};
3753
+ const path8 = configPath();
3754
+ if (!existsSync(path8)) return {};
3715
3755
  try {
3716
- const raw = await readFile(path6, "utf8");
3756
+ const raw = await readFile(path8, "utf8");
3717
3757
  return JSON.parse(raw);
3718
3758
  } catch {
3719
3759
  return {};
3720
3760
  }
3721
3761
  }
3722
3762
  async function writeConfig(config) {
3723
- const path6 = configPath();
3724
- await mkdir(dirname(path6), { recursive: true });
3725
- await writeFile(path6, JSON.stringify(config, null, 2) + "\n", "utf8");
3763
+ const path8 = configPath();
3764
+ await mkdir(dirname(path8), { recursive: true });
3765
+ await writeFile(path8, JSON.stringify(config, null, 2) + "\n", "utf8");
3726
3766
  if (platform() !== "win32") {
3727
3767
  try {
3728
- await chmod(path6, 384);
3768
+ await chmod(path8, 384);
3729
3769
  } catch {
3730
3770
  }
3731
3771
  }
@@ -4045,9 +4085,9 @@ function readCliVersion() {
4045
4085
  join2(here, "package.json"),
4046
4086
  join2(here, "..", "package.json")
4047
4087
  ];
4048
- for (const path6 of candidates) {
4088
+ for (const path8 of candidates) {
4049
4089
  try {
4050
- const pkg = JSON.parse(readFileSync(path6, "utf8"));
4090
+ const pkg = JSON.parse(readFileSync(path8, "utf8"));
4051
4091
  if (typeof pkg.version === "string" && pkg.version) return pkg.version;
4052
4092
  } catch {
4053
4093
  }
@@ -4406,7 +4446,7 @@ function registerApiCommand(program2) {
4406
4446
  "-q, --query <kv...>",
4407
4447
  "Query params as key=value (repeatable), e.g. --query source=cli page=2"
4408
4448
  ).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(
4409
- async (service, method, path6, opts) => {
4449
+ async (service, method, path8, opts) => {
4410
4450
  const mode = resolveOutputMode(opts.json);
4411
4451
  try {
4412
4452
  const upperMethod = method.toUpperCase();
@@ -4445,7 +4485,7 @@ function registerApiCommand(program2) {
4445
4485
  const result = await topolo.client.request({
4446
4486
  service,
4447
4487
  method: httpMethod,
4448
- path: path6,
4488
+ path: path8,
4449
4489
  body,
4450
4490
  ...query ? { query } : {},
4451
4491
  ...resource ? { resource } : {},
@@ -4609,13 +4649,13 @@ async function requiredClient() {
4609
4649
  function parseJsonObject(value) {
4610
4650
  let source = value;
4611
4651
  if (value.startsWith("@")) {
4612
- const path6 = value.slice(1);
4613
- if (!path6) throw new Error("--data @file path must not be empty");
4652
+ const path8 = value.slice(1);
4653
+ if (!path8) throw new Error("--data @file path must not be empty");
4614
4654
  try {
4615
- source = readFileSync2(path6, "utf8");
4655
+ source = readFileSync2(path8, "utf8");
4616
4656
  } catch (err) {
4617
4657
  const detail = err instanceof Error ? err.message : String(err);
4618
- throw new Error(`Could not read --data file ${path6}: ${detail}`);
4658
+ throw new Error(`Could not read --data file ${path8}: ${detail}`);
4619
4659
  }
4620
4660
  }
4621
4661
  let parsed;
@@ -4690,7 +4730,7 @@ function formatActionPlan(plan) {
4690
4730
  // src/commands/apps.ts
4691
4731
  import { execSync } from "child_process";
4692
4732
  import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
4693
- import path4 from "path";
4733
+ import path5 from "path";
4694
4734
 
4695
4735
  // src/commands/app-audit-local.ts
4696
4736
  import {
@@ -4722,14 +4762,26 @@ function isEvidenceStampFresh(stamp) {
4722
4762
  return Date.now() - recordedAt <= EVIDENCE_FRESHNESS_MS;
4723
4763
  }
4724
4764
  var SKIPPED_DIRS = /* @__PURE__ */ new Set([
4765
+ ".action-normalize",
4766
+ ".claude",
4767
+ ".codex-worktrees",
4768
+ ".deploy",
4725
4769
  ".git",
4726
4770
  ".next",
4771
+ ".playwright",
4772
+ ".topolo-tmp",
4727
4773
  ".turbo",
4774
+ ".worktrees",
4728
4775
  ".wrangler",
4776
+ "_tmp",
4777
+ "_worktrees",
4778
+ "artifacts",
4729
4779
  "build",
4730
4780
  "coverage",
4731
4781
  "dist",
4732
- "node_modules"
4782
+ "node_modules",
4783
+ "output",
4784
+ "tmp"
4733
4785
  ]);
4734
4786
  var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
4735
4787
  ".cjs",
@@ -5281,8 +5333,8 @@ function enrichFinding(application, finding2, evidence) {
5281
5333
  evidence: verificationEvidence
5282
5334
  });
5283
5335
  }
5284
- const usesNodeCheck = evidence.packageJsons.some((record2) => {
5285
- const scripts = record2.packageJson.scripts ?? {};
5336
+ const usesNodeCheck = evidence.packageJsons.some((record4) => {
5337
+ const scripts = record4.packageJson.scripts ?? {};
5286
5338
  return Object.values(scripts).some((cmd) => typeof cmd === "string" && /node\s+--check/.test(cmd));
5287
5339
  });
5288
5340
  if (hasBuild && usesNodeCheck) {
@@ -5362,7 +5414,7 @@ function nativeDashboardWidgetEvidence(evidence) {
5362
5414
  return unique(items);
5363
5415
  }
5364
5416
  function collectEvidence(repoRoot, application) {
5365
- const appRepoPath = applicationSourcePath(application.appDir);
5417
+ const appRepoPath = applicationSourcePath(repoRoot, application.appDir);
5366
5418
  const appRoot = path.join(repoRoot, appRepoPath);
5367
5419
  const packageJsonPath = path.join(appRoot, "package.json");
5368
5420
  const packageJson2 = readPackageJson(packageJsonPath);
@@ -5414,8 +5466,11 @@ function sharedAuthEvidence(evidence) {
5414
5466
  if (text.includes("TopoloAuthMiddleware")) items.push("source=TopoloAuthMiddleware");
5415
5467
  if (text.includes("TopoloClient")) items.push("source=TopoloClient");
5416
5468
  if (text.includes("TopoloOAuth")) items.push("source=TopoloOAuth");
5417
- if (/\.initialize(?:Session)?\(\s*\)/.test(text)) items.push("auth-boot=blocking-cookie-refresh");
5418
- if (/cookieRefresh:\s*['"]background['"]/.test(text)) items.push("auth-boot=background-cookie-refresh");
5469
+ const browserRecords = productionBrowserSourceRecords(evidence);
5470
+ const blockingBoot = browserRecords.find(({ text: source }) => /\b(?:auth|authClient|topoloAuth)\.initialize(?:Session)?\(\s*\)/i.test(source));
5471
+ const backgroundBoot = browserRecords.find(({ text: source }) => /cookieRefresh:\s*['"]background['"]/.test(source));
5472
+ if (blockingBoot) items.push("auth-boot=blocking-cookie-refresh", `auth-boot-file=${blockingBoot.path}`);
5473
+ if (backgroundBoot) items.push("auth-boot=background-cookie-refresh", `auth-background-file=${backgroundBoot.path}`);
5419
5474
  if (/Authorization.*Bearer/i.test(text) && (text.includes("/validate") || text.includes("TOPOLO_AUTH_URL") || text.includes("AUTH_BASE_URL") || text.includes("auth.topolo.app"))) {
5420
5475
  items.push("source=auth-validate-fetch");
5421
5476
  }
@@ -5434,7 +5489,8 @@ function documentedOperatorLocalAuthBoundaryEvidence(evidence) {
5434
5489
  }
5435
5490
  function sharedShellEvidence(evidence) {
5436
5491
  const deps = dependencyNames(evidence);
5437
- const text = evidence.source.text;
5492
+ const browserRecords = productionBrowserSourceRecords(evidence);
5493
+ const text = browserRecords.map((record4) => record4.text).join("\n");
5438
5494
  const items = [];
5439
5495
  if (deps.has("@topolo-io/app-shell")) items.push("dependency=@topolo-io/app-shell");
5440
5496
  if (deps.has("@topolo-io/ui-kit")) items.push("dependency=@topolo-io/ui-kit");
@@ -5454,11 +5510,9 @@ function sharedShellEvidence(evidence) {
5454
5510
  const hasVisualQaFixtures = hasVisualQaMode && /fixture|visualQaResponse|visualQaApi|mock[A-Z]\w*|devpreview/i.test(text) && /fetch|useQuery|api/i.test(text);
5455
5511
  if (hasVisualQaFixtures) items.push("source=visual-qa-fixtures");
5456
5512
  if (/from\s+['"]lucide-react['"]/.test(text)) items.push("local=direct-lucide-react");
5457
- if (/(?:icon|Icon)\s*:\s*['"][◈◇⬡⬢▣◉✉▷✦○◆✕⚙🔑💡⚠✓♫]/.test(text)) {
5458
- items.push("local=raw-symbol-icons");
5459
- }
5460
- if (/(?:<span[^>]*>|<div[^>]*>)[^<]{0,16}[◈◇⬡⬢▣◉✉▷✦○◆✕⚙🔑💡⚠✓♫]/.test(text)) {
5461
- items.push("local=raw-symbol-icons");
5513
+ const rawSymbolRecord = browserRecords.find(({ text: source }) => /(?:icon|Icon)\s*:\s*['"][◈◇⬡⬢▣◉✉▷✦○◆✕⚙🔑💡⚠✓♫]/.test(source) || /(?:<span[^>]*>|<div[^>]*>)[^<]{0,16}[◈◇⬡⬢▣◉✉▷✦○◆✕⚙🔑💡⚠✓♫]/.test(source));
5514
+ if (rawSymbolRecord) {
5515
+ items.push("local=raw-symbol-icons", `raw-symbol-file=${rawSymbolRecord.path}`);
5462
5516
  }
5463
5517
  if (text.includes(["Topolo", "Workspace", "Shell"].join(""))) items.push("forbidden=workspace-shell");
5464
5518
  if (/\bTopoloShell\b/.test(text)) items.push("legacy=TopoloShellFrame");
@@ -5488,6 +5542,16 @@ function hasLocalBrowserSurface(evidence) {
5488
5542
  (file) => file.endsWith(".tsx") || file.endsWith(".jsx") || file.includes("/src/client/") || file.includes("/client/")
5489
5543
  );
5490
5544
  }
5545
+ function productionBrowserSourceRecords(evidence) {
5546
+ return evidence.source.records.filter((record4) => {
5547
+ const sourcePath = record4.path.toLowerCase();
5548
+ if (/(^|\/)(?:__tests__|test|tests|scripts|fixtures|migrations)(\/|$)/.test(sourcePath)) return false;
5549
+ if (/\.(?:test|spec)\.[^.]+$/.test(sourcePath)) return false;
5550
+ if (/\/apps\/(?:api|analytics|gateway|jobs|queue|runtime|worker)\//.test(sourcePath)) return false;
5551
+ if (/\.(?:tsx|jsx)$/.test(sourcePath)) return true;
5552
+ return /\/(?:web|marketing|desktop)\/src\//.test(sourcePath) && /\.(?:ts|js)$/.test(sourcePath);
5553
+ });
5554
+ }
5491
5555
  function organizationScopeEvidence(evidence) {
5492
5556
  const text = evidence.source.text;
5493
5557
  const items = [];
@@ -5548,35 +5612,35 @@ function verificationEvidenceFor(evidence) {
5548
5612
  "test:auth-contract": "test",
5549
5613
  validate: "validate"
5550
5614
  };
5551
- for (const record2 of evidence.packageJsons) {
5552
- const scripts = record2.packageJson.scripts ?? {};
5553
- const isRootPackage = record2.path === evidence.packageJsonPath;
5615
+ for (const record4 of evidence.packageJsons) {
5616
+ const scripts = record4.packageJson.scripts ?? {};
5617
+ const isRootPackage = record4.path === evidence.packageJsonPath;
5554
5618
  const seenCanonical = /* @__PURE__ */ new Set();
5555
5619
  for (const [scriptName, canonical] of Object.entries(scriptAliases)) {
5556
5620
  if (!scripts[scriptName]) continue;
5557
5621
  if (seenCanonical.has(canonical)) continue;
5558
5622
  seenCanonical.add(canonical);
5559
- items.push(isRootPackage ? `script=${canonical}` : `script=${packageLabel(record2)}:${canonical}`);
5623
+ items.push(isRootPackage ? `script=${canonical}` : `script=${packageLabel(record4)}:${canonical}`);
5560
5624
  }
5561
5625
  }
5562
5626
  return unique(items);
5563
5627
  }
5564
5628
  function dependencyNames(evidence) {
5565
5629
  return new Set(
5566
- evidence.packageJsons.flatMap((record2) => [
5567
- ...Object.keys(record2.packageJson.dependencies ?? {}),
5568
- ...Object.keys(record2.packageJson.devDependencies ?? {}),
5569
- ...Object.keys(record2.packageJson.peerDependencies ?? {})
5630
+ evidence.packageJsons.flatMap((record4) => [
5631
+ ...Object.keys(record4.packageJson.dependencies ?? {}),
5632
+ ...Object.keys(record4.packageJson.devDependencies ?? {}),
5633
+ ...Object.keys(record4.packageJson.peerDependencies ?? {})
5570
5634
  ])
5571
5635
  );
5572
5636
  }
5573
5637
  function packageDependencyVersions(evidence, packageName) {
5574
5638
  return unique(
5575
- evidence.packageJsons.flatMap((record2) => {
5639
+ evidence.packageJsons.flatMap((record4) => {
5576
5640
  const specs = [
5577
- record2.packageJson.dependencies?.[packageName],
5578
- record2.packageJson.devDependencies?.[packageName],
5579
- record2.packageJson.peerDependencies?.[packageName]
5641
+ record4.packageJson.dependencies?.[packageName],
5642
+ record4.packageJson.devDependencies?.[packageName],
5643
+ record4.packageJson.peerDependencies?.[packageName]
5580
5644
  ];
5581
5645
  return specs.filter((spec) => typeof spec === "string");
5582
5646
  })
@@ -5604,7 +5668,7 @@ function matchingSystems(repoRoot, application) {
5604
5668
  const docsRoot = topoloDocsRoot(repoRoot);
5605
5669
  const systemsRoot = path.join(docsRoot, "src", "content", "systems");
5606
5670
  if (!isDirectory(systemsRoot)) return [];
5607
- const appRepoPath = applicationSourcePath(application.appDir);
5671
+ const appRepoPath = applicationSourcePath(repoRoot, application.appDir);
5608
5672
  return readdirSync(systemsRoot).filter((entry) => entry.endsWith(".json")).map((entry) => readJson(path.join(systemsRoot, entry))).filter((system) => Boolean(system)).filter((system) => {
5609
5673
  const repoPaths = [...system.repo_paths ?? [], ...system.repoPaths ?? []];
5610
5674
  return repoPaths.some(
@@ -5627,11 +5691,19 @@ function existingDocs(repoRoot, systems) {
5627
5691
  function topoloDocsRoot(repoRoot) {
5628
5692
  return path.join(repoRoot, "system-apps", "TopoloDocs", "apps", "web");
5629
5693
  }
5630
- function applicationSourcePath(appDir) {
5694
+ var APPLICATION_SOURCE_ROOTS = [
5695
+ "apps",
5696
+ "system-apps",
5697
+ "topolo-platform/services",
5698
+ "topolo-platform/core",
5699
+ "topolo-platform/packages"
5700
+ ];
5701
+ function applicationSourcePath(repoRoot, appDir) {
5631
5702
  if (appDir === "TopoloCli" || appDir === "TopoloCLI") return "topolo-platform/packages/topolo-cli";
5632
- if (appDir === "TopoloDocs") return "system-apps/TopoloDocs";
5633
- if (appDir === "TopoloAuth") return "topolo-platform/core/TopoloAuth";
5634
- if (appDir === "TopoloCloudControl") return "topolo-platform/core/TopoloCloudControl";
5703
+ for (const root of APPLICATION_SOURCE_ROOTS) {
5704
+ const relative4 = `${root}/${appDir}`;
5705
+ if (isDirectory(path.join(repoRoot, relative4))) return relative4;
5706
+ }
5635
5707
  return `apps/${appDir}`;
5636
5708
  }
5637
5709
  function docExists(contentRoot, docPath) {
@@ -5647,18 +5719,21 @@ function findWranglerFiles(appRoot) {
5647
5719
  return ["wrangler.json", "wrangler.jsonc", "wrangler.toml"].filter((file) => isFile(path.join(appRoot, file))).map((file) => relativeTopoloPath(path.join(appRoot, file)));
5648
5720
  }
5649
5721
  function readSourceIndex(root) {
5650
- if (!isDirectory(root)) return { files: [], text: "" };
5722
+ if (!isDirectory(root)) return { files: [], text: "", records: [] };
5651
5723
  const files = collectSourceFiles(root, 4e3);
5652
- const chunks = files.map((file) => {
5724
+ const records = files.map((file) => {
5725
+ let text = "";
5653
5726
  try {
5654
- return readFileSync3(file, "utf8");
5727
+ text = readFileSync3(file, "utf8");
5655
5728
  } catch {
5656
- return "";
5729
+ text = "";
5657
5730
  }
5731
+ return { path: relativeTopoloPath(file), text };
5658
5732
  });
5659
5733
  return {
5660
- files: files.map(relativeTopoloPath),
5661
- text: chunks.join("\n")
5734
+ files: records.map((record4) => record4.path),
5735
+ text: records.map((record4) => record4.text).join("\n"),
5736
+ records
5662
5737
  };
5663
5738
  }
5664
5739
  function collectSourceFiles(root, limit) {
@@ -5702,8 +5777,8 @@ function collectPackageJsons(root) {
5702
5777
  function readPackageJson(filePath) {
5703
5778
  return readJson(filePath);
5704
5779
  }
5705
- function packageLabel(record2) {
5706
- return record2.packageJson.name ?? relativeTopoloPath(path.dirname(record2.path));
5780
+ function packageLabel(record4) {
5781
+ return record4.packageJson.name ?? relativeTopoloPath(path.dirname(record4.path));
5707
5782
  }
5708
5783
  function sortedDirEntries(dir) {
5709
5784
  return readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
@@ -5792,14 +5867,14 @@ import { constants } from "fs";
5792
5867
  import { access } from "fs/promises";
5793
5868
  import { relative, resolve } from "path";
5794
5869
  function normalizeHomePath(value) {
5795
- const path6 = String(value || "/dashboard").trim();
5796
- if (!path6.startsWith("/") || path6.startsWith("//") || /[?#]/.test(path6)) {
5870
+ const path8 = String(value || "/dashboard").trim();
5871
+ if (!path8.startsWith("/") || path8.startsWith("//") || /[?#]/.test(path8)) {
5797
5872
  throw new Error("Invalid home path. Use an absolute application path such as /dashboard.");
5798
5873
  }
5799
- if (["/", "/login", "/auth/callback", "/getting-started"].includes(path6)) {
5874
+ if (["/", "/login", "/auth/callback", "/getting-started"].includes(path8)) {
5800
5875
  throw new Error("Home path must be a protected product route, not a reserved entry route.");
5801
5876
  }
5802
- return path6.replace(/\/$/, "") || "/dashboard";
5877
+ return path8.replace(/\/$/, "") || "/dashboard";
5803
5878
  }
5804
5879
  function normalizeHomeLabel(value) {
5805
5880
  const label = String(value || "Dashboard").trim();
@@ -5880,16 +5955,6 @@ function normalizeScaffoldProfile(value) {
5880
5955
  if (value === "spatial-assets") return value;
5881
5956
  throw new Error("Invalid scaffold profile. Use spatial-assets.");
5882
5957
  }
5883
- function resolveScaffoldResources(profile, options) {
5884
- const profileResources = profile === "spatial-assets" ? { d1: true, r2: true, queue: true, workflow: true, containerProcessing: true } : { d1: false, r2: false, queue: false, workflow: false, containerProcessing: false };
5885
- return {
5886
- d1: Boolean(profileResources.d1 || options.withD1),
5887
- r2: Boolean(profileResources.r2 || options.withR2),
5888
- queue: Boolean(profileResources.queue || options.withQueue),
5889
- workflow: Boolean(profileResources.workflow || options.withWorkflow),
5890
- containerProcessing: Boolean(profileResources.containerProcessing || options.withContainerProcessing)
5891
- };
5892
- }
5893
5958
  function normalizeContainerAppId(value, surfaceModel) {
5894
5959
  const trimmed = String(value || "").trim();
5895
5960
  if (surfaceModel === "mobile_module") {
@@ -5940,9 +6005,9 @@ function toTitle(value) {
5940
6005
  function json(value) {
5941
6006
  return JSON.stringify(value, null, 2);
5942
6007
  }
5943
- async function pathExists(path6) {
6008
+ async function pathExists(path8) {
5944
6009
  try {
5945
- await access(path6, constants.F_OK);
6010
+ await access(path8, constants.F_OK);
5946
6011
  return true;
5947
6012
  } catch {
5948
6013
  return false;
@@ -5950,8 +6015,8 @@ async function pathExists(path6) {
5950
6015
  }
5951
6016
  function resolveInside(root, filePath) {
5952
6017
  const resolved = resolve(root, filePath);
5953
- const relativePath = relative(root, resolved);
5954
- if (relativePath.startsWith("..") || relativePath === "" || relativePath.startsWith("/")) {
6018
+ const relativePath2 = relative(root, resolved);
6019
+ if (relativePath2.startsWith("..") || relativePath2 === "" || relativePath2.startsWith("/")) {
5955
6020
  throw new Error(`Scaffold file path escapes the target root: ${filePath}`);
5956
6021
  }
5957
6022
  return resolved;
@@ -5978,48 +6043,6 @@ var STAGING_CLOUDFLARE_ACCOUNT_ID = "4f4e1c69a3830946f9fea7b1eb7531ac";
5978
6043
  var DEVELOPMENT_CLOUDFLARE_ACCOUNT_ID = "0ebdeec262a245481a721fc4f9d6d798";
5979
6044
  var CLOUDFLARE_COMPATIBILITY_DATE = "2026-05-05";
5980
6045
 
5981
- // src/commands/app-scaffold-package-policy.ts
5982
- var TOPOLO_PACKAGE_SCAFFOLD_POLICY = {
5983
- "@topolo-io/api-key-management": "browser-baseline",
5984
- "@topolo-io/app-funnel": "browser-baseline",
5985
- "@topolo-io/app-launcher": "browser-baseline",
5986
- "@topolo-io/app-manifest": "domain-opt-in",
5987
- "@topolo-io/app-shell": "browser-baseline",
5988
- "@topolo-io/auth-client": "browser-baseline",
5989
- "@topolo-io/auth-middleware": "api-baseline",
5990
- "@topolo-io/billing-manifest": "domain-opt-in",
5991
- "@topolo-io/billing-ui": "domain-opt-in",
5992
- "@topolo-io/bridge": "domain-opt-in",
5993
- "@topolo-io/calendar": "domain-opt-in",
5994
- "@topolo-io/chat-widget": "browser-baseline",
5995
- "@topolo-io/data": "domain-opt-in",
5996
- "@topolo-io/design-tokens": "browser-baseline",
5997
- "@topolo-io/error-middleware": "api-baseline",
5998
- "@topolo-io/mailbox": "domain-opt-in",
5999
- "@topolo-io/mobile-error-middleware": "domain-opt-in",
6000
- "@topolo-io/mobile-experience-contracts": "domain-opt-in",
6001
- "@topolo-io/nexus-client": "api-baseline",
6002
- "@topolo-io/node-canvas": "domain-opt-in",
6003
- "@topolo-io/notifications": "browser-baseline",
6004
- "@topolo-io/onboarding": "browser-baseline",
6005
- "@topolo-io/platform-mcp": "tooling",
6006
- "@topolo-io/resource-context": "browser-baseline",
6007
- "@topolo-io/security-headers": "api-baseline",
6008
- "@topolo-io/tour": "browser-baseline",
6009
- "@topolo-io/ui-kit": "browser-baseline",
6010
- "@topolo-io/widget-server": "api-baseline",
6011
- "@topolo-io/worker-runtime": "api-baseline",
6012
- "@topolo/cli": "tooling",
6013
- "@topolo/login": "developer-baseline",
6014
- "@topolo/mcp": "tooling",
6015
- "@topolo/sdk": "api-baseline"
6016
- };
6017
- function packagesForScaffoldClass(scaffoldClass) {
6018
- return Object.entries(TOPOLO_PACKAGE_SCAFFOLD_POLICY).filter(([, value]) => value === scaffoldClass).map(([name]) => name).sort();
6019
- }
6020
- var PLATFORM_BROWSER_TOPOLO_PACKAGES = packagesForScaffoldClass("browser-baseline");
6021
- var PLATFORM_API_TOPOLO_PACKAGES = packagesForScaffoldClass("api-baseline");
6022
-
6023
6046
  // src/commands/app-scaffold-package-template.ts
6024
6047
  function packageJson(application, target, options) {
6025
6048
  const dependencies = {};
@@ -6044,9 +6067,6 @@ function packageJson(application, target, options) {
6044
6067
  devDependencies.autoprefixer = "10.5.0";
6045
6068
  devDependencies.postcss = "8.5.6";
6046
6069
  devDependencies.tailwindcss = "3.4.19";
6047
- for (const packageName of PLATFORM_BROWSER_TOPOLO_PACKAGES) {
6048
- dependencies[packageName] = topoloPackageVersion(packageName, target);
6049
- }
6050
6070
  dependencies["react-router-dom"] = "^7.6.2";
6051
6071
  } else {
6052
6072
  dependencies["@topolo/login"] = topoloPackageVersion("@topolo/login", target);
@@ -6055,9 +6075,6 @@ function packageJson(application, target, options) {
6055
6075
  if (options.api) {
6056
6076
  devDependencies["@cloudflare/workers-types"] = "^5.20260716.1";
6057
6077
  if (target === "platform") {
6058
- for (const packageName of PLATFORM_API_TOPOLO_PACKAGES) {
6059
- dependencies[packageName] = topoloPackageVersion(packageName, target);
6060
- }
6061
6078
  dependencies.hono = "^4";
6062
6079
  dependencies.zod = "^4.4.3";
6063
6080
  scripts.deploy = `node -e 'console.error("\\nX Bare deploy is disabled. Use deploy:staging or deploy:production.\\n"); process.exit(1)'`;
@@ -6069,6 +6086,9 @@ function packageJson(application, target, options) {
6069
6086
  }
6070
6087
  devDependencies.wrangler = "^4.91.0";
6071
6088
  }
6089
+ for (const packageName of options.packages) {
6090
+ dependencies[packageName] = topoloPackageVersion(packageName, target);
6091
+ }
6072
6092
  if (target === "platform") {
6073
6093
  const testDirs = [];
6074
6094
  if (options.browser) testDirs.push("apps/web");
@@ -6090,7 +6110,7 @@ function packageJson(application, target, options) {
6090
6110
  }
6091
6111
  }
6092
6112
  }
6093
- if (target === "platform" && hasScaffoldResources(options.resources)) {
6113
+ if (target === "platform" && (options.api || options.browser)) {
6094
6114
  const deployChecks = [
6095
6115
  ...options.api ? ["wrangler deploy --config apps/api/wrangler.toml --env staging --dry-run --outdir .wrangler/api-build"] : [],
6096
6116
  ...options.browser ? ["wrangler deploy --config apps/web/wrangler.toml --env staging --dry-run --outdir .wrangler/web-build"] : []
@@ -6174,6 +6194,320 @@ function resourceConfig(application, profile) {
6174
6194
  };
6175
6195
  }
6176
6196
 
6197
+ // src/commands/app-capability-registry.ts
6198
+ var APPLICATION_CAPABILITY_REGISTRY_VERSION = "1";
6199
+ var TOPOLO_APP_CAPABILITY_IDS = [
6200
+ "login",
6201
+ "launcher",
6202
+ "marketplace",
6203
+ "hosting",
6204
+ "api",
6205
+ "widget",
6206
+ "onboarding",
6207
+ "resource_management",
6208
+ "notifications",
6209
+ "notification_producer",
6210
+ "billing",
6211
+ "metering",
6212
+ "nexus_ai",
6213
+ "integrations",
6214
+ "webhooks",
6215
+ "automation",
6216
+ "agent_access",
6217
+ "data",
6218
+ "object_storage",
6219
+ "background_jobs",
6220
+ "durable_workflows",
6221
+ "privacy",
6222
+ "chat",
6223
+ "calendar",
6224
+ "mailbox",
6225
+ "mobile"
6226
+ ];
6227
+ var bothTargets = ["platform", "developer"];
6228
+ var platformTarget = ["platform"];
6229
+ var APPLICATION_CAPABILITY_REGISTRY = {
6230
+ login: capability("login", "TopoloAuth", bothTargets, [], ["browser"], {
6231
+ platform: ["@topolo-io/auth-client", "@topolo-io/app-shell"],
6232
+ developer: ["@topolo/login"]
6233
+ }, ["openid", "profile", "email", "organization:read"], [], [], ["auth-client"], ["auth-login-smoke"]),
6234
+ launcher: capability("launcher", "TopoloDevelopers + TopoloAuth", bothTargets, ["login"], ["browser"], {
6235
+ platform: ["@topolo-io/app-funnel", "@topolo-io/app-launcher", "@topolo-io/app-shell", "@topolo-io/design-tokens", "@topolo-io/ui-kit"],
6236
+ developer: []
6237
+ }, [], [], [], ["launcher-registration"], ["launcher-catalog-smoke"]),
6238
+ marketplace: capability("marketplace", "TopoloDevelopers", bothTargets, ["launcher"], ["browser"], emptyPackages(), [], [], [], ["marketplace-registration"], ["marketplace-projection-check"]),
6239
+ hosting: capability("hosting", "TopoloCloudControl or external owner", bothTargets, [], [], emptyPackages(), [], [], [], ["environment-manifest", "health-contract"], ["environment-health-smoke"]),
6240
+ api: capability("api", "Application runtime + TopoloAuth", bothTargets, [], ["api"], {
6241
+ platform: ["@topolo-io/auth-middleware", "@topolo-io/error-middleware", "@topolo-io/security-headers", "@topolo-io/worker-runtime", "@topolo/sdk"],
6242
+ developer: ["@topolo/sdk"]
6243
+ }, ["entitlements:read"], ["workspace:read", "workspace:write"], [], ["protected-worker"], ["api-auth-allow-deny"]),
6244
+ widget: capability("widget", "Application runtime + TopoloDevelopers", bothTargets, ["api"], ["api"], {
6245
+ platform: ["@topolo-io/widget-server", "@topolo/sdk"],
6246
+ developer: ["@topolo/sdk"]
6247
+ }, [], ["workspace:read"], [], ["widget-route", "widget-action-contract"], ["widget-contract-smoke"]),
6248
+ onboarding: capability("onboarding", "TopoloAuth + shared onboarding packages", platformTarget, ["launcher"], ["browser"], {
6249
+ platform: ["@topolo-io/onboarding", "@topolo-io/tour"],
6250
+ developer: []
6251
+ }, [], [], [], ["getting-started-route"], ["onboarding-progress-smoke"]),
6252
+ resource_management: capability("resource_management", "TopoloAuth + application resource catalog", platformTarget, ["launcher"], ["browser"], {
6253
+ platform: ["@topolo-io/api-key-management", "@topolo-io/resource-context"],
6254
+ developer: []
6255
+ }, [], ["workspace:read"], [], ["resource-selector"], ["resource-binding-smoke"]),
6256
+ notifications: capability("notifications", "TopoloNotify + TopoloAuth", platformTarget, ["launcher"], ["browser"], {
6257
+ platform: ["@topolo-io/notifications"],
6258
+ developer: []
6259
+ }, [], [], [], ["notification-consumer"], ["notification-inbox-smoke"]),
6260
+ notification_producer: capability("notification_producer", "TopoloNotify + TopoloDevelopers", platformTarget, ["api"], ["api"], {
6261
+ platform: ["@topolo-io/notifications", "@topolo-io/auth-middleware"],
6262
+ developer: []
6263
+ }, [], ["events:write"], [], ["notification-producer-adapter", "notification-contracts"], ["notification-delivery-smoke"]),
6264
+ billing: capability("billing", "TopoloPay + TopoloAuth", bothTargets, ["launcher"], ["browser"], {
6265
+ platform: ["@topolo-io/billing-manifest", "@topolo-io/billing-ui"],
6266
+ developer: []
6267
+ }, ["entitlements:read"], [], [], ["pricing-manifest"], ["pricing-registration-smoke"]),
6268
+ metering: capability("metering", "TopoloNexus", platformTarget, ["api"], ["api"], {
6269
+ platform: ["@topolo-io/nexus-client", "@topolo-io/worker-runtime"],
6270
+ developer: []
6271
+ }, [], ["usage:write"], [], ["nexus-usage-adapter"], ["usage-attribution-smoke"]),
6272
+ nexus_ai: capability("nexus_ai", "TopoloNexus", platformTarget, ["metering"], ["api"], {
6273
+ platform: ["@topolo-io/nexus-client"],
6274
+ developer: []
6275
+ }, [], ["usage:write"], [], ["nexus-ai-adapter"], ["ai-budget-rejection-smoke"]),
6276
+ integrations: capability("integrations", "TopoloNexus", platformTarget, ["api"], ["api"], {
6277
+ platform: ["@topolo-io/nexus-client"],
6278
+ developer: []
6279
+ }, [], [], [], ["integration-connection-adapter"], ["integration-connection-smoke"]),
6280
+ webhooks: capability("webhooks", "TopoloNotify", bothTargets, ["api"], ["api"], {
6281
+ platform: ["@topolo-io/notifications"],
6282
+ developer: []
6283
+ }, [], [], [], ["webhook-receiver"], ["webhook-signature-smoke"]),
6284
+ automation: capability("automation", "TopoloFlow + application runtime", platformTarget, ["api"], ["api"], {
6285
+ platform: ["@topolo/sdk"],
6286
+ developer: []
6287
+ }, [], [], [], ["flow-adapter"], ["flow-run-smoke"]),
6288
+ agent_access: capability("agent_access", "TopoloDevelopers + TopoloAgent", bothTargets, ["widget"], ["api"], {
6289
+ platform: ["@topolo-io/worker-runtime", "@topolo/sdk"],
6290
+ developer: ["@topolo/sdk"]
6291
+ }, [], ["workspace:read"], [], ["action-contracts"], ["credential-scoped-action-smoke"]),
6292
+ data: capability("data", "Application runtime through @topolo-io/data", platformTarget, ["api"], ["api"], {
6293
+ platform: ["@topolo-io/data"],
6294
+ developer: []
6295
+ }, [], ["workspace:read", "workspace:write"], ["d1"], ["workspace-migration", "data-port"], ["tenant-isolation-smoke"]),
6296
+ object_storage: capability("object_storage", "Application runtime through @topolo-io/data", platformTarget, ["data"], ["api"], {
6297
+ platform: ["@topolo-io/data"],
6298
+ developer: []
6299
+ }, [], ["workspace:read", "workspace:write"], ["r2"], ["object-store-port"], ["object-lifecycle-smoke"]),
6300
+ background_jobs: capability("background_jobs", "Application runtime", platformTarget, ["api"], ["api"], emptyPackages(), [], ["workspace:write"], ["queue"], ["queue-producer", "queue-consumer"], ["queue-execution-smoke"]),
6301
+ durable_workflows: capability("durable_workflows", "Application runtime + TopoloFlow", platformTarget, ["background_jobs"], ["api"], emptyPackages(), [], ["workspace:write"], ["workflow"], ["workflow-entrypoint"], ["workflow-execution-smoke"]),
6302
+ privacy: capability("privacy", "TopoloConsent + application data owner", platformTarget, ["api"], ["api"], emptyPackages(), [], [], [], ["privacy-adapter", "retention-declaration"], ["dsr-export-delete-smoke"]),
6303
+ chat: capability("chat", "TopoloChat", platformTarget, ["launcher"], ["browser"], {
6304
+ platform: ["@topolo-io/chat-widget"],
6305
+ developer: []
6306
+ }, [], [], [], ["chat-surface"], ["chat-surface-smoke"]),
6307
+ calendar: capability("calendar", "TopoloCalendar", platformTarget, ["launcher"], ["browser"], {
6308
+ platform: ["@topolo-io/calendar"],
6309
+ developer: []
6310
+ }, [], [], [], ["calendar-surface"], ["calendar-surface-smoke"]),
6311
+ mailbox: capability("mailbox", "TopoloMail", platformTarget, ["launcher"], ["browser"], {
6312
+ platform: ["@topolo-io/mailbox"],
6313
+ developer: []
6314
+ }, [], [], [], ["mailbox-surface"], ["mailbox-surface-smoke"]),
6315
+ mobile: capability("mobile", "TopoloOne + application native runtime", bothTargets, [], ["mobile"], {
6316
+ platform: ["@topolo-io/bridge", "@topolo-io/mobile-error-middleware", "@topolo-io/mobile-experience-contracts"],
6317
+ developer: []
6318
+ }, [], [], [], ["mobile-experience-metadata"], ["native-experience-smoke"])
6319
+ };
6320
+ var PROFILE_CAPABILITIES = {
6321
+ "spatial-assets": ["object_storage", "background_jobs", "durable_workflows"]
6322
+ };
6323
+ function resolveApplicationCapabilities(options) {
6324
+ const requested = options.requested ?? [];
6325
+ const unknown = requested.filter((id) => !isTopoloAppCapability(id));
6326
+ if (unknown.length > 0) {
6327
+ throw new Error(`Unknown application capability: ${unknown.join(", ")}. Supported capabilities: ${TOPOLO_APP_CAPABILITY_IDS.join(", ")}.`);
6328
+ }
6329
+ if (options.profile && !PROFILE_CAPABILITIES[options.profile]) {
6330
+ throw new Error(`Unknown capability profile: ${options.profile}.`);
6331
+ }
6332
+ const selected = /* @__PURE__ */ new Set();
6333
+ const add = (id) => {
6334
+ const definition = APPLICATION_CAPABILITY_REGISTRY[id];
6335
+ if (!definition.targets.includes(options.target)) {
6336
+ throw new Error(`Capability ${id} is not available to ${options.target} applications.`);
6337
+ }
6338
+ for (const dependency of definition.requires) add(dependency);
6339
+ selected.add(id);
6340
+ };
6341
+ if (options.browser) {
6342
+ add("login");
6343
+ add("launcher");
6344
+ add("hosting");
6345
+ if (options.target === "platform") {
6346
+ add("onboarding");
6347
+ add("resource_management");
6348
+ add("notifications");
6349
+ }
6350
+ }
6351
+ if (options.distributionModel === "marketplace" && options.browser) add("marketplace");
6352
+ if (options.api) {
6353
+ add("api");
6354
+ add("widget");
6355
+ if (options.target === "platform") add("agent_access");
6356
+ }
6357
+ if (options.mobile) add("mobile");
6358
+ for (const id of options.profile ? PROFILE_CAPABILITIES[options.profile] ?? [] : []) add(id);
6359
+ for (const id of requested) add(id);
6360
+ for (const id of selected) {
6361
+ const definition = APPLICATION_CAPABILITY_REGISTRY[id];
6362
+ if (definition.surfaces.includes("browser") && !options.browser) {
6363
+ throw new Error(`Capability ${id} requires a browser surface.`);
6364
+ }
6365
+ if (definition.surfaces.includes("api") && !options.api) {
6366
+ throw new Error(`Capability ${id} requires an API surface.`);
6367
+ }
6368
+ if (definition.surfaces.includes("mobile") && !options.mobile) {
6369
+ throw new Error(`Capability ${id} requires a mobile surface model.`);
6370
+ }
6371
+ }
6372
+ const ordered = TOPOLO_APP_CAPABILITY_IDS.filter((id) => selected.has(id));
6373
+ const definitions = ordered.map((id) => APPLICATION_CAPABILITY_REGISTRY[id]);
6374
+ const targetPackages = options.target === "platform" ? "platform" : "developer";
6375
+ return {
6376
+ registryVersion: APPLICATION_CAPABILITY_REGISTRY_VERSION,
6377
+ selected: ordered,
6378
+ declarations: ordered.map((id) => ({ id, version: 1 })),
6379
+ packages: unique2(definitions.flatMap((definition) => definition.packages[targetPackages])),
6380
+ authScopes: unique2(definitions.flatMap((definition) => definition.authScopes)),
6381
+ servicePermissions: unique2(definitions.flatMap((definition) => definition.servicePermissions)),
6382
+ resources: unique2(definitions.flatMap((definition) => definition.resources)),
6383
+ artifacts: unique2(definitions.flatMap((definition) => definition.artifacts)),
6384
+ stagingEvidence: unique2(definitions.flatMap((definition) => definition.stagingEvidence))
6385
+ };
6386
+ }
6387
+ function isTopoloAppCapability(value) {
6388
+ return TOPOLO_APP_CAPABILITY_IDS.includes(value);
6389
+ }
6390
+ function capability(id, owner, targets, requires, surfaces, packages, authScopes, servicePermissions, resources, artifacts, stagingEvidence) {
6391
+ return { id, version: 1, owner, targets, requires, surfaces, packages, authScopes, servicePermissions, resources, artifacts, stagingEvidence };
6392
+ }
6393
+ function emptyPackages() {
6394
+ return { platform: [], developer: [] };
6395
+ }
6396
+ function unique2(values) {
6397
+ return Array.from(new Set(values));
6398
+ }
6399
+
6400
+ // src/commands/app-capability-artifacts.ts
6401
+ function capabilityCompositionProjection(capabilities) {
6402
+ return {
6403
+ registryVersion: capabilities.registryVersion,
6404
+ selected: capabilities.declarations,
6405
+ artifacts: capabilities.artifacts,
6406
+ stagingEvidence: capabilities.stagingEvidence
6407
+ };
6408
+ }
6409
+ function capabilityContractProjection(capabilities) {
6410
+ return {
6411
+ registryVersion: APPLICATION_CAPABILITY_REGISTRY_VERSION,
6412
+ selected: capabilities.selected.map((id) => {
6413
+ const definition = APPLICATION_CAPABILITY_REGISTRY[id];
6414
+ return {
6415
+ id,
6416
+ version: definition.version,
6417
+ owner: definition.owner,
6418
+ packages: definition.packages,
6419
+ authScopes: definition.authScopes,
6420
+ servicePermissions: definition.servicePermissions,
6421
+ resources: definition.resources,
6422
+ artifacts: definition.artifacts,
6423
+ stagingEvidence: definition.stagingEvidence
6424
+ };
6425
+ })
6426
+ };
6427
+ }
6428
+ function defaultPricingManifest(options) {
6429
+ if (!options.appId) throw new Error("Billing composition requires a canonical appId.");
6430
+ return {
6431
+ appId: options.appId,
6432
+ developerOrgId: null,
6433
+ schemaVersion: 1,
6434
+ marketingCopy: { tagline: options.description, marketplaceBadges: ["free"] },
6435
+ meters: [],
6436
+ plans: [{
6437
+ id: "free",
6438
+ tier: "free",
6439
+ displayName: "Free",
6440
+ summary: `Start using ${options.displayName}.`,
6441
+ pricing: { model: "free" },
6442
+ bullets: ["Shared Topolo identity and application access"],
6443
+ isDefault: true
6444
+ }]
6445
+ };
6446
+ }
6447
+
6448
+ // src/commands/app-capability-adapter-template.ts
6449
+ function managedCapabilityAdapter(capabilities) {
6450
+ const notifications = capabilities.includes("notification_producer");
6451
+ const nexus = capabilities.some((capability2) => capability2 === "metering" || capability2 === "nexus_ai" || capability2 === "integrations");
6452
+ const webhooks = capabilities.includes("webhooks");
6453
+ if (!notifications && !nexus && !webhooks) return null;
6454
+ return [
6455
+ ...notifications || nexus ? ["import { createServiceClient } from '@topolo-io/auth-middleware';"] : [],
6456
+ ...notifications ? ["import { TopoloEvents } from '@topolo-io/notifications';"] : [],
6457
+ ...webhooks ? ["import { WEBHOOK_SIGNATURE_HEADER, verifyWebhookSignature } from '@topolo-io/notifications';"] : [],
6458
+ ...nexus ? ["import { createNexusClient } from '@topolo-io/nexus-client';"] : [],
6459
+ "",
6460
+ "export interface TopoloCapabilityEnv {",
6461
+ " APP_ID?: string;",
6462
+ " TOPOLO_SERVICE_CLIENT_SECRET?: string;",
6463
+ ...notifications ? [" TOPOLO_NOTIFY_URL?: string;"] : [],
6464
+ ...nexus ? [" NEXUS_GATEWAY_URL?: string;"] : [],
6465
+ ...webhooks ? [" TOPOLO_WEBHOOK_SECRET?: string;"] : [],
6466
+ "}",
6467
+ ...notifications ? [
6468
+ "",
6469
+ "export function createTopoloEvents(env: TopoloCapabilityEnv): TopoloEvents {",
6470
+ " if (!env.APP_ID) throw new Error('APP_ID is required for notification attribution.');",
6471
+ " if (!env.TOPOLO_NOTIFY_URL) throw new Error('TOPOLO_NOTIFY_URL is required for notification delivery.');",
6472
+ " return new TopoloEvents({",
6473
+ " appId: env.APP_ID,",
6474
+ " notifyBaseUrl: env.TOPOLO_NOTIFY_URL,",
6475
+ " serviceClient: createServiceClient(env),",
6476
+ " });",
6477
+ "}"
6478
+ ] : [],
6479
+ ...nexus ? [
6480
+ "",
6481
+ "export function createTopoloNexus(env: TopoloCapabilityEnv, organizationId: string) {",
6482
+ " if (!env.APP_ID) throw new Error('APP_ID is required for Nexus attribution.');",
6483
+ " if (!env.NEXUS_GATEWAY_URL) throw new Error('NEXUS_GATEWAY_URL is required for Nexus requests.');",
6484
+ " if (!organizationId) throw new Error('Verified organizationId is required for Nexus requests.');",
6485
+ " const serviceClient = createServiceClient(env);",
6486
+ " return createNexusClient({",
6487
+ " appId: env.APP_ID,",
6488
+ " gatewayUrl: env.NEXUS_GATEWAY_URL,",
6489
+ " organizationId,",
6490
+ " serviceTokenProvider: (scope) => serviceClient.getToken(scope ?? 'usage:write'),",
6491
+ " });",
6492
+ "}"
6493
+ ] : [],
6494
+ ...webhooks ? [
6495
+ "",
6496
+ "export async function verifyTopoloWebhookRequest(",
6497
+ " request: Request,",
6498
+ " env: TopoloCapabilityEnv,",
6499
+ "): Promise<{ ok: true; body: string } | { ok: false; response: Response }> {",
6500
+ " if (!env.TOPOLO_WEBHOOK_SECRET) return { ok: false, response: Response.json({ error: 'webhook_secret_missing' }, { status: 503 }) };",
6501
+ " const body = await request.text();",
6502
+ " const valid = await verifyWebhookSignature(env.TOPOLO_WEBHOOK_SECRET, body, request.headers.get(WEBHOOK_SIGNATURE_HEADER));",
6503
+ " if (!valid) return { ok: false, response: Response.json({ error: 'invalid_webhook_signature' }, { status: 401 }) };",
6504
+ " return { ok: true, body };",
6505
+ "}"
6506
+ ] : [],
6507
+ ""
6508
+ ].join("\n");
6509
+ }
6510
+
6177
6511
  // src/commands/app-scaffold-browser-template.ts
6178
6512
  function readme(application, target, options) {
6179
6513
  const displayName = toTitle(application.name);
@@ -7490,9 +7824,15 @@ function platformWorkerTs(application, options) {
7490
7824
  ];
7491
7825
  const workflowExport = options.resources.workflow ? [
7492
7826
  "",
7493
- `export class ${resource.workflowClassName} extends WorkflowEntrypoint<Env, unknown> {`,
7494
- " async run(_event: Readonly<WorkflowEvent<unknown>>, step: WorkflowStep): Promise<unknown> {",
7495
- " return step.do('scaffold no-op', async () => ({ status: 'pending_implementation' }));",
7827
+ `export class ${resource.workflowClassName} extends WorkflowEntrypoint<Env, CapabilityWorkflowInput> {`,
7828
+ " async run(event: Readonly<WorkflowEvent<CapabilityWorkflowInput>>, step: WorkflowStep): Promise<unknown> {",
7829
+ " if (event.payload.type !== 'capability.probe' || !event.payload.workspaceId) {",
7830
+ " throw new Error('Unsupported workflow message. Implement a vertical workflow contract before dispatching it.');",
7831
+ " }",
7832
+ " return step.do('capability probe', async () => ({",
7833
+ " status: 'completed',",
7834
+ " workspaceId: event.payload.workspaceId,",
7835
+ " }));",
7496
7836
  " }",
7497
7837
  "}"
7498
7838
  ] : [];
@@ -7527,6 +7867,21 @@ function platformWorkerTs(application, options) {
7527
7867
  "export interface Env {",
7528
7868
  ...envBindings,
7529
7869
  "}",
7870
+ ...options.resources.queue ? [
7871
+ "",
7872
+ "interface CapabilityJob {",
7873
+ " type: 'capability.probe';",
7874
+ " workspaceId: string;",
7875
+ " payload?: unknown;",
7876
+ "}"
7877
+ ] : [],
7878
+ ...options.resources.workflow ? [
7879
+ "",
7880
+ "interface CapabilityWorkflowInput {",
7881
+ " type: 'capability.probe';",
7882
+ " workspaceId: string;",
7883
+ "}"
7884
+ ] : [],
7530
7885
  "",
7531
7886
  "function buildWidgets(): TopoloWidgetDraft[] {",
7532
7887
  " return [",
@@ -7623,6 +7978,14 @@ function platformWorkerTs(application, options) {
7623
7978
  " bindings.__rt = rt;",
7624
7979
  " return securedFetch(request, bindings, ctx as ExecutionContext);",
7625
7980
  " }),",
7981
+ ...options.resources.queue ? [
7982
+ " async queue(batch: MessageBatch<CapabilityJob>): Promise<void> {",
7983
+ " for (const message of batch.messages) {",
7984
+ " if (message.body?.type === 'capability.probe' && message.body.workspaceId) message.ack();",
7985
+ " else message.retry();",
7986
+ " }",
7987
+ " },"
7988
+ ] : [],
7626
7989
  "};",
7627
7990
  "",
7628
7991
  "function errorMessage(error: unknown): string {",
@@ -8040,14 +8403,24 @@ function topoloAppManifestJson(application, options) {
8040
8403
  development: "https://nexus.topolo.dev",
8041
8404
  staging: "https://nexus.stg.topolo.us",
8042
8405
  production: "https://nexus.topolo.app"
8043
- }
8406
+ },
8407
+ ...options.payload.capabilities.includes("notification_producer") ? {
8408
+ TOPOLO_NOTIFY_URL: {
8409
+ development: "https://notify.topolo.dev",
8410
+ staging: "https://notify.stg.topolo.us",
8411
+ production: "https://notify.topolo.app"
8412
+ }
8413
+ } : {}
8044
8414
  // No NEXUS_APP_ID: generate-wrangler binds APP_ID (the app_id) into every
8045
8415
  // env, and worker-runtime reads env.APP_ID for Nexus usage attribution,
8046
8416
  // so a second copy of the same identity would only be a chance to drift.
8047
8417
  // It previously carried the SLUG, which reads as a working identity while
8048
8418
  // Nexus attributes by app_id.
8049
8419
  },
8050
- secrets: ["TOPOLO_SERVICE_CLIENT_SECRET"],
8420
+ secrets: [
8421
+ "TOPOLO_SERVICE_CLIENT_SECRET",
8422
+ ...options.payload.capabilities.includes("webhooks") ? ["TOPOLO_WEBHOOK_SECRET"] : []
8423
+ ],
8051
8424
  ...Object.keys(bindings).length ? { bindings } : {}
8052
8425
  };
8053
8426
  }
@@ -8108,6 +8481,12 @@ function topoloAppManifestJson(application, options) {
8108
8481
  }
8109
8482
  },
8110
8483
  targets,
8484
+ capabilityComposition: {
8485
+ registryVersion: options.payload.capabilityComposition.registryVersion,
8486
+ selected: options.payload.capabilityComposition.declarations,
8487
+ artifacts: options.payload.capabilityComposition.artifacts,
8488
+ stagingEvidence: options.payload.capabilityComposition.stagingEvidence
8489
+ },
8111
8490
  registration: {
8112
8491
  ownerType: options.payload.ownerType,
8113
8492
  portfolio: options.payload.portfolio,
@@ -8126,7 +8505,14 @@ function topoloAppManifestJson(application, options) {
8126
8505
  supportEmail: "support@topolo.io",
8127
8506
  auth: {
8128
8507
  callbackPath: "/auth/callback",
8129
- requestedScopes: options.api ? ["openid", "profile", "email", "organization:read", "entitlements:read", `${application.id}:read`] : ["openid", "profile", "email", "organization:read"]
8508
+ requestedScopes: unique3([
8509
+ "openid",
8510
+ "profile",
8511
+ "email",
8512
+ "organization:read",
8513
+ ...options.payload.capabilityComposition.authScopes,
8514
+ ...options.api ? [`${application.id}:read`] : []
8515
+ ])
8130
8516
  },
8131
8517
  capabilities: options.payload.capabilities,
8132
8518
  marketplace: {
@@ -8138,7 +8524,7 @@ function topoloAppManifestJson(application, options) {
8138
8524
  docsUrl: `https://docs.topolo.app/applications/${slug}`
8139
8525
  }
8140
8526
  },
8141
- notifications: { role: "consumer_only" },
8527
+ notifications: options.payload.capabilities.includes("notification_producer") ? { role: "producer", contracts: "topolo.notification-contracts.json" } : options.payload.capabilities.includes("notifications") ? { role: "consumer_only" } : { role: "internal_non_producer" },
8142
8528
  ...actions.length ? { actions } : {}
8143
8529
  })}
8144
8530
  `;
@@ -8514,12 +8900,18 @@ function serviceManifestJson(application, options) {
8514
8900
  description: `Manage ${title} ${resourceLabel} data`,
8515
8901
  resourcePattern: null
8516
8902
  },
8517
- {
8903
+ ...options.servicePermissions.includes("usage:write") ? [{
8518
8904
  id: `perm_${permissionPrefix}_usage_write`,
8519
8905
  name: "usage:write",
8520
8906
  description: `Record Nexus usage events for ${title}`,
8521
8907
  resourcePattern: null
8522
- }
8908
+ }] : [],
8909
+ ...options.servicePermissions.includes("events:write") ? [{
8910
+ id: `perm_${permissionPrefix}_events_write`,
8911
+ name: "events:write",
8912
+ description: `Publish governed notification events for ${title}`,
8913
+ resourcePattern: null
8914
+ }] : []
8523
8915
  ],
8524
8916
  apiKeyScopes: [
8525
8917
  {
@@ -8534,12 +8926,18 @@ function serviceManifestJson(application, options) {
8534
8926
  description: `Manage ${title} ${resourceLabel} data`,
8535
8927
  resourcePattern: null
8536
8928
  },
8537
- {
8929
+ ...options.servicePermissions.includes("usage:write") ? [{
8538
8930
  id: `aks_${permissionPrefix}_usage_write`,
8539
8931
  name: "usage.write",
8540
8932
  description: `Record Nexus usage events for ${title}`,
8541
8933
  resourcePattern: null
8542
- }
8934
+ }] : [],
8935
+ ...options.servicePermissions.includes("events:write") ? [{
8936
+ id: `aks_${permissionPrefix}_events_write`,
8937
+ name: "events.write",
8938
+ description: `Publish governed notification events for ${title}`,
8939
+ resourcePattern: null
8940
+ }] : []
8543
8941
  ],
8544
8942
  roleBundles: {
8545
8943
  admin: ["workspace:read", "workspace:write"],
@@ -8554,7 +8952,14 @@ function developerManifest(application, options) {
8554
8952
  const callbackPath = "/auth/callback";
8555
8953
  const localCallbackUrl = "http://localhost:5173/auth/callback";
8556
8954
  const productionCallbackUrl = application.productionUrl ? `${application.productionUrl.replace(/\/$/, "")}${callbackPath}` : null;
8557
- const requestedScopes = options.api ? ["openid", "profile", "email", "organization:read", "entitlements:read", `${application.id}:read`] : ["openid", "profile", "email", "organization:read"];
8955
+ const requestedScopes = unique3([
8956
+ "openid",
8957
+ "profile",
8958
+ "email",
8959
+ "organization:read",
8960
+ ...options.capabilityComposition.authScopes,
8961
+ ...options.api ? [`${application.id}:read`] : []
8962
+ ]);
8558
8963
  return `${json({
8559
8964
  schema: "https://topolo.app/schemas/topolo-app-manifest.v1.json",
8560
8965
  id: application.id,
@@ -8569,17 +8974,9 @@ function developerManifest(application, options) {
8569
8974
  app_id: application.appId,
8570
8975
  capabilities: {
8571
8976
  selected: options.capabilities,
8572
- supported_v1: [
8573
- "login",
8574
- "launcher",
8575
- "marketplace",
8576
- "api",
8577
- "widget",
8578
- "billing",
8579
- "nexus_ai",
8580
- "agent_access",
8581
- "hosting"
8582
- ]
8977
+ registry_version: options.capabilityComposition.registryVersion,
8978
+ declarations: options.capabilityComposition.declarations,
8979
+ supported_v1: TOPOLO_APP_CAPABILITY_IDS
8583
8980
  },
8584
8981
  auth: {
8585
8982
  provider: "topolo",
@@ -8641,10 +9038,13 @@ function developerManifest(application, options) {
8641
9038
  docsUrl: application.productionUrl || "https://developers.topolo.app"
8642
9039
  }
8643
9040
  },
8644
- notifications: { role: "consumer_only" }
9041
+ notifications: options.capabilities.includes("notification_producer") ? { role: "producer", contracts: "topolo.notification-contracts.json" } : options.capabilities.includes("notifications") ? { role: "consumer_only" } : { role: "internal_non_producer" }
8645
9042
  })}
8646
9043
  `;
8647
9044
  }
9045
+ function unique3(values) {
9046
+ return Array.from(new Set(values));
9047
+ }
8648
9048
  function envExample(application) {
8649
9049
  return [
8650
9050
  "VITE_TOPOLO_AUTH_URL=https://auth.topolo.app",
@@ -8908,12 +9308,24 @@ function applicationScaffoldPayload(application, options = {}) {
8908
9308
  const surfaceModel = normalizeSurfaceModel(options.surfaceModel);
8909
9309
  const containerAppId = normalizeContainerAppId(options.containerAppId, surfaceModel);
8910
9310
  const profile = normalizeScaffoldProfile(options.profile);
8911
- const resources = resolveScaffoldResources(profile, options);
8912
- if (target === "platform" && api && !profile) {
8913
- resources.d1 = true;
8914
- }
8915
9311
  const ownerType = target === "platform" ? "topolo_first_party" : "third_party";
8916
9312
  const distributionModel = target === "platform" ? "marketplace" : "organization_internal";
9313
+ const capabilityComposition = resolveApplicationCapabilities({
9314
+ target,
9315
+ browser,
9316
+ api,
9317
+ mobile: surfaceModel === "mobile_container" || surfaceModel === "mobile_module",
9318
+ distributionModel,
9319
+ requested: options.capabilities,
9320
+ profile
9321
+ });
9322
+ const resources = {
9323
+ d1: capabilityComposition.resources.includes("d1"),
9324
+ r2: capabilityComposition.resources.includes("r2"),
9325
+ queue: capabilityComposition.resources.includes("queue"),
9326
+ workflow: capabilityComposition.resources.includes("workflow"),
9327
+ containerProcessing: profile === "spatial-assets"
9328
+ };
8917
9329
  const requestedAppDir = options.appDir ?? catalogApplication?.appDir ?? (target === "platform" ? `Topolo${toPascalCase(id)}` : `topolo-${slug}`);
8918
9330
  const appDir = target === "platform" ? normalizeFirstPartyAppDir(requestedAppDir) : requestedAppDir;
8919
9331
  const name = options.name ?? catalogApplication?.name ?? `topolo-${slug}`;
@@ -8933,7 +9345,7 @@ function applicationScaffoldPayload(application, options = {}) {
8933
9345
  const appId = options.appId ?? defaultAppId;
8934
9346
  const productionUrl = browser ? options.productionUrl ?? catalogApplication?.productionUrl ?? `https://${slug}.topolo.app` : null;
8935
9347
  const scopes = scaffoldScopes({ browser, api });
8936
- const capabilities = scaffoldCapabilities({ browser, api, distributionModel });
9348
+ const capabilities = capabilityComposition.selected;
8937
9349
  const ciWorkflowPath = target === "platform" ? ".github/workflows/topolo-conformance.yml" : null;
8938
9350
  const ciWorkflow = target === "platform" ? topoloConformanceWorkflow({ id, appDir, audience }) : null;
8939
9351
  const applicationPayload = {
@@ -8959,12 +9371,13 @@ function applicationScaffoldPayload(application, options = {}) {
8959
9371
  experience,
8960
9372
  distributionModel,
8961
9373
  capabilities,
9374
+ capabilityComposition,
8962
9375
  application: applicationPayload,
8963
9376
  scopes,
8964
9377
  requirements: APPLICATION_REQUIREMENTS.filter(
8965
9378
  (requirement) => requirement.appliesTo.some((scope) => scopes.includes(scope))
8966
9379
  ),
8967
- packages: scaffoldPackages({ browser, api, target }),
9380
+ packages: capabilityComposition.packages,
8968
9381
  files: scaffoldFiles({
8969
9382
  payload: {
8970
9383
  ownerType,
@@ -8977,7 +9390,8 @@ function applicationScaffoldPayload(application, options = {}) {
8977
9390
  resources,
8978
9391
  experience,
8979
9392
  distributionModel,
8980
- capabilities
9393
+ capabilities,
9394
+ capabilityComposition
8981
9395
  },
8982
9396
  application: applicationPayload,
8983
9397
  target,
@@ -9038,28 +9452,6 @@ function scaffoldScopes(options) {
9038
9452
  }
9039
9453
  return scopes;
9040
9454
  }
9041
- function scaffoldPackages(options) {
9042
- if (options.target === "developer") {
9043
- return options.browser ? ["@topolo/login"] : [];
9044
- }
9045
- const packages = /* @__PURE__ */ new Set();
9046
- if (options.browser) {
9047
- for (const packageName of PLATFORM_BROWSER_TOPOLO_PACKAGES) packages.add(packageName);
9048
- }
9049
- if (options.api) {
9050
- for (const packageName of PLATFORM_API_TOPOLO_PACKAGES) packages.add(packageName);
9051
- }
9052
- return Array.from(packages);
9053
- }
9054
- function scaffoldCapabilities(options) {
9055
- const capabilities = [];
9056
- if (options.browser) {
9057
- capabilities.push("login", "launcher");
9058
- }
9059
- if (options.distributionModel === "marketplace") capabilities.push("marketplace");
9060
- if (options.api) capabilities.push("api", "widget");
9061
- return capabilities;
9062
- }
9063
9455
  function scaffoldFiles(options) {
9064
9456
  const { application, target } = options;
9065
9457
  const slug = toSlug(application.id);
@@ -9099,9 +9491,31 @@ function scaffoldFiles(options) {
9099
9491
  content: packageJson(application, target, {
9100
9492
  browser: options.browser,
9101
9493
  api: options.api,
9102
- resources: options.payload.resources
9494
+ resources: options.payload.resources,
9495
+ packages: options.payload.capabilityComposition.packages
9103
9496
  })
9104
9497
  },
9498
+ {
9499
+ path: `${base}/topolo.capability-contract.json`,
9500
+ purpose: "Resolved application-factory contract for packages, permissions, resources, artifacts, and staging proof.",
9501
+ requiredSignals: [
9502
+ "registry version matches topolo.app.json capability composition",
9503
+ "every selected capability names its canonical owner and verification contract"
9504
+ ],
9505
+ content: `${json(capabilityContractProjection(options.payload.capabilityComposition))}
9506
+ `
9507
+ },
9508
+ ...options.payload.capabilities.includes("billing") ? [{
9509
+ path: `${base}/pricing-manifest.json`,
9510
+ purpose: "Canonical TopoloPay/Nexus pricing and meter registration manifest.",
9511
+ requiredSignals: ["contains a valid default plan", "remains the source for app-specific plan and meter design"],
9512
+ content: `${json(defaultPricingManifest({
9513
+ appId: application.appId ?? "",
9514
+ displayName: toTitle(application.name),
9515
+ description: options.payload.experience.tagline
9516
+ }))}
9517
+ `
9518
+ }] : [],
9105
9519
  {
9106
9520
  path: `${base}/README.md`,
9107
9521
  purpose: "Starter README with install, run, and verification commands.",
@@ -9433,7 +9847,8 @@ function scaffoldFiles(options) {
9433
9847
  ],
9434
9848
  content: serviceManifestJson(application, {
9435
9849
  audience: options.payload.audience,
9436
- tenancy: options.payload.tenancy
9850
+ tenancy: options.payload.tenancy,
9851
+ servicePermissions: options.payload.capabilityComposition.servicePermissions
9437
9852
  })
9438
9853
  }
9439
9854
  );
@@ -9454,7 +9869,8 @@ function scaffoldFiles(options) {
9454
9869
  surfaceModel: options.payload.surfaceModel,
9455
9870
  containerAppId: options.payload.containerAppId,
9456
9871
  distributionModel: options.payload.distributionModel,
9457
- capabilities: options.payload.capabilities
9872
+ capabilities: options.payload.capabilities,
9873
+ capabilityComposition: options.payload.capabilityComposition
9458
9874
  })
9459
9875
  },
9460
9876
  {
@@ -9547,6 +9963,7 @@ export default defineConfig({
9547
9963
  }
9548
9964
  }
9549
9965
  if (options.api) {
9966
+ const capabilityAdapter = target === "platform" ? managedCapabilityAdapter(options.payload.capabilities) : null;
9550
9967
  files.push(
9551
9968
  ...target === "platform" ? [] : [{
9552
9969
  path: `${apiBase}/wrangler.toml`,
@@ -9571,7 +9988,13 @@ export default defineConfig({
9571
9988
  profile: options.payload.profile,
9572
9989
  resources: options.payload.resources
9573
9990
  })
9574
- }
9991
+ },
9992
+ ...capabilityAdapter ? [{
9993
+ path: `${apiBase}/src/topolo-capabilities.ts`,
9994
+ purpose: "Managed adapters for selected Topolo notification, Nexus, AI, integration, and webhook capabilities.",
9995
+ requiredSignals: ["uses verified organization context", "uses environment-scoped gateways and keyless service identity", "verifies webhook signatures over the raw body"],
9996
+ content: capabilityAdapter
9997
+ }] : []
9575
9998
  );
9576
9999
  if (target === "platform") {
9577
10000
  files.push(
@@ -9775,7 +10198,14 @@ function buildProvisioningRequest(payload, supportEmail) {
9775
10198
  "http://localhost:5173/auth/callback",
9776
10199
  payload.application.productionUrl ? `${payload.application.productionUrl.replace(/\/$/, "")}/auth/callback` : null
9777
10200
  ].filter(Boolean),
9778
- requestedScopes: payload.application.appId ? ["openid", "profile", "email", "organization:read", "entitlements:read", `${payload.application.id}:read`] : ["openid", "profile", "email", "organization:read"],
10201
+ requestedScopes: Array.from(/* @__PURE__ */ new Set([
10202
+ "openid",
10203
+ "profile",
10204
+ "email",
10205
+ "organization:read",
10206
+ ...payload.capabilityComposition.authScopes,
10207
+ ...payload.application.appId ? [`${payload.application.id}:read`] : []
10208
+ ])),
9779
10209
  marketplace: {
9780
10210
  intent: payload.distributionModel === "marketplace" ? "submit_when_ready" : "not_requested",
9781
10211
  status: "preview",
@@ -9784,7 +10214,7 @@ function buildProvisioningRequest(payload, supportEmail) {
9784
10214
  description: payload.experience.tagline,
9785
10215
  docsUrl: payload.application.productionUrl || "https://developers.topolo.app"
9786
10216
  },
9787
- notifications: { role: "consumer_only" },
10217
+ notifications: payload.capabilities.includes("notification_producer") ? { role: "producer", contracts: "topolo.notification-contracts.json" } : payload.capabilities.includes("notifications") ? { role: "consumer_only" } : { role: "internal_non_producer" },
9788
10218
  applicationId: payload.application.id,
9789
10219
  name: payload.application.name,
9790
10220
  description: payload.experience.tagline,
@@ -9852,18 +10282,18 @@ async function applyProvisionedAppId(manifestPath, appId) {
9852
10282
  } catch {
9853
10283
  return { changed: false, previous: null };
9854
10284
  }
9855
- let json3;
10285
+ let json2;
9856
10286
  try {
9857
- json3 = JSON.parse(raw);
10287
+ json2 = JSON.parse(raw);
9858
10288
  } catch {
9859
10289
  return { changed: false, previous: null };
9860
10290
  }
9861
- const previous = typeof json3.appId === "string" ? json3.appId : null;
10291
+ const previous = typeof json2.appId === "string" ? json2.appId : null;
9862
10292
  if (previous === appId) {
9863
10293
  return { changed: false, previous };
9864
10294
  }
9865
- json3.appId = appId;
9866
- await writeFile3(manifestPath, `${JSON.stringify(json3, null, 2)}
10295
+ json2.appId = appId;
10296
+ await writeFile3(manifestPath, `${JSON.stringify(json2, null, 2)}
9867
10297
  `, "utf8");
9868
10298
  return { changed: true, previous };
9869
10299
  }
@@ -9872,13 +10302,283 @@ async function applyProvisionedAppId(manifestPath, appId) {
9872
10302
  import { join as join3 } from "path";
9873
10303
 
9874
10304
  // src/commands/app-reconcile.ts
9875
- import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
9876
- import path3 from "path";
10305
+ import { readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
10306
+ import path4 from "path";
9877
10307
  import { createInterface as createInterface2 } from "readline/promises";
9878
10308
  import { stdin as input2, stdout as output } from "process";
10309
+
10310
+ // src/commands/app-capability-composition.ts
10311
+ import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
10312
+ import path3 from "path";
10313
+ async function reconcileApplicationCapabilitySource(options) {
10314
+ const manifestPath = path3.resolve(options.manifestPath || "topolo.app.json");
10315
+ const root = path3.dirname(manifestPath);
10316
+ const packagePath = path3.join(root, "package.json");
10317
+ const contractPath = path3.join(root, "topolo.capability-contract.json");
10318
+ const manifest = parseRecord(await readFile3(manifestPath, "utf8"), manifestPath);
10319
+ const registration = record(manifest.registration);
10320
+ if (!registration) throw new Error("topolo.app.json must declare registration before capability composition.");
10321
+ if (registration.ownerType !== options.ownerType) {
10322
+ throw new Error(`${String(registration.ownerType)} capabilities cannot be composed through the ${options.ownerType} command surface.`);
10323
+ }
10324
+ const target = options.ownerType === "topolo_first_party" ? "platform" : "developer";
10325
+ const surfaces = resolveManifestSurfaces(manifest, registration);
10326
+ const existing = Array.isArray(registration.capabilities) ? registration.capabilities.filter((value) => typeof value === "string") : [];
10327
+ const capabilities = resolveApplicationCapabilities({
10328
+ target,
10329
+ browser: surfaces.browser,
10330
+ api: surfaces.api,
10331
+ mobile: surfaces.mobile,
10332
+ distributionModel: registration.distributionModel === "marketplace" ? "marketplace" : "organization_internal",
10333
+ requested: [...existing, ...options.additions ?? []]
10334
+ });
10335
+ const expectedComposition = capabilityCompositionProjection(capabilities);
10336
+ const expectedNotifications = capabilities.selected.includes("notification_producer") ? { role: "producer", contracts: "topolo.notification-contracts.json" } : capabilities.selected.includes("notifications") ? { role: "consumer_only" } : { role: "internal_non_producer" };
10337
+ const changes = [];
10338
+ compareChange(changes, "topolo.app.json#capabilityComposition", manifest.capabilityComposition, expectedComposition);
10339
+ compareChange(changes, "topolo.app.json#registration.capabilities", registration.capabilities, capabilities.selected);
10340
+ compareChange(changes, "topolo.app.json#notifications", manifest.notifications, expectedNotifications);
10341
+ const packageJson2 = parseRecord(await readFile3(packagePath, "utf8"), packagePath);
10342
+ const dependencies = record(packageJson2.dependencies) ?? {};
10343
+ const nextDependencies = { ...dependencies };
10344
+ for (const packageName of capabilities.packages) {
10345
+ if (typeof nextDependencies[packageName] !== "string") {
10346
+ nextDependencies[packageName] = packageVersion(packageName, target);
10347
+ }
10348
+ }
10349
+ const sortedDependencies = sortRecord(nextDependencies);
10350
+ compareChange(changes, "package.json#dependencies", dependencies, sortedDependencies);
10351
+ const expectedContract = capabilityContractProjection(capabilities);
10352
+ const existingContract = await readOptionalJson(contractPath);
10353
+ compareChange(changes, "topolo.capability-contract.json", existingContract, expectedContract);
10354
+ const adapter = target === "platform" ? managedCapabilityAdapter(capabilities.selected) : null;
10355
+ const apiDirectory = resolveApiDirectory(root, manifest, target);
10356
+ const adapterPath = path3.join(apiDirectory, "src", "topolo-capabilities.ts");
10357
+ if (adapter) compareChange(changes, relativePath(root, adapterPath), await readOptionalText(adapterPath), adapter);
10358
+ const pricingPath = path3.join(root, "pricing-manifest.json");
10359
+ const pricingManifest = capabilities.selected.includes("billing") ? defaultPricingManifest({
10360
+ appId: String(manifest.appId || manifest.app_id || "").trim(),
10361
+ displayName: String(registration.displayName || manifest.name || "Application"),
10362
+ description: String(registration.description || `${String(registration.displayName || manifest.name || "Application")} on Topolo.`)
10363
+ }) : null;
10364
+ if (pricingManifest) {
10365
+ compareChange(changes, "pricing-manifest.json", await readOptionalJson(pricingPath), pricingManifest);
10366
+ }
10367
+ const missingResources = capabilities.resources.filter((resource) => !manifestProvidesResource(manifest, resource));
10368
+ const verticalConfiguration = [];
10369
+ for (const [capability2, fileName, description] of VERTICAL_CAPABILITY_FILES) {
10370
+ if (capabilities.selected.includes(capability2) && !await fileExists(path3.join(root, fileName))) {
10371
+ verticalConfiguration.push(`${fileName}: ${description}`);
10372
+ }
10373
+ }
10374
+ if (options.mode === "apply" && changes.length > 0) {
10375
+ manifest.capabilityComposition = expectedComposition;
10376
+ registration.capabilities = capabilities.selected;
10377
+ manifest.registration = registration;
10378
+ manifest.notifications = expectedNotifications;
10379
+ packageJson2.dependencies = sortedDependencies;
10380
+ await writeJson(manifestPath, manifest);
10381
+ await writeJson(packagePath, packageJson2);
10382
+ await writeJson(contractPath, expectedContract);
10383
+ if (pricingManifest) await writeJson(pricingPath, pricingManifest);
10384
+ if (adapter) {
10385
+ await mkdir3(path3.dirname(adapterPath), { recursive: true });
10386
+ await writeFile4(adapterPath, adapter, "utf8");
10387
+ }
10388
+ }
10389
+ const blocked = missingResources.length > 0 || verticalConfiguration.length > 0;
10390
+ return {
10391
+ mode: options.mode,
10392
+ applied: options.mode === "apply" && changes.length > 0,
10393
+ status: blocked ? "blocked" : options.mode === "apply" || changes.length === 0 ? "in_sync" : "drift",
10394
+ manifestPath,
10395
+ packagePath,
10396
+ contractPath,
10397
+ capabilities,
10398
+ changes,
10399
+ missingResources,
10400
+ verticalConfiguration
10401
+ };
10402
+ }
10403
+ function registerApplicationCapabilityCommands(parent, ownerType) {
10404
+ parent.command("add-capability <capabilities...>").description("Compose approved shared capabilities into an existing application source tree.").option("--manifest <path>", "Path to topolo.app.json", "topolo.app.json").option("--check", "Report source changes without writing").option("--apply", "Write canonical manifest, package, and managed capability artifacts").option("--json", "Output JSON").option("--no-json", "Output human-readable details").action(async (capabilities, options) => {
10405
+ const outputMode = resolveOutputMode(options.json);
10406
+ try {
10407
+ if (options.check && options.apply) throw new Error("Choose exactly one of --check or --apply.");
10408
+ const result = await reconcileApplicationCapabilitySource({
10409
+ manifestPath: options.manifest,
10410
+ ownerType,
10411
+ mode: options.apply ? "apply" : "check",
10412
+ additions: capabilities
10413
+ });
10414
+ emit(result, outputMode, () => formatCapabilitySource(result));
10415
+ if (result.status !== "in_sync" && !options.apply) process.exitCode = 2;
10416
+ if (result.status === "blocked") process.exitCode = 2;
10417
+ } catch (error) {
10418
+ fail(error, outputMode);
10419
+ }
10420
+ });
10421
+ parent.command("doctor").description("Check declared capabilities against packages, resources, managed artifacts, and vertical insertion points.").option("--manifest <path>", "Path to topolo.app.json", "topolo.app.json").option("--json", "Output JSON").option("--no-json", "Output human-readable details").action(async (options) => {
10422
+ const outputMode = resolveOutputMode(options.json);
10423
+ try {
10424
+ const result = await reconcileApplicationCapabilitySource({ manifestPath: options.manifest, ownerType, mode: "check" });
10425
+ emit(result, outputMode, () => formatCapabilitySource(result));
10426
+ if (result.status !== "in_sync") process.exitCode = 2;
10427
+ } catch (error) {
10428
+ fail(error, outputMode);
10429
+ }
10430
+ });
10431
+ parent.command("upgrade").description("Upgrade an application to the current capability registry without changing its selected product capabilities.").option("--manifest <path>", "Path to topolo.app.json", "topolo.app.json").option("--check", "Report source changes without writing").option("--apply", "Apply the registry upgrade").option("--json", "Output JSON").option("--no-json", "Output human-readable details").action(async (options) => {
10432
+ const outputMode = resolveOutputMode(options.json);
10433
+ try {
10434
+ if (options.check && options.apply) throw new Error("Choose exactly one of --check or --apply.");
10435
+ const result = await reconcileApplicationCapabilitySource({
10436
+ manifestPath: options.manifest,
10437
+ ownerType,
10438
+ mode: options.apply ? "apply" : "check"
10439
+ });
10440
+ emit(result, outputMode, () => formatCapabilitySource(result));
10441
+ if (result.status !== "in_sync" && !options.apply) process.exitCode = 2;
10442
+ if (result.status === "blocked") process.exitCode = 2;
10443
+ } catch (error) {
10444
+ fail(error, outputMode);
10445
+ }
10446
+ });
10447
+ }
10448
+ function formatCapabilitySource(result) {
10449
+ const lines = [
10450
+ `Capability source: ${result.status}`,
10451
+ `Registry: ${result.capabilities.registryVersion}`,
10452
+ `Selected: ${result.capabilities.selected.join(", ")}`
10453
+ ];
10454
+ for (const change of result.changes) lines.push(`- ${change.path}`);
10455
+ if (result.missingResources.length > 0) lines.push(`Missing provisioned resources: ${result.missingResources.join(", ")}`);
10456
+ for (const item of result.verticalConfiguration) lines.push(`Vertical configuration required: ${item}`);
10457
+ return lines.join("\n");
10458
+ }
10459
+ function resolveManifestSurfaces(manifest, registration) {
10460
+ const targets = record(manifest.targets) ?? {};
10461
+ const targetRecords = Object.values(targets).map(record).filter((value) => Boolean(value));
10462
+ const runtime = record(manifest.runtime);
10463
+ return {
10464
+ browser: targetRecords.some((target) => Boolean(target.assets) || target.dir === "apps/web" || target.kind === "web") || runtime?.browser === true,
10465
+ api: targetRecords.some((target) => target.dir === "apps/api" || target.kind === "worker" && !target.assets) || runtime?.api === true,
10466
+ mobile: registration.surfaceModel === "mobile_container" || registration.surfaceModel === "mobile_module"
10467
+ };
10468
+ }
10469
+ function manifestProvidesResource(manifest, resource) {
10470
+ const targets = record(manifest.targets) ?? {};
10471
+ for (const value of Object.values(targets)) {
10472
+ const target = record(value);
10473
+ const bindings = record(target?.bindings);
10474
+ if (!bindings) continue;
10475
+ if (resource === "d1" && Array.isArray(bindings.d1) && bindings.d1.length > 0) {
10476
+ return bindings.d1.every((value2) => {
10477
+ const binding = record(value2);
10478
+ const configurations = ["development", "staging", "production"].map((environment) => record(binding?.[environment])).filter((configuration) => Boolean(configuration));
10479
+ return configurations.length > 0 && configurations.every((configuration) => typeof configuration.databaseId === "string" && configuration.databaseId.length > 0 && configuration.databaseId !== "provision-after-scaffold");
10480
+ });
10481
+ }
10482
+ if (resource === "r2" && Array.isArray(bindings.r2) && bindings.r2.length > 0) return true;
10483
+ if (resource === "queue" && record(bindings.queues)) return true;
10484
+ if (resource === "workflow" && Array.isArray(bindings.workflows) && bindings.workflows.length > 0) return true;
10485
+ }
10486
+ return false;
10487
+ }
10488
+ function packageVersion(packageName, target) {
10489
+ if (target === "platform") {
10490
+ if (packageName === "@topolo-io/notifications") return "^0.6.8";
10491
+ return "latest";
10492
+ }
10493
+ if (packageName === "@topolo/login") return "^0.1.0";
10494
+ if (packageName === "@topolo/sdk") return "^0.8.1";
10495
+ throw new Error(`Capability registry exposed private package ${packageName} to a third-party application.`);
10496
+ }
10497
+ function compareChange(changes, changePath, before, after) {
10498
+ if (stableJson(before) !== stableJson(after)) changes.push({ path: changePath, before, after });
10499
+ }
9879
10500
  function record(value) {
9880
10501
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
9881
10502
  }
10503
+ function parseRecord(raw, filePath) {
10504
+ const value = JSON.parse(raw);
10505
+ const parsed = record(value);
10506
+ if (!parsed) throw new Error(`${filePath} must contain a JSON object.`);
10507
+ return parsed;
10508
+ }
10509
+ async function readOptionalJson(filePath) {
10510
+ try {
10511
+ return JSON.parse(await readFile3(filePath, "utf8"));
10512
+ } catch (error) {
10513
+ if (error.code === "ENOENT") return null;
10514
+ throw error;
10515
+ }
10516
+ }
10517
+ async function readOptionalText(filePath) {
10518
+ try {
10519
+ return await readFile3(filePath, "utf8");
10520
+ } catch (error) {
10521
+ if (error.code === "ENOENT") return null;
10522
+ throw error;
10523
+ }
10524
+ }
10525
+ async function fileExists(filePath) {
10526
+ try {
10527
+ await readFile3(filePath);
10528
+ return true;
10529
+ } catch (error) {
10530
+ if (error.code === "ENOENT") return false;
10531
+ throw error;
10532
+ }
10533
+ }
10534
+ async function writeJson(filePath, value) {
10535
+ await writeFile4(filePath, `${JSON.stringify(value, null, 2)}
10536
+ `, "utf8");
10537
+ }
10538
+ function sortRecord(value) {
10539
+ return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left.localeCompare(right)));
10540
+ }
10541
+ function stableJson(value) {
10542
+ if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
10543
+ if (value && typeof value === "object") {
10544
+ return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableJson(item)}`).join(",")}}`;
10545
+ }
10546
+ return JSON.stringify(value) ?? "undefined";
10547
+ }
10548
+ var VERTICAL_CAPABILITY_FILES = [
10549
+ ["notification_producer", "topolo.notification-contracts.json", "define app-owned events and recipient policies"],
10550
+ ["metering", "topolo.metering.json", "define app-owned meters and attribution semantics"],
10551
+ ["nexus_ai", "topolo.ai.json", "define AI use cases, budget behavior, and model policy"],
10552
+ ["integrations", "topolo.integrations.json", "define the external providers and connection purposes this app uses"],
10553
+ ["webhooks", "topolo.webhooks.json", "define signed event routes, replay policy, and delivery ownership"],
10554
+ ["automation", "topolo.automation.json", "define app-owned triggers and TopoloFlow actions"],
10555
+ ["data", "topolo.data.json", "define the workspace data model and migration ownership"],
10556
+ ["object_storage", "topolo.object-storage.json", "define object lifecycle, access, and deletion policy"],
10557
+ ["background_jobs", "topolo.jobs.json", "define accepted job types, retry policy, and idempotency"],
10558
+ ["durable_workflows", "topolo.workflows.json", "define workflow inputs, steps, and recovery semantics"],
10559
+ ["privacy", "topolo.privacy.json", "define app-owned retention, export, and deletion adapters"],
10560
+ ["chat", "topolo.chat.json", "define where the shared chat surface appears in this product"],
10561
+ ["calendar", "topolo.calendar.json", "define the app-owned scheduling surface and resource mapping"],
10562
+ ["mailbox", "topolo.mailbox.json", "define the app-owned mailbox surface and message context"]
10563
+ ];
10564
+ function resolveApiDirectory(root, manifest, target) {
10565
+ if (target === "developer") return root;
10566
+ for (const value of Object.values(record(manifest.targets) ?? {})) {
10567
+ const targetRecord = record(value);
10568
+ if (targetRecord?.kind === "worker" && !targetRecord.assets && typeof targetRecord.dir === "string") {
10569
+ return path3.resolve(root, targetRecord.dir);
10570
+ }
10571
+ }
10572
+ return path3.join(root, "apps", "api");
10573
+ }
10574
+ function relativePath(root, filePath) {
10575
+ return path3.relative(root, filePath).split(path3.sep).join("/");
10576
+ }
10577
+
10578
+ // src/commands/app-reconcile.ts
10579
+ function record2(value) {
10580
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
10581
+ }
9882
10582
  function environmentValue(value, environment) {
9883
10583
  if (typeof value === "string") return value;
9884
10584
  if (!value) return null;
@@ -9902,8 +10602,14 @@ function resolveManifestApplicationUrl(manifest, environment) {
9902
10602
  }
9903
10603
  return `https://${target.subdomain}.${domain}`;
9904
10604
  }
10605
+ function resolveManifestHealthcheckPath(manifest) {
10606
+ const targets = manifest.targets || {};
10607
+ const targetName = manifest.primaryTarget || (targets.web ? "web" : Object.keys(targets)[0]);
10608
+ const healthcheckPath = targetName ? targets[targetName]?.healthcheckPath?.trim() : "";
10609
+ return healthcheckPath && healthcheckPath.startsWith("/") && !healthcheckPath.startsWith("//") ? healthcheckPath : "/health";
10610
+ }
9905
10611
  function assertRegistration(value) {
9906
- const registration = record(value);
10612
+ const registration = record2(value);
9907
10613
  const required = [
9908
10614
  "ownerType",
9909
10615
  "applicationId",
@@ -9932,9 +10638,9 @@ function titleFromSlug(value) {
9932
10638
  return value.replace(/^topolo[-_]/, "").split(/[-_]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
9933
10639
  }
9934
10640
  async function completeApplicationRegistrationInteractively(options) {
9935
- const manifestPath = path3.resolve(options.manifestPath || "topolo.app.json");
9936
- const manifest = JSON.parse(await readFile3(manifestPath, "utf8"));
9937
- const existing = record(manifest.registration) || {};
10641
+ const manifestPath = path4.resolve(options.manifestPath || "topolo.app.json");
10642
+ const manifest = JSON.parse(await readFile4(manifestPath, "utf8"));
10643
+ const existing = record2(manifest.registration) || {};
9938
10644
  const appId = String(manifest.appId || manifest.app_id || "").trim();
9939
10645
  const slug = String(existing.serviceSlug || manifest.name || "").trim();
9940
10646
  const defaultName = titleFromSlug(slug);
@@ -9968,19 +10674,19 @@ async function completeApplicationRegistrationInteractively(options) {
9968
10674
  containerAppId: existing.containerAppId,
9969
10675
  distributionModel: existing.distributionModel || (options.expectedOwnerType === "topolo_first_party" ? "marketplace" : "organization_internal"),
9970
10676
  version: String(existing.version || "1.0.0"),
9971
- pricing: record(existing.pricing) || { type: "free", monthlyPrice: null },
10677
+ pricing: record2(existing.pricing) || { type: "free", monthlyPrice: null },
9972
10678
  supportEmail,
9973
10679
  productionUrl,
9974
10680
  demoUrl: typeof existing.demoUrl === "string" ? existing.demoUrl : void 0,
9975
10681
  repoUrl: typeof existing.repoUrl === "string" ? existing.repoUrl : void 0,
9976
10682
  tags: Array.isArray(existing.tags) ? existing.tags : [],
9977
10683
  screenshots: Array.isArray(existing.screenshots) ? existing.screenshots : [],
9978
- auth: record(existing.auth) || {
10684
+ auth: record2(existing.auth) || {
9979
10685
  callbackPath: "/auth/callback",
9980
10686
  requestedScopes: ["openid", "profile", "email", "organization:read"]
9981
10687
  },
9982
10688
  capabilities: Array.isArray(existing.capabilities) ? existing.capabilities : ["login", "launcher"],
9983
- marketplace: record(existing.marketplace) || {
10689
+ marketplace: record2(existing.marketplace) || {
9984
10690
  intent: options.expectedOwnerType === "topolo_first_party" ? "submit_when_ready" : "not_requested",
9985
10691
  status: "preview",
9986
10692
  shortBlurb: description,
@@ -10004,7 +10710,7 @@ async function completeApplicationRegistrationInteractively(options) {
10004
10710
  changed = true;
10005
10711
  }
10006
10712
  if (!appId) throw new Error("topolo.app.json must declare the immutable appId before reconciliation.");
10007
- if (changed) await writeFile4(manifestPath, `${JSON.stringify(manifest, null, 2)}
10713
+ if (changed) await writeFile5(manifestPath, `${JSON.stringify(manifest, null, 2)}
10008
10714
  `, "utf8");
10009
10715
  return { changed, manifestPath };
10010
10716
  } finally {
@@ -10012,8 +10718,8 @@ async function completeApplicationRegistrationInteractively(options) {
10012
10718
  }
10013
10719
  }
10014
10720
  async function loadApplicationReconcileRequest(options) {
10015
- const manifestPath = path3.resolve(options.manifestPath || "topolo.app.json");
10016
- const raw = await readFile3(manifestPath, "utf8");
10721
+ const manifestPath = path4.resolve(options.manifestPath || "topolo.app.json");
10722
+ const raw = await readFile4(manifestPath, "utf8");
10017
10723
  const manifest = JSON.parse(raw);
10018
10724
  assertRegistration(manifest.registration);
10019
10725
  const registration = manifest.registration;
@@ -10048,6 +10754,7 @@ async function loadApplicationReconcileRequest(options) {
10048
10754
  pricingType: registration.pricing.type,
10049
10755
  monthlyPrice: registration.pricing.monthlyPrice ?? null,
10050
10756
  productionUrl,
10757
+ healthcheckPath: resolveManifestHealthcheckPath(manifest),
10051
10758
  demoUrl: registration.ownerType === "topolo_first_party" ? productionUrl : registration.demoUrl ?? productionUrl,
10052
10759
  repoUrl: registration.repoUrl ?? null,
10053
10760
  supportEmail: registration.supportEmail,
@@ -10157,6 +10864,7 @@ function registerApplicationReconcileCommand(parent, ownerType, dependencies = {
10157
10864
  const loadRequest = dependencies.loadRequest || loadApplicationReconcileRequest;
10158
10865
  const reconcileRequest = dependencies.reconcileRequest || reconcileApplicationRequest;
10159
10866
  const confirm = dependencies.confirm || confirmApplicationReconcile;
10867
+ const reconcileSource = dependencies.reconcileSource || reconcileApplicationCapabilitySource;
10160
10868
  parent.command("reconcile").description("Check or apply the declarative application registration in topolo.app.json.").option("--manifest <path>", "Path to topolo.app.json", "topolo.app.json").option("--check", "Report drift without mutating remote data").option("--dry-run", "Alias for --check").option("--apply", "Apply mutable drift and required projections").option("--interactive", "Use the human confirmation flow").option("--non-interactive", "Disable all prompts").option("--yes", "Accept the interactive apply confirmation").option("--json", "Output JSON").option("--no-json", "Output human-readable details").action(async (options) => {
10161
10869
  const outputMode = resolveOutputMode(options.json);
10162
10870
  try {
@@ -10167,6 +10875,20 @@ function registerApplicationReconcileCommand(parent, ownerType, dependencies = {
10167
10875
  expectedOwnerType: ownerType
10168
10876
  });
10169
10877
  }
10878
+ const source = await reconcileSource({
10879
+ manifestPath: options.manifest,
10880
+ ownerType,
10881
+ mode: selection.mode
10882
+ });
10883
+ if (selection.mode === "apply" && source.status === "blocked") {
10884
+ emit(
10885
+ { manifest: source.manifestPath, source, reconciliation: null },
10886
+ outputMode,
10887
+ () => formatCapabilitySource(source)
10888
+ );
10889
+ process.exitCode = 2;
10890
+ return;
10891
+ }
10170
10892
  const loaded = await loadRequest({
10171
10893
  manifestPath: options.manifest,
10172
10894
  expectedOwnerType: ownerType,
@@ -10184,11 +10906,11 @@ function registerApplicationReconcileCommand(parent, ownerType, dependencies = {
10184
10906
  }
10185
10907
  }
10186
10908
  emit(
10187
- { manifest: loaded.manifestPath, reconciliation: result },
10909
+ { manifest: loaded.manifestPath, source, reconciliation: result },
10188
10910
  outputMode,
10189
10911
  () => formatApplicationReconcile(result, loaded.manifestPath)
10190
10912
  );
10191
- if (result.mode === "check" && result.plan.status !== "in_sync") process.exitCode = 2;
10913
+ if (source.status === "blocked" || result.mode === "check" && (result.plan.status !== "in_sync" || source.status !== "in_sync")) process.exitCode = 2;
10192
10914
  } catch (error) {
10193
10915
  fail(error, outputMode);
10194
10916
  }
@@ -10226,6 +10948,7 @@ function registerAppsCommand(program2) {
10226
10948
  }
10227
10949
  });
10228
10950
  registerApplicationReconcileCommand(apps, "third_party");
10951
+ registerApplicationCapabilityCommands(apps, "third_party");
10229
10952
  apps.command("get <application>").description("Show one Topolo application available to the current credential.").option("--json", "Output JSON", true).option("--no-json", "Output human-readable details").action(async (application, opts) => {
10230
10953
  const mode = resolveOutputMode(opts.json);
10231
10954
  try {
@@ -10295,7 +11018,7 @@ function registerAppsCommand(program2) {
10295
11018
  ).option("--json", "Output JSON").option("--no-json", "Output human-readable checklist").option("--app-dir <path>", "Application directory under the scaffold root").option("--name <name>", "Application display/package source name").option("--package-name <name>", "package.json name").option("--production-url <url>", "Production browser URL").option("--app-id <id>", "Stable SDK/Auth app id for API-backed apps").option("--audience <audience>", "Developer app audience: business or personal").option("--tenancy <tenancy>", "Developer app tenancy: organization or household").option(
10296
11019
  "--surface-model <surfaceModel>",
10297
11020
  "Developer app surface model: web_app, mobile_container, mobile_module, or messaging_surface"
10298
- ).option("--container-app-id <appId>", "Container app id when --surface-model mobile_module is used").option("--write", "Write the generated scaffold files to disk").option("--repo-root <path>", "Directory where scaffold files should be written").option("--force", "Overwrite existing scaffold files when --write is set").option("--with-api", "Include API/service files for developer scaffolds").option("--no-api", "Do not include API/service requirements").option("--no-browser", "Do not include browser-shell requirements").option(
11021
+ ).option("--container-app-id <appId>", "Container app id when --surface-model mobile_module is used").option("--capability <id...>", "Compose one or more approved Topolo capabilities").option("--write", "Write the generated scaffold files to disk").option("--repo-root <path>", "Directory where scaffold files should be written").option("--force", "Overwrite existing scaffold files when --write is set").option("--with-api", "Include API/service files for developer scaffolds").option("--no-api", "Do not include API/service requirements").option("--no-browser", "Do not include browser-shell requirements").option(
10299
11022
  "--provision",
10300
11023
  "Register the app in the current Topolo developer workspace using the authenticated session"
10301
11024
  ).action(async (application, opts) => {
@@ -10314,7 +11037,8 @@ function registerAppsCommand(program2) {
10314
11037
  audience: resolveScaffoldAudience(opts.audience),
10315
11038
  tenancy: resolveScaffoldTenancy(opts.tenancy),
10316
11039
  surfaceModel: resolveScaffoldSurfaceModel(opts.surfaceModel),
10317
- containerAppId: opts.containerAppId
11040
+ containerAppId: opts.containerAppId,
11041
+ capabilities: opts.capability
10318
11042
  });
10319
11043
  const writeResult = opts.write ? await writeApplicationScaffold(payload, { repoRoot: opts.repoRoot, force: opts.force }) : null;
10320
11044
  const provisionResult = opts.provision ? await provisionApplicationScaffold(payload) : null;
@@ -10398,7 +11122,7 @@ function applicationAuditPayload(application, options = {}) {
10398
11122
  }
10399
11123
  function recordApplicationEvidence(application, options = {}) {
10400
11124
  const appId = resolveApplication(application).id;
10401
- const root = path4.resolve(options.appRoot ?? process.cwd());
11125
+ const root = path5.resolve(options.appRoot ?? process.cwd());
10402
11126
  if (!existsSync4(root)) {
10403
11127
  throw new Error(`Application root does not exist: ${root}`);
10404
11128
  }
@@ -10410,8 +11134,8 @@ function recordApplicationEvidence(application, options = {}) {
10410
11134
  commit = void 0;
10411
11135
  }
10412
11136
  }
10413
- const evidenceDir = path4.join(root, ".topolo");
10414
- const evidencePath = path4.join(evidenceDir, "evidence.json");
11137
+ const evidenceDir = path5.join(root, ".topolo");
11138
+ const evidencePath = path5.join(evidenceDir, "evidence.json");
10415
11139
  let before = null;
10416
11140
  if (existsSync4(evidencePath)) {
10417
11141
  try {
@@ -10711,17 +11435,181 @@ function statusesAtOrWorse(threshold) {
10711
11435
 
10712
11436
  // src/commands/platform.ts
10713
11437
  import { readdirSync as readdirSync2, readFileSync as readFileSync6, statSync as statSync2 } from "fs";
10714
- import path5 from "path";
11438
+ import path7 from "path";
11439
+
11440
+ // src/commands/app-capability-provision.ts
11441
+ import { readFile as readFile5, writeFile as writeFile6 } from "fs/promises";
11442
+ import path6 from "path";
11443
+ async function provisionApplicationResources(options) {
11444
+ const manifestPath = path6.resolve(options.manifestPath || "topolo.app.json");
11445
+ const manifest = parseRecord2(await readFile5(manifestPath, "utf8"), manifestPath);
11446
+ const environments = requiredRecord(manifest.environments, "topolo.app.json#environments");
11447
+ const environment = requiredRecord(environments[options.environment], `topolo.app.json#environments.${options.environment}`);
11448
+ const accountId = requiredString(environment.accountId, `topolo.app.json#environments.${options.environment}.accountId`);
11449
+ const token = options.token ?? process.env.CLOUDFLARE_API_TOKEN;
11450
+ if (!token) throw new Error("CLOUDFLARE_API_TOKEN is required to inspect or provision Cloudflare application resources.");
11451
+ const fetchImpl = options.fetchImpl ?? fetch;
11452
+ const operations = [];
11453
+ let changed = false;
11454
+ for (const target of Object.values(record3(manifest.targets) ?? {})) {
11455
+ const bindings = record3(record3(target)?.bindings);
11456
+ if (!bindings) continue;
11457
+ for (const bindingValue of array(bindings.d1)) {
11458
+ const binding = record3(bindingValue);
11459
+ const config = record3(binding?.[options.environment]);
11460
+ if (!config) continue;
11461
+ const name = requiredString(config.databaseName, `D1 ${options.environment} databaseName`);
11462
+ const databases = await cloudflareList(fetchImpl, token, `/accounts/${accountId}/d1/database?name=${encodeURIComponent(name)}&per_page=10000`);
11463
+ const existing = databases.find((item) => item.name === name);
11464
+ if (existing) {
11465
+ const id = requiredString(existing.uuid ?? existing.id, `D1 database ${name} id`);
11466
+ const mismatched = config.databaseId !== id;
11467
+ operations.push({ kind: "d1", name, status: mismatched && options.mode === "check" ? "mismatch" : "exists", id });
11468
+ if (mismatched && options.mode === "apply") {
11469
+ config.databaseId = id;
11470
+ changed = true;
11471
+ }
11472
+ } else if (options.mode === "apply") {
11473
+ const created = await cloudflareCreate(fetchImpl, token, `/accounts/${accountId}/d1/database`, { name });
11474
+ const id = requiredString(created.uuid ?? created.id, `created D1 database ${name} id`);
11475
+ config.databaseId = id;
11476
+ changed = true;
11477
+ operations.push({ kind: "d1", name, status: "created", id });
11478
+ } else {
11479
+ operations.push({ kind: "d1", name, status: "missing" });
11480
+ }
11481
+ }
11482
+ for (const bindingValue of array(bindings.r2)) {
11483
+ const config = record3(record3(bindingValue)?.[options.environment]);
11484
+ if (!config) continue;
11485
+ const name = requiredString(config.bucketName, `R2 ${options.environment} bucketName`);
11486
+ const buckets = await cloudflareList(fetchImpl, token, `/accounts/${accountId}/r2/buckets?name_contains=${encodeURIComponent(name)}&per_page=1000`);
11487
+ const exists = buckets.some((item) => item.name === name);
11488
+ if (exists) operations.push({ kind: "r2", name, status: "exists" });
11489
+ else if (options.mode === "apply") {
11490
+ await cloudflareCreate(fetchImpl, token, `/accounts/${accountId}/r2/buckets`, { name });
11491
+ operations.push({ kind: "r2", name, status: "created" });
11492
+ } else operations.push({ kind: "r2", name, status: "missing" });
11493
+ }
11494
+ const queues = record3(bindings.queues);
11495
+ const queueNames = /* @__PURE__ */ new Set();
11496
+ for (const bindingValue of [...array(queues?.producers), ...array(queues?.consumers)]) {
11497
+ const config = record3(record3(bindingValue)?.[options.environment]);
11498
+ if (config && typeof config.queue === "string") queueNames.add(config.queue);
11499
+ }
11500
+ if (queueNames.size > 0) {
11501
+ const existingQueues = await cloudflareList(fetchImpl, token, `/accounts/${accountId}/queues?page=1&per_page=1000`);
11502
+ for (const name of queueNames) {
11503
+ const exists = existingQueues.some((item) => item.queue_name === name || item.name === name);
11504
+ if (exists) operations.push({ kind: "queue", name, status: "exists" });
11505
+ else if (options.mode === "apply") {
11506
+ await cloudflareCreate(fetchImpl, token, `/accounts/${accountId}/queues`, { queue_name: name });
11507
+ operations.push({ kind: "queue", name, status: "created" });
11508
+ } else operations.push({ kind: "queue", name, status: "missing" });
11509
+ }
11510
+ }
11511
+ }
11512
+ if (changed) await writeFile6(manifestPath, `${JSON.stringify(manifest, null, 2)}
11513
+ `, "utf8");
11514
+ const drift = operations.some((operation) => operation.status === "missing" || operation.status === "mismatch");
11515
+ return {
11516
+ mode: options.mode,
11517
+ environment: options.environment,
11518
+ manifestPath,
11519
+ accountId,
11520
+ changed,
11521
+ status: drift ? "drift" : "in_sync",
11522
+ operations
11523
+ };
11524
+ }
11525
+ function registerApplicationResourceProvisionCommand(parent) {
11526
+ parent.command("provision-resources").description("Inspect or create declared Cloudflare resources for one explicit application environment.").requiredOption("--environment <environment>", "development, staging, or production").option("--manifest <path>", "Path to topolo.app.json", "topolo.app.json").option("--check", "Inspect without creating resources").option("--apply", "Create missing resources and write provider identifiers").option("--confirm-production", "Confirm production resource creation").option("--json", "Output JSON").option("--no-json", "Output human-readable details").action(async (options) => {
11527
+ const outputMode = resolveOutputMode(options.json);
11528
+ try {
11529
+ if (!isEnvironment(options.environment)) throw new Error("Environment must be development, staging, or production.");
11530
+ if (options.check && options.apply) throw new Error("Choose exactly one of --check or --apply.");
11531
+ if (options.environment === "production" && options.apply && !options.confirmProduction) {
11532
+ throw new Error("Production resource creation requires --confirm-production.");
11533
+ }
11534
+ const result = await provisionApplicationResources({
11535
+ manifestPath: options.manifest,
11536
+ environment: options.environment,
11537
+ mode: options.apply ? "apply" : "check"
11538
+ });
11539
+ emit(result, outputMode, () => formatResourceProvision(result));
11540
+ if (result.status !== "in_sync") process.exitCode = 2;
11541
+ } catch (error) {
11542
+ fail(error, outputMode);
11543
+ }
11544
+ });
11545
+ }
11546
+ function formatResourceProvision(result) {
11547
+ return [
11548
+ `Application resources: ${result.status}`,
11549
+ `Environment: ${result.environment}`,
11550
+ ...result.operations.map((operation) => `- ${operation.kind} ${operation.name}: ${operation.status}`)
11551
+ ].join("\n");
11552
+ }
11553
+ async function cloudflareList(fetchImpl, token, resourcePath) {
11554
+ const result = await cloudflareRequest(fetchImpl, token, resourcePath, "GET");
11555
+ if (Array.isArray(result)) return result.map(record3).filter((value) => Boolean(value));
11556
+ const resultRecord = record3(result);
11557
+ const values = array(resultRecord?.buckets ?? resultRecord?.queues ?? resultRecord?.databases);
11558
+ return values.map(record3).filter((value) => Boolean(value));
11559
+ }
11560
+ async function cloudflareCreate(fetchImpl, token, resourcePath, body) {
11561
+ const result = await cloudflareRequest(fetchImpl, token, resourcePath, "POST", body);
11562
+ return requiredRecord(result, `Cloudflare create response for ${resourcePath}`);
11563
+ }
11564
+ async function cloudflareRequest(fetchImpl, token, resourcePath, method, body) {
11565
+ const response = await fetchImpl(`https://api.cloudflare.com/client/v4${resourcePath}`, {
11566
+ method,
11567
+ headers: { Authorization: `Bearer ${token}`, ...body ? { "Content-Type": "application/json" } : {} },
11568
+ ...body ? { body: JSON.stringify(body) } : {}
11569
+ });
11570
+ const payload = await response.json().catch(() => null);
11571
+ const envelope = record3(payload);
11572
+ if (!response.ok || envelope?.success === false) {
11573
+ throw new Error(`Cloudflare ${method} ${resourcePath} failed with HTTP ${response.status}.`);
11574
+ }
11575
+ return envelope && "result" in envelope ? envelope.result : payload;
11576
+ }
11577
+ function isEnvironment(value) {
11578
+ return value === "development" || value === "staging" || value === "production";
11579
+ }
11580
+ function parseRecord2(raw, filePath) {
11581
+ const value = JSON.parse(raw);
11582
+ return requiredRecord(value, filePath);
11583
+ }
11584
+ function requiredRecord(value, label) {
11585
+ const valueRecord = record3(value);
11586
+ if (!valueRecord) throw new Error(`${label} must be an object.`);
11587
+ return valueRecord;
11588
+ }
11589
+ function requiredString(value, label) {
11590
+ if (typeof value !== "string" || !value.trim()) throw new Error(`${label} must be a non-empty string.`);
11591
+ return value;
11592
+ }
11593
+ function record3(value) {
11594
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
11595
+ }
11596
+ function array(value) {
11597
+ return Array.isArray(value) ? value : [];
11598
+ }
11599
+
11600
+ // src/commands/platform.ts
10715
11601
  function registerPlatformCommand(program2) {
10716
11602
  const platform3 = program2.command("platform").description("Internal Topolo first-party platform and personal app commands");
10717
11603
  const platformApps = platform3.command("apps").description("Scaffold and operate first-party Topolo platform and personal applications");
10718
11604
  registerApplicationReconcileCommand(platformApps, "topolo_first_party");
11605
+ registerApplicationCapabilityCommands(platformApps, "topolo_first_party");
11606
+ registerApplicationResourceProvisionCommand(platformApps);
10719
11607
  platformApps.command("scaffold <application>").description(
10720
11608
  "Generate a first-party Topolo scaffold for the platform or personal portfolio."
10721
11609
  ).option("--json", "Output JSON").option("--no-json", "Output human-readable checklist").option("--app-dir <path>", "Application repo name or apps/<repo> path").option("--name <name>", "Application display/package source name").option("--package-name <name>", "package.json name").option("--production-url <url>", "Production browser URL").option("--service <id>", "Stable SDK/Auth app id for API-backed apps").option("--portfolio <portfolio>", "First-party portfolio: platform or personal").option(
10722
11610
  "--surface-model <surfaceModel>",
10723
11611
  "First-party surface model: web_app, mobile_container, mobile_module, or messaging_surface"
10724
- ).option("--container-app-id <appId>", "Container app id when --surface-model mobile_module is used").option("--profile <profile>", "First-party scaffold profile such as spatial-assets").option("--with-d1", "Declare a D1 binding in the first-party scaffold").option("--with-r2", "Declare an R2 binding in the first-party scaffold").option("--with-queue", "Declare a Queue binding in the first-party scaffold").option("--with-workflow", "Declare a Workflow binding in the first-party scaffold").option("--with-container-processing", "Declare container-processing metadata in CloudControl").option("--home-path <path>", "Authenticated home route (default: /dashboard)").option("--home-label <label>", "Home navigation label (default: Dashboard)").option("--navigation-mode <mode>", "Product navigation: sidebar, topbar, or none").option("--workspace-switcher <mode>", "Workspace context selector: required, optional, or off").option("--ui-template <template>", "Shared UI starter: operations, index, workbench, or minimal").option("--tagline <text>", "Product-facing landing and home description").option("--write", "Write the generated scaffold files to disk").option("--repo-root <path>", "Directory where scaffold files should be written").option("--force", "Overwrite existing scaffold files when --write is set").option("--with-api", "Include the API/service baseline").option("--no-api", "Do not include API/service requirements").option("--no-browser", "Do not include browser-shell requirements").option(
11612
+ ).option("--container-app-id <appId>", "Container app id when --surface-model mobile_module is used").option("--profile <profile>", "First-party scaffold profile such as spatial-assets").option("--capability <id...>", "Compose one or more versioned platform capabilities").option("--home-path <path>", "Authenticated home route (default: /dashboard)").option("--home-label <label>", "Home navigation label (default: Dashboard)").option("--navigation-mode <mode>", "Product navigation: sidebar, topbar, or none").option("--workspace-switcher <mode>", "Workspace context selector: required, optional, or off").option("--ui-template <template>", "Shared UI starter: operations, index, workbench, or minimal").option("--tagline <text>", "Product-facing landing and home description").option("--write", "Write the generated scaffold files to disk").option("--repo-root <path>", "Directory where scaffold files should be written").option("--force", "Overwrite existing scaffold files when --write is set").option("--with-api", "Include the API/service baseline").option("--no-api", "Do not include API/service requirements").option("--no-browser", "Do not include browser-shell requirements").option(
10725
11613
  "--provision",
10726
11614
  "Provision the generated first-party app into Topolo Developers and Auth using the authenticated Topolo admin session"
10727
11615
  ).option(
@@ -10752,11 +11640,7 @@ function registerPlatformCommand(program2) {
10752
11640
  surfaceModel: resolvePlatformSurfaceModel(opts.surfaceModel),
10753
11641
  containerAppId: opts.containerAppId,
10754
11642
  profile: opts.profile,
10755
- withD1: opts.withD1,
10756
- withR2: opts.withR2,
10757
- withQueue: opts.withQueue,
10758
- withWorkflow: opts.withWorkflow,
10759
- withContainerProcessing: opts.withContainerProcessing,
11643
+ capabilities: opts.capability,
10760
11644
  homePath: opts.homePath,
10761
11645
  homeLabel: opts.homeLabel,
10762
11646
  navigationMode: opts.navigationMode,
@@ -10829,12 +11713,12 @@ var SKIPPED_SCAN_DIRS = /* @__PURE__ */ new Set([
10829
11713
  "output"
10830
11714
  ]);
10831
11715
  function scanStagingProductionUrls(rootInput, allowPatterns) {
10832
- const root = path5.resolve(rootInput);
11716
+ const root = path7.resolve(rootInput);
10833
11717
  const allowlist = allowPatterns.map((pattern) => new RegExp(pattern));
10834
11718
  const violations = [];
10835
11719
  function shouldSkipFile(filePath) {
10836
- const basename = path5.basename(filePath);
10837
- const relativePath = path5.relative(root, filePath);
11720
+ const basename = path7.basename(filePath);
11721
+ const relativePath2 = path7.relative(root, filePath);
10838
11722
  if (basename === "package-lock.json" || basename === "pnpm-lock.yaml" || basename === "yarn.lock") return true;
10839
11723
  if (basename === "topolo.cloudcontrol.json") return true;
10840
11724
  if (basename === "wrangler.toml" || basename === "wrangler.jsonc") return true;
@@ -10843,29 +11727,29 @@ function scanStagingProductionUrls(rootInput, allowPatterns) {
10843
11727
  if (basename.endsWith(".generated.ts")) return true;
10844
11728
  if (basename.endsWith(".test.ts") || basename.endsWith(".test.tsx") || basename.endsWith(".spec.ts")) return true;
10845
11729
  if (basename === ".env.production") return true;
10846
- if (relativePath === path5.join("src", "environment.ts") || relativePath === path5.join("src", "environmentUrls.ts") || relativePath.endsWith(path5.join("src", "environment.ts")) || relativePath.endsWith(path5.join("src", "lib", "environment.ts")) || relativePath === path5.join("packages", "topolo-ui-kit", "src", "environmentUrls.ts")) return true;
10847
- if (relativePath.includes(`${path5.sep}test${path5.sep}`) || relativePath.includes(`${path5.sep}tests${path5.sep}`) || relativePath.includes(`${path5.sep}e2e${path5.sep}`)) return true;
10848
- if (relativePath.startsWith(`scripts${path5.sep}`) || relativePath.includes(`${path5.sep}scripts${path5.sep}`)) return true;
10849
- return !RUNTIME_EXTENSIONS.has(path5.extname(filePath));
11730
+ if (relativePath2 === path7.join("src", "environment.ts") || relativePath2 === path7.join("src", "environmentUrls.ts") || relativePath2.endsWith(path7.join("src", "environment.ts")) || relativePath2.endsWith(path7.join("src", "lib", "environment.ts")) || relativePath2 === path7.join("packages", "topolo-ui-kit", "src", "environmentUrls.ts")) return true;
11731
+ if (relativePath2.includes(`${path7.sep}test${path7.sep}`) || relativePath2.includes(`${path7.sep}tests${path7.sep}`) || relativePath2.includes(`${path7.sep}e2e${path7.sep}`)) return true;
11732
+ if (relativePath2.startsWith(`scripts${path7.sep}`) || relativePath2.includes(`${path7.sep}scripts${path7.sep}`)) return true;
11733
+ return !RUNTIME_EXTENSIONS.has(path7.extname(filePath));
10850
11734
  }
10851
11735
  function scanFile(filePath) {
10852
11736
  if (shouldSkipFile(filePath)) return;
10853
- const relativePath = path5.relative(root, filePath);
11737
+ const relativePath2 = path7.relative(root, filePath);
10854
11738
  const content = readFileSync6(filePath, "utf8");
10855
11739
  const lines = content.split(/\r?\n/);
10856
11740
  lines.forEach((line, index) => {
10857
11741
  const matches = [...line.matchAll(PRODUCTION_TOPOLO_URL_PATTERN)].map((match) => match[0]);
10858
11742
  for (const url of new Set(matches)) {
10859
- const allowed = STAGING_TOPOLO_URL_PATTERN.test(line) || line.includes("PRODUCTION_") || line.includes("hostname ===") || line.includes(".split(") && line.includes(".join(") || allowlist.some((pattern) => pattern.test(`${relativePath}:${index + 1}:${line}`));
11743
+ const allowed = STAGING_TOPOLO_URL_PATTERN.test(line) || line.includes("PRODUCTION_") || line.includes("hostname ===") || line.includes(".split(") && line.includes(".join(") || allowlist.some((pattern) => pattern.test(`${relativePath2}:${index + 1}:${line}`));
10860
11744
  if (!allowed) {
10861
- violations.push({ file: relativePath, line: index + 1, url, text: line.trim() });
11745
+ violations.push({ file: relativePath2, line: index + 1, url, text: line.trim() });
10862
11746
  }
10863
11747
  }
10864
11748
  });
10865
11749
  }
10866
11750
  function walk(dir) {
10867
11751
  for (const entry of readdirSync2(dir, { withFileTypes: true })) {
10868
- const fullPath = path5.join(dir, entry.name);
11752
+ const fullPath = path7.join(dir, entry.name);
10869
11753
  if (entry.isDirectory()) {
10870
11754
  if (!SKIPPED_SCAN_DIRS.has(entry.name)) walk(fullPath);
10871
11755
  continue;
@@ -11033,7 +11917,7 @@ var DEFAULT_SEED_BASE_URL = "https://seed.stg.topolo.us";
11033
11917
  function seedBaseUrl() {
11034
11918
  return process.env.TOPOLO_SEED_URL || DEFAULT_SEED_BASE_URL;
11035
11919
  }
11036
- async function authedFetch(path6, init = {}) {
11920
+ async function authedFetch(path8, init = {}) {
11037
11921
  const credential = await refreshCredentialIfNeeded("staging");
11038
11922
  if (!credential) {
11039
11923
  throw new Error("No staging credential configured. Run `topolo auth login --env staging` or set TOPOLO_STAGING_API_KEY.");
@@ -11043,7 +11927,7 @@ async function authedFetch(path6, init = {}) {
11043
11927
  else headers.set("Authorization", `Bearer ${credential.accessToken}`);
11044
11928
  headers.set("X-Topolo-Client", `topolo-cli/${CLI_VERSION}`);
11045
11929
  if (init.body && !headers.has("Content-Type")) headers.set("Content-Type", "application/json");
11046
- const response = await fetch(`${seedBaseUrl()}${path6}`, { ...init, headers });
11930
+ const response = await fetch(`${seedBaseUrl()}${path8}`, { ...init, headers });
11047
11931
  const text = await response.text();
11048
11932
  let body;
11049
11933
  try {
@@ -11320,7 +12204,7 @@ function createOAuthClientPayload(options) {
11320
12204
  logo_url: options.logoUrl
11321
12205
  };
11322
12206
  }
11323
- async function authFetchJson(path6, init = {}) {
12207
+ async function authFetchJson(path8, init = {}) {
11324
12208
  const environment = selectedEnvironment();
11325
12209
  const credential = await refreshCredentialIfNeeded(environment);
11326
12210
  if (!credential) {
@@ -11342,7 +12226,7 @@ async function authFetchJson(path6, init = {}) {
11342
12226
  throw err;
11343
12227
  }
11344
12228
  headers.set("Authorization", `Bearer ${credential.accessToken}`);
11345
- const response = await fetch(`${authBaseUrl}${path6}`, { ...init, headers });
12229
+ const response = await fetch(`${authBaseUrl}${path8}`, { ...init, headers });
11346
12230
  const text = await response.text();
11347
12231
  const payload = text ? JSON.parse(text) : {};
11348
12232
  if (!response.ok || payload.success === false) {
@@ -11358,7 +12242,7 @@ function normalizeList(values, fallback = []) {
11358
12242
  }
11359
12243
 
11360
12244
  // src/commands/integrations.ts
11361
- import { readdir, readFile as readFile4, stat } from "fs/promises";
12245
+ import { readdir, readFile as readFile6, stat } from "fs/promises";
11362
12246
  import { join as join4, relative as relative3 } from "path";
11363
12247
  function registerIntegrationsCommand(program2) {
11364
12248
  const integrations = program2.command("integrations").description("Generate and validate Topolo integration handoffs.");
@@ -11502,7 +12386,7 @@ function detectFramework(packageJson2, haystack) {
11502
12386
  }
11503
12387
  async function readPackageJson2(repoRoot) {
11504
12388
  try {
11505
- return JSON.parse(await readFile4(join4(repoRoot, "package.json"), "utf8"));
12389
+ return JSON.parse(await readFile6(join4(repoRoot, "package.json"), "utf8"));
11506
12390
  } catch {
11507
12391
  return null;
11508
12392
  }
@@ -11525,7 +12409,7 @@ async function collectFiles(repoRoot) {
11525
12409
  if (info.size > 25e4) continue;
11526
12410
  result.push({
11527
12411
  path: relative3(root, fullPath),
11528
- content: await readFile4(fullPath, "utf8")
12412
+ content: await readFile6(fullPath, "utf8")
11529
12413
  });
11530
12414
  }
11531
12415
  }
@@ -11543,7 +12427,7 @@ function formatCheckResult(result) {
11543
12427
  // src/commands/skills.ts
11544
12428
  import { spawnSync as spawnSync2 } from "child_process";
11545
12429
  import { existsSync as existsSync5 } from "fs";
11546
- import { mkdir as mkdir3, copyFile, readFile as readFile5, writeFile as writeFile5, rm } from "fs/promises";
12430
+ import { mkdir as mkdir4, copyFile, readFile as readFile7, writeFile as writeFile7, rm } from "fs/promises";
11547
12431
  import { homedir as homedir2 } from "os";
11548
12432
  import { dirname as dirname4, join as join5, resolve as resolve3 } from "path";
11549
12433
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -11579,7 +12463,7 @@ function codexSkillDir() {
11579
12463
  return join5(homedir2(), ".codex", "skills", "topolo");
11580
12464
  }
11581
12465
  async function installSkillFile(sourceDir, targetDir) {
11582
- await mkdir3(targetDir, { recursive: true });
12466
+ await mkdir4(targetDir, { recursive: true });
11583
12467
  const target = join5(targetDir, "SKILL.md");
11584
12468
  await copyFile(join5(sourceDir, "topolo", "SKILL.md"), target);
11585
12469
  return target;
@@ -11597,11 +12481,11 @@ function registerViaOfficialCli(cli, mcp, agentName) {
11597
12481
  return result.status === 0;
11598
12482
  }
11599
12483
  async function registerClaudeViaConfig(mcp) {
11600
- const path6 = join5(homedir2(), ".claude.json");
12484
+ const path8 = join5(homedir2(), ".claude.json");
11601
12485
  let config = {};
11602
- if (existsSync5(path6)) {
12486
+ if (existsSync5(path8)) {
11603
12487
  try {
11604
- config = JSON.parse(await readFile5(path6, "utf8"));
12488
+ config = JSON.parse(await readFile7(path8, "utf8"));
11605
12489
  } catch {
11606
12490
  config = {};
11607
12491
  }
@@ -11614,13 +12498,13 @@ async function registerClaudeViaConfig(mcp) {
11614
12498
  env: { TOPOLO_AGENT_NAME: AGENT_NAME_FOR.claude }
11615
12499
  };
11616
12500
  config.mcpServers = servers;
11617
- await writeFile5(path6, `${JSON.stringify(config, null, 2)}
12501
+ await writeFile7(path8, `${JSON.stringify(config, null, 2)}
11618
12502
  `, "utf8");
11619
12503
  }
11620
12504
  async function registerCodexViaConfig(mcp) {
11621
- const path6 = join5(homedir2(), ".codex", "config.toml");
12505
+ const path8 = join5(homedir2(), ".codex", "config.toml");
11622
12506
  let text = "";
11623
- if (existsSync5(path6)) text = await readFile5(path6, "utf8");
12507
+ if (existsSync5(path8)) text = await readFile7(path8, "utf8");
11624
12508
  text = text.replace(
11625
12509
  /(^|\n)\[mcp_servers\.topolo(?:\.[a-z]+)?\][^\[]*/g,
11626
12510
  (match) => match.startsWith("\n") ? "\n" : ""
@@ -11636,8 +12520,8 @@ command = "${mcp.command}"${argsToml}
11636
12520
  [mcp_servers.topolo.env]
11637
12521
  TOPOLO_AGENT_NAME = "${AGENT_NAME_FOR.codex}"
11638
12522
  `;
11639
- await mkdir3(dirname4(path6), { recursive: true });
11640
- await writeFile5(path6, `${text}${block}`, "utf8");
12523
+ await mkdir4(dirname4(path8), { recursive: true });
12524
+ await writeFile7(path8, `${text}${block}`, "utf8");
11641
12525
  }
11642
12526
  async function installForAgent(agent, sourceDir, mcp, opts) {
11643
12527
  const skillDir = agent === "claude" ? claudeSkillDir(opts.projectDir) : codexSkillDir();