@topolo/cli 0.5.1 → 0.5.2

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, path5, status, body, message) {
65
- super("http_error", message ?? `HTTP ${status} from ${service}${path5}`);
64
+ constructor(service, path6, status, body, message) {
65
+ super("http_error", message ?? `HTTP ${status} from ${service}${path6}`);
66
66
  this.name = "TopoloHttpError";
67
67
  this.status = status;
68
68
  this.body = body;
69
69
  this.service = service;
70
- this.path = path5;
70
+ this.path = path6;
71
71
  }
72
72
  };
73
73
  var APP_API_URLS = {
@@ -185,7 +185,7 @@ var APPLICATIONS = {
185
185
  "appDir": "TopoloAuth",
186
186
  "name": "topolo-auth",
187
187
  "packageName": "topolo-auth",
188
- "productionUrl": "https://auth.topolo.app/login",
188
+ "productionUrl": "https://auth.topolo.app",
189
189
  "services": [
190
190
  "auth"
191
191
  ],
@@ -273,9 +273,7 @@ var APPLICATIONS = {
273
273
  "domains": [
274
274
  "brand.topolo.app"
275
275
  ],
276
- "routes": [
277
- "brand.topolo.app"
278
- ],
276
+ "routes": [],
279
277
  "appId": "brand"
280
278
  },
281
279
  {
@@ -387,9 +385,7 @@ var APPLICATIONS = {
387
385
  "domains": [
388
386
  "campaigns.topolo.app"
389
387
  ],
390
- "routes": [
391
- "campaigns.topolo.app"
392
- ],
388
+ "routes": [],
393
389
  "appId": "campaigns"
394
390
  },
395
391
  {
@@ -1077,11 +1073,13 @@ var APPLICATIONS = {
1077
1073
  {
1078
1074
  "name": "topolo-nexus-dashboard",
1079
1075
  "kind": "worker",
1080
- "apiUrl": null,
1076
+ "apiUrl": "https://nexus.topolo.app",
1081
1077
  "domains": [
1082
1078
  "nexus.topolo.app"
1083
1079
  ],
1084
- "routes": [],
1080
+ "routes": [
1081
+ "nexus.topolo.app"
1082
+ ],
1085
1083
  "appId": null
1086
1084
  },
1087
1085
  {
@@ -2178,17 +2176,17 @@ function validateTopoloActionInput(schema, input3) {
2178
2176
  validateNode(schema, input3, "$", schema, errors, 0);
2179
2177
  return { valid: errors.length === 0, errors };
2180
2178
  }
2181
- function inspectSchema(schema, path5, root, depth) {
2182
- if (depth > 128) return { path: path5, code: "schema_depth", message: "Schema recursion exceeded 128 levels." };
2179
+ function inspectSchema(schema, path6, root, depth) {
2180
+ if (depth > 128) return { path: path6, code: "schema_depth", message: "Schema recursion exceeded 128 levels." };
2183
2181
  const unsupportedKeyword = Object.keys(schema).find((keyword) => !SUPPORTED_SCHEMA_KEYWORDS.has(keyword));
2184
2182
  if (unsupportedKeyword) {
2185
- return { path: path5, code: "unsupported_schema_keyword", message: `Action schema uses unsupported keyword ${unsupportedKeyword}.` };
2183
+ return { path: path6, code: "unsupported_schema_keyword", message: `Action schema uses unsupported keyword ${unsupportedKeyword}.` };
2186
2184
  }
2187
2185
  if (typeof schema.format === "string" && !["uri", "email", "date-time"].includes(schema.format)) {
2188
- return { path: path5, code: "unsupported_schema_format", message: `Action schema uses unsupported format ${schema.format}.` };
2186
+ return { path: path6, code: "unsupported_schema_format", message: `Action schema uses unsupported format ${schema.format}.` };
2189
2187
  }
2190
2188
  if (typeof schema.$ref === "string" && !resolveLocalReference(root, schema.$ref)) {
2191
- return { path: path5, code: "unsupported_schema_ref", message: `Cannot resolve schema reference ${schema.$ref}.` };
2189
+ return { path: path6, code: "unsupported_schema_ref", message: `Cannot resolve schema reference ${schema.$ref}.` };
2192
2190
  }
2193
2191
  const children = [];
2194
2192
  if (isRecord(schema.properties)) children.push(...Object.entries(schema.properties));
@@ -2204,7 +2202,7 @@ function inspectSchema(schema, path5, root, depth) {
2204
2202
  );
2205
2203
  for (const [key, child] of children) {
2206
2204
  if (!isRecord(child)) continue;
2207
- const issue = inspectSchema(child, `${path5}.${key}`, root, depth + 1);
2205
+ const issue = inspectSchema(child, `${path6}.${key}`, root, depth + 1);
2208
2206
  if (issue) return issue;
2209
2207
  }
2210
2208
  return null;
@@ -2212,129 +2210,129 @@ function inspectSchema(schema, path5, root, depth) {
2212
2210
  function arraySchemaEntries(keyword, value) {
2213
2211
  return Array.isArray(value) ? value.map((candidate, index) => [`${keyword}[${index}]`, candidate]) : [];
2214
2212
  }
2215
- function validateNode(schema, value, path5, root, errors, depth) {
2213
+ function validateNode(schema, value, path6, root, errors, depth) {
2216
2214
  if (depth > 128) {
2217
- errors.push({ path: path5, code: "schema_depth", message: "Schema recursion exceeded 128 levels." });
2215
+ errors.push({ path: path6, code: "schema_depth", message: "Schema recursion exceeded 128 levels." });
2218
2216
  return;
2219
2217
  }
2220
2218
  if (typeof schema.$ref === "string") {
2221
2219
  const resolved = resolveLocalReference(root, schema.$ref);
2222
2220
  if (!resolved) {
2223
- errors.push({ path: path5, code: "unsupported_schema_ref", message: `Cannot resolve schema reference ${schema.$ref}.` });
2221
+ errors.push({ path: path6, code: "unsupported_schema_ref", message: `Cannot resolve schema reference ${schema.$ref}.` });
2224
2222
  return;
2225
2223
  }
2226
- validateNode(resolved, value, path5, root, errors, depth + 1);
2224
+ validateNode(resolved, value, path6, root, errors, depth + 1);
2227
2225
  }
2228
2226
  if (Array.isArray(schema.allOf)) {
2229
2227
  for (const candidate of schema.allOf) {
2230
- if (isRecord(candidate)) validateNode(candidate, value, path5, root, errors, depth + 1);
2228
+ if (isRecord(candidate)) validateNode(candidate, value, path6, root, errors, depth + 1);
2231
2229
  }
2232
2230
  }
2233
2231
  if (Array.isArray(schema.anyOf) && !schema.anyOf.some((candidate) => candidateMatches(candidate, value, root, depth))) {
2234
- errors.push({ path: path5, code: "any_of", message: "Value does not match any allowed schema." });
2232
+ errors.push({ path: path6, code: "any_of", message: "Value does not match any allowed schema." });
2235
2233
  return;
2236
2234
  }
2237
2235
  if (Array.isArray(schema.oneOf)) {
2238
2236
  const matches = schema.oneOf.filter((candidate) => candidateMatches(candidate, value, root, depth)).length;
2239
2237
  if (matches !== 1) {
2240
- errors.push({ path: path5, code: "one_of", message: "Value must match exactly one allowed schema." });
2238
+ errors.push({ path: path6, code: "one_of", message: "Value must match exactly one allowed schema." });
2241
2239
  return;
2242
2240
  }
2243
2241
  }
2244
2242
  if (value === null && schema.nullable === true) return;
2245
2243
  if (Object.prototype.hasOwnProperty.call(schema, "const") && !sameJsonValue(value, schema.const)) {
2246
- errors.push({ path: path5, code: "const", message: `Value must equal ${JSON.stringify(schema.const)}.` });
2244
+ errors.push({ path: path6, code: "const", message: `Value must equal ${JSON.stringify(schema.const)}.` });
2247
2245
  return;
2248
2246
  }
2249
2247
  if (Array.isArray(schema.enum) && !schema.enum.some((candidate) => sameJsonValue(value, candidate))) {
2250
- errors.push({ path: path5, code: "enum", message: `Value must be one of ${schema.enum.map((item) => JSON.stringify(item)).join(", ")}.` });
2248
+ errors.push({ path: path6, code: "enum", message: `Value must be one of ${schema.enum.map((item) => JSON.stringify(item)).join(", ")}.` });
2251
2249
  return;
2252
2250
  }
2253
2251
  if (!matchesType(schema.type, value)) {
2254
- errors.push({ path: path5, code: "type", message: `Expected ${String(schema.type)}.` });
2252
+ errors.push({ path: path6, code: "type", message: `Expected ${String(schema.type)}.` });
2255
2253
  return;
2256
2254
  }
2257
- if (typeof value === "string") validateString(schema, value, path5, errors);
2258
- if (typeof value === "number") validateNumber(schema, value, path5, errors);
2259
- if (Array.isArray(value)) validateArray(schema, value, path5, root, errors, depth);
2260
- if (isRecord(value)) validateObject(schema, value, path5, root, errors, depth);
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);
2261
2259
  }
2262
- function validateObject(schema, value, path5, root, errors, depth) {
2260
+ function validateObject(schema, value, path6, root, errors, depth) {
2263
2261
  if (typeof schema.minProperties === "number" && Object.keys(value).length < schema.minProperties) {
2264
- errors.push({ path: path5, code: "min_properties", message: `Expected at least ${schema.minProperties} properties.` });
2262
+ errors.push({ path: path6, code: "min_properties", message: `Expected at least ${schema.minProperties} properties.` });
2265
2263
  }
2266
2264
  if (typeof schema.maxProperties === "number" && Object.keys(value).length > schema.maxProperties) {
2267
- errors.push({ path: path5, code: "max_properties", message: `Expected at most ${schema.maxProperties} properties.` });
2265
+ errors.push({ path: path6, code: "max_properties", message: `Expected at most ${schema.maxProperties} properties.` });
2268
2266
  }
2269
2267
  const properties = isRecord(schema.properties) ? schema.properties : {};
2270
2268
  const required = Array.isArray(schema.required) ? schema.required.map(String) : [];
2271
2269
  for (const key of required) {
2272
2270
  if (!Object.prototype.hasOwnProperty.call(value, key)) {
2273
- errors.push({ path: `${path5}.${key}`, code: "required", message: "Required property is missing." });
2271
+ errors.push({ path: `${path6}.${key}`, code: "required", message: "Required property is missing." });
2274
2272
  }
2275
2273
  }
2276
2274
  if (isRecord(schema.propertyNames)) {
2277
2275
  for (const key of Object.keys(value)) {
2278
- validateNode(schema.propertyNames, key, `${path5}.${key}`, root, errors, depth + 1);
2276
+ validateNode(schema.propertyNames, key, `${path6}.${key}`, root, errors, depth + 1);
2279
2277
  }
2280
2278
  }
2281
2279
  for (const [key, child] of Object.entries(value)) {
2282
2280
  const childSchema = properties[key];
2283
2281
  if (isRecord(childSchema)) {
2284
- validateNode(childSchema, child, `${path5}.${key}`, root, errors, depth + 1);
2282
+ validateNode(childSchema, child, `${path6}.${key}`, root, errors, depth + 1);
2285
2283
  } else if (schema.additionalProperties === false) {
2286
- errors.push({ path: `${path5}.${key}`, code: "additional_property", message: "Property is not declared by the action schema." });
2284
+ errors.push({ path: `${path6}.${key}`, code: "additional_property", message: "Property is not declared by the action schema." });
2287
2285
  } else if (isRecord(schema.additionalProperties)) {
2288
- validateNode(schema.additionalProperties, child, `${path5}.${key}`, root, errors, depth + 1);
2286
+ validateNode(schema.additionalProperties, child, `${path6}.${key}`, root, errors, depth + 1);
2289
2287
  }
2290
2288
  }
2291
2289
  }
2292
- function validateArray(schema, value, path5, root, errors, depth) {
2290
+ function validateArray(schema, value, path6, root, errors, depth) {
2293
2291
  if (typeof schema.minItems === "number" && value.length < schema.minItems) {
2294
- errors.push({ path: path5, code: "min_items", message: `Expected at least ${schema.minItems} items.` });
2292
+ errors.push({ path: path6, code: "min_items", message: `Expected at least ${schema.minItems} items.` });
2295
2293
  }
2296
2294
  if (typeof schema.maxItems === "number" && value.length > schema.maxItems) {
2297
- errors.push({ path: path5, code: "max_items", message: `Expected at most ${schema.maxItems} items.` });
2295
+ errors.push({ path: path6, code: "max_items", message: `Expected at most ${schema.maxItems} items.` });
2298
2296
  }
2299
2297
  if (schema.uniqueItems === true) {
2300
2298
  const unique2 = new Set(value.map((item) => JSON.stringify(item)));
2301
- if (unique2.size !== value.length) errors.push({ path: path5, code: "unique_items", message: "Expected every item to be unique." });
2299
+ if (unique2.size !== value.length) errors.push({ path: path6, code: "unique_items", message: "Expected every item to be unique." });
2302
2300
  }
2303
2301
  if (isRecord(schema.items)) {
2304
- value.forEach((item, index) => validateNode(schema.items, item, `${path5}[${index}]`, root, errors, depth + 1));
2302
+ value.forEach((item, index) => validateNode(schema.items, item, `${path6}[${index}]`, root, errors, depth + 1));
2305
2303
  }
2306
2304
  }
2307
- function validateString(schema, value, path5, errors) {
2305
+ function validateString(schema, value, path6, errors) {
2308
2306
  if (typeof schema.minLength === "number" && value.length < schema.minLength) {
2309
- errors.push({ path: path5, code: "min_length", message: `Expected at least ${schema.minLength} characters.` });
2307
+ errors.push({ path: path6, code: "min_length", message: `Expected at least ${schema.minLength} characters.` });
2310
2308
  }
2311
2309
  if (typeof schema.maxLength === "number" && value.length > schema.maxLength) {
2312
- errors.push({ path: path5, code: "max_length", message: `Expected at most ${schema.maxLength} characters.` });
2310
+ errors.push({ path: path6, code: "max_length", message: `Expected at most ${schema.maxLength} characters.` });
2313
2311
  }
2314
2312
  if (typeof schema.pattern === "string") {
2315
2313
  try {
2316
- if (!new RegExp(schema.pattern).test(value)) errors.push({ path: path5, code: "pattern", message: "Value does not match the required pattern." });
2314
+ if (!new RegExp(schema.pattern).test(value)) errors.push({ path: path6, code: "pattern", message: "Value does not match the required pattern." });
2317
2315
  } catch {
2318
- errors.push({ path: path5, code: "invalid_schema_pattern", message: "Action schema contains an invalid pattern." });
2316
+ errors.push({ path: path6, code: "invalid_schema_pattern", message: "Action schema contains an invalid pattern." });
2319
2317
  }
2320
2318
  }
2321
- if (schema.format === "uri" && !isUrl(value)) errors.push({ path: path5, code: "format_uri", message: "Expected an absolute URL." });
2322
- if (schema.format === "email" && !/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(value)) errors.push({ path: path5, code: "format_email", message: "Expected an email address." });
2323
- if (schema.format === "date-time" && !isDateTime(value)) errors.push({ path: path5, code: "format_date_time", message: "Expected an ISO-8601 date-time." });
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." });
2324
2322
  }
2325
- function validateNumber(schema, value, path5, errors) {
2326
- if (typeof schema.minimum === "number" && value < schema.minimum) errors.push({ path: path5, code: "minimum", message: `Expected a value of at least ${schema.minimum}.` });
2327
- if (typeof schema.maximum === "number" && value > schema.maximum) errors.push({ path: path5, code: "maximum", message: `Expected a value of at most ${schema.maximum}.` });
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}.` });
2328
2326
  const exclusiveMinimum = typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : schema.exclusiveMinimum === true && typeof schema.minimum === "number" ? schema.minimum : null;
2329
2327
  const exclusiveMaximum = typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : schema.exclusiveMaximum === true && typeof schema.maximum === "number" ? schema.maximum : null;
2330
2328
  if (exclusiveMinimum !== null && value <= exclusiveMinimum) {
2331
- errors.push({ path: path5, code: "exclusive_minimum", message: `Expected a value greater than ${exclusiveMinimum}.` });
2329
+ errors.push({ path: path6, code: "exclusive_minimum", message: `Expected a value greater than ${exclusiveMinimum}.` });
2332
2330
  }
2333
2331
  if (exclusiveMaximum !== null && value >= exclusiveMaximum) {
2334
- errors.push({ path: path5, code: "exclusive_maximum", message: `Expected a value less than ${exclusiveMaximum}.` });
2332
+ errors.push({ path: path6, code: "exclusive_maximum", message: `Expected a value less than ${exclusiveMaximum}.` });
2335
2333
  }
2336
2334
  if (typeof schema.multipleOf === "number" && !isMultipleOf(value, schema.multipleOf)) {
2337
- errors.push({ path: path5, code: "multiple_of", message: `Expected a multiple of ${schema.multipleOf}.` });
2335
+ errors.push({ path: path6, code: "multiple_of", message: `Expected a multiple of ${schema.multipleOf}.` });
2338
2336
  }
2339
2337
  }
2340
2338
  function candidateMatches(candidate, value, root, depth) {
@@ -3227,8 +3225,8 @@ function normalizeActionCatalogEntry(item) {
3227
3225
  const name = pickActionString(a.name);
3228
3226
  const appId = pickActionString(a.app_id) ?? pickActionString(a.appId);
3229
3227
  const method = pickActionString(a.method)?.toUpperCase();
3230
- const path5 = pickActionString(a.path);
3231
- if (!actionId || !name || !appId || !method || !path5 || !isHttpMethod(method)) {
3228
+ const path6 = pickActionString(a.path);
3229
+ if (!actionId || !name || !appId || !method || !path6 || !isHttpMethod(method)) {
3232
3230
  return null;
3233
3231
  }
3234
3232
  return {
@@ -3243,7 +3241,7 @@ function normalizeActionCatalogEntry(item) {
3243
3241
  appSlug: pickActionString(a.app_slug) ?? pickActionString(a.appSlug) ?? null,
3244
3242
  appName: pickActionString(a.app_name) ?? pickActionString(a.appName) ?? null,
3245
3243
  method,
3246
- path: path5,
3244
+ path: path6,
3247
3245
  permissionName: pickActionString(a.permission_name) ?? pickActionString(a.permissionName) ?? "",
3248
3246
  requiredPermission: pickActionString(a.required_permission) ?? pickActionString(a.requiredPermission) ?? "",
3249
3247
  authorizationMode: pickAuthorizationMode(a.authorization_mode ?? a.authorizationMode),
@@ -3279,7 +3277,7 @@ function stringArray(value) {
3279
3277
  }
3280
3278
  function mapActionInput(action, input3) {
3281
3279
  const remaining = { ...input3 || {} };
3282
- const path5 = action.path.replace(/\{([a-zA-Z0-9_]+)\}/g, (_match, key) => {
3280
+ const path6 = action.path.replace(/\{([a-zA-Z0-9_]+)\}/g, (_match, key) => {
3283
3281
  const value = remaining[key];
3284
3282
  if (value === void 0 || value === null || value === "") {
3285
3283
  throw new TopoloSdkError(
@@ -3296,14 +3294,14 @@ function mapActionInput(action, input3) {
3296
3294
  delete remaining.body;
3297
3295
  if (action.method === "GET") {
3298
3296
  return {
3299
- path: path5,
3297
+ path: path6,
3300
3298
  query: objectToQuery({ ...remaining, ...explicitQuery })
3301
3299
  };
3302
3300
  }
3303
3301
  const query = objectToQuery(explicitQuery);
3304
3302
  const body = explicitBody !== void 0 ? explicitBody : remaining;
3305
3303
  return {
3306
- path: path5,
3304
+ path: path6,
3307
3305
  ...query ? { query } : {},
3308
3306
  body
3309
3307
  };
@@ -3473,8 +3471,8 @@ var TopoloOAuth = class {
3473
3471
  if (params.clientSecret) body.set("client_secret", params.clientSecret);
3474
3472
  return this.#postForm("/api/developer-oauth/token", body);
3475
3473
  }
3476
- async #postForm(path5, body) {
3477
- const url = new URL(path5, this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`);
3474
+ async #postForm(path6, body) {
3475
+ const url = new URL(path6, this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`);
3478
3476
  const res = await this.fetchImpl(url.toString(), {
3479
3477
  method: "POST",
3480
3478
  headers: {
@@ -3490,7 +3488,7 @@ var TopoloOAuth = class {
3490
3488
  const err = parsed;
3491
3489
  throw new TopoloAuthError(err.error_description || err.error, err.error);
3492
3490
  }
3493
- throw new TopoloHttpError("auth", path5, res.status, parsed);
3491
+ throw new TopoloHttpError("auth", path6, res.status, parsed);
3494
3492
  }
3495
3493
  return parsed;
3496
3494
  }
@@ -3528,13 +3526,13 @@ var TopoloHttpError2 = class extends TopoloSdkError2 {
3528
3526
  body;
3529
3527
  service;
3530
3528
  path;
3531
- constructor(service, path5, status, body, message) {
3532
- super("http_error", message ?? `HTTP ${status} from ${service}${path5}`);
3529
+ constructor(service, path6, status, body, message) {
3530
+ super("http_error", message ?? `HTTP ${status} from ${service}${path6}`);
3533
3531
  this.name = "TopoloHttpError";
3534
3532
  this.status = status;
3535
3533
  this.body = body;
3536
3534
  this.service = service;
3537
- this.path = path5;
3535
+ this.path = path6;
3538
3536
  }
3539
3537
  };
3540
3538
  var APP_API_URLS2 = {
@@ -3665,8 +3663,8 @@ var TopoloOAuth2 = class {
3665
3663
  if (params.clientSecret) body.set("client_secret", params.clientSecret);
3666
3664
  return this.#postForm("/api/developer-oauth/token", body);
3667
3665
  }
3668
- async #postForm(path5, body) {
3669
- const url = new URL(path5, this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`);
3666
+ async #postForm(path6, body) {
3667
+ const url = new URL(path6, this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`);
3670
3668
  const res = await this.fetchImpl(url.toString(), {
3671
3669
  method: "POST",
3672
3670
  headers: {
@@ -3682,7 +3680,7 @@ var TopoloOAuth2 = class {
3682
3680
  const err = parsed;
3683
3681
  throw new TopoloAuthError2(err.error_description || err.error, err.error);
3684
3682
  }
3685
- throw new TopoloHttpError2("auth", path5, res.status, parsed);
3683
+ throw new TopoloHttpError2("auth", path6, res.status, parsed);
3686
3684
  }
3687
3685
  return parsed;
3688
3686
  }
@@ -3712,22 +3710,22 @@ function configPath() {
3712
3710
  return join(base, "topolo", "config.json");
3713
3711
  }
3714
3712
  async function readConfig() {
3715
- const path5 = configPath();
3716
- if (!existsSync(path5)) return {};
3713
+ const path6 = configPath();
3714
+ if (!existsSync(path6)) return {};
3717
3715
  try {
3718
- const raw = await readFile(path5, "utf8");
3716
+ const raw = await readFile(path6, "utf8");
3719
3717
  return JSON.parse(raw);
3720
3718
  } catch {
3721
3719
  return {};
3722
3720
  }
3723
3721
  }
3724
3722
  async function writeConfig(config) {
3725
- const path5 = configPath();
3726
- await mkdir(dirname(path5), { recursive: true });
3727
- await writeFile(path5, JSON.stringify(config, null, 2) + "\n", "utf8");
3723
+ const path6 = configPath();
3724
+ await mkdir(dirname(path6), { recursive: true });
3725
+ await writeFile(path6, JSON.stringify(config, null, 2) + "\n", "utf8");
3728
3726
  if (platform() !== "win32") {
3729
3727
  try {
3730
- await chmod(path5, 384);
3728
+ await chmod(path6, 384);
3731
3729
  } catch {
3732
3730
  }
3733
3731
  }
@@ -4047,9 +4045,9 @@ function readCliVersion() {
4047
4045
  join2(here, "package.json"),
4048
4046
  join2(here, "..", "package.json")
4049
4047
  ];
4050
- for (const path5 of candidates) {
4048
+ for (const path6 of candidates) {
4051
4049
  try {
4052
- const pkg = JSON.parse(readFileSync(path5, "utf8"));
4050
+ const pkg = JSON.parse(readFileSync(path6, "utf8"));
4053
4051
  if (typeof pkg.version === "string" && pkg.version) return pkg.version;
4054
4052
  } catch {
4055
4053
  }
@@ -4408,7 +4406,7 @@ function registerApiCommand(program2) {
4408
4406
  "-q, --query <kv...>",
4409
4407
  "Query params as key=value (repeatable), e.g. --query source=cli page=2"
4410
4408
  ).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(
4411
- async (service, method, path5, opts) => {
4409
+ async (service, method, path6, opts) => {
4412
4410
  const mode = resolveOutputMode(opts.json);
4413
4411
  try {
4414
4412
  const upperMethod = method.toUpperCase();
@@ -4447,7 +4445,7 @@ function registerApiCommand(program2) {
4447
4445
  const result = await topolo.client.request({
4448
4446
  service,
4449
4447
  method: httpMethod,
4450
- path: path5,
4448
+ path: path6,
4451
4449
  body,
4452
4450
  ...query ? { query } : {},
4453
4451
  ...resource ? { resource } : {},
@@ -4611,13 +4609,13 @@ async function requiredClient() {
4611
4609
  function parseJsonObject(value) {
4612
4610
  let source = value;
4613
4611
  if (value.startsWith("@")) {
4614
- const path5 = value.slice(1);
4615
- if (!path5) throw new Error("--data @file path must not be empty");
4612
+ const path6 = value.slice(1);
4613
+ if (!path6) throw new Error("--data @file path must not be empty");
4616
4614
  try {
4617
- source = readFileSync2(path5, "utf8");
4615
+ source = readFileSync2(path6, "utf8");
4618
4616
  } catch (err) {
4619
4617
  const detail = err instanceof Error ? err.message : String(err);
4620
- throw new Error(`Could not read --data file ${path5}: ${detail}`);
4618
+ throw new Error(`Could not read --data file ${path6}: ${detail}`);
4621
4619
  }
4622
4620
  }
4623
4621
  let parsed;
@@ -4691,8 +4689,8 @@ function formatActionPlan(plan) {
4691
4689
 
4692
4690
  // src/commands/apps.ts
4693
4691
  import { execSync } from "child_process";
4694
- import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync4, writeFileSync } from "fs";
4695
- import path3 from "path";
4692
+ import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
4693
+ import path4 from "path";
4696
4694
 
4697
4695
  // src/commands/app-audit-local.ts
4698
4696
  import {
@@ -5794,14 +5792,14 @@ import { constants } from "fs";
5794
5792
  import { access } from "fs/promises";
5795
5793
  import { relative, resolve } from "path";
5796
5794
  function normalizeHomePath(value) {
5797
- const path5 = String(value || "/dashboard").trim();
5798
- if (!path5.startsWith("/") || path5.startsWith("//") || /[?#]/.test(path5)) {
5795
+ const path6 = String(value || "/dashboard").trim();
5796
+ if (!path6.startsWith("/") || path6.startsWith("//") || /[?#]/.test(path6)) {
5799
5797
  throw new Error("Invalid home path. Use an absolute application path such as /dashboard.");
5800
5798
  }
5801
- if (["/", "/login", "/auth/callback", "/getting-started"].includes(path5)) {
5799
+ if (["/", "/login", "/auth/callback", "/getting-started"].includes(path6)) {
5802
5800
  throw new Error("Home path must be a protected product route, not a reserved entry route.");
5803
5801
  }
5804
- return path5.replace(/\/$/, "") || "/dashboard";
5802
+ return path6.replace(/\/$/, "") || "/dashboard";
5805
5803
  }
5806
5804
  function normalizeHomeLabel(value) {
5807
5805
  const label = String(value || "Dashboard").trim();
@@ -5942,9 +5940,9 @@ function toTitle(value) {
5942
5940
  function json(value) {
5943
5941
  return JSON.stringify(value, null, 2);
5944
5942
  }
5945
- async function pathExists(path5) {
5943
+ async function pathExists(path6) {
5946
5944
  try {
5947
- await access(path5, constants.F_OK);
5945
+ await access(path6, constants.F_OK);
5948
5946
  return true;
5949
5947
  } catch {
5950
5948
  return false;
@@ -6480,11 +6478,20 @@ function appIdentityTs(application) {
6480
6478
  "}",
6481
6479
  "",
6482
6480
  "export function getBrowserAuthBaseUrl(): string {",
6483
- " const defaultAuthUrl =",
6484
- " typeof window !== 'undefined' && window.location.hostname.endsWith('.stg.topolo.us')",
6485
- " ? 'https://auth.stg.topolo.us'",
6486
- " : 'https://auth.topolo.app';",
6487
- " return (import.meta.env.VITE_TOPOLO_AUTH_URL || defaultAuthUrl).replace(/\\/+$/, '');",
6481
+ " // A declared URL always wins; deployed builds set VITE_TOPOLO_AUTH_URL in",
6482
+ " // their per-environment build commands, so the host map below only covers",
6483
+ " // platform domains. Anything else fails loudly \u2014 an unknown host",
6484
+ " // (localhost, workers.dev previews) must never silently authenticate",
6485
+ " // against production Auth.",
6486
+ " const declared = import.meta.env.VITE_TOPOLO_AUTH_URL;",
6487
+ " if (declared) return declared.replace(/\\/+$/, '');",
6488
+ " const hostname = typeof window !== 'undefined' ? window.location.hostname : '';",
6489
+ " if (hostname.endsWith('.stg.topolo.us')) return 'https://auth.stg.topolo.us';",
6490
+ " if (hostname.endsWith('.topolo.dev')) return 'https://auth.topolo.dev';",
6491
+ " if (hostname.endsWith('.topolo.app')) return 'https://auth.topolo.app';",
6492
+ " throw new Error(",
6493
+ " `VITE_TOPOLO_AUTH_URL must be set for host \"${hostname || 'non-browser context'}\" \u2014 refusing to default to production Auth.`,",
6494
+ " );",
6488
6495
  "}",
6489
6496
  "",
6490
6497
  "// Canonical browser app id. index.html bakes %VITE_TOPOLO_APP_ID% into",
@@ -7219,7 +7226,12 @@ import { createAccountLinkPayload, createTopoloAuthorizationUrl, exchangeTopoloC
7219
7226
 
7220
7227
  type AuthState = 'ready' | 'missing_client' | 'authorizing' | 'authenticated' | 'error';
7221
7228
  const APP_NAME = '${escapeSingleQuoted(appName)}';
7222
- const AUTH_URL = import.meta.env.VITE_TOPOLO_AUTH_URL ?? import.meta.env.VITE_TOPOLO_AUTH_BASE_URL ?? 'https://auth.topolo.app';
7229
+ // Environment-aware default: hardcoding the production host would point a
7230
+ // staging build at prod Auth whenever the VITE_ vars were dropped.
7231
+ const DEFAULT_AUTH_URL = window.location.hostname.endsWith('.stg.topolo.us')
7232
+ ? 'https://auth.stg.topolo.us'
7233
+ : 'https://auth.topolo.app';
7234
+ const AUTH_URL = import.meta.env.VITE_TOPOLO_AUTH_URL ?? import.meta.env.VITE_TOPOLO_AUTH_BASE_URL ?? DEFAULT_AUTH_URL;
7223
7235
  const CLIENT_ID = import.meta.env.VITE_TOPOLO_CLIENT_ID || '${defaultClientId}';
7224
7236
  const REDIRECT_URI = import.meta.env.VITE_TOPOLO_REDIRECT_URI || \`\${window.location.origin}/auth/callback\`;
7225
7237
  const REQUESTED_SCOPES = (import.meta.env.VITE_TOPOLO_SCOPES || 'openid profile email organization:read entitlements:read').split(/[\\s,]+/).filter(Boolean);
@@ -7318,235 +7330,6 @@ function webWorkerTs(application, api) {
7318
7330
  ""
7319
7331
  ].join("\n");
7320
7332
  }
7321
- function platformWebWranglerToml(application, api) {
7322
- const slug = toSlug(application.id);
7323
- const host = application.productionUrl ? new URL(application.productionUrl).host : `${slug}.topolo.app`;
7324
- return [
7325
- `name = "topolo-${slug}"`,
7326
- 'main = "src/worker.ts"',
7327
- `compatibility_date = "${CLOUDFLARE_COMPATIBILITY_DATE}"`,
7328
- 'compatibility_flags = ["nodejs_compat"]',
7329
- `account_id = "${CLOUDFLARE_ACCOUNT_ID}"`,
7330
- "workers_dev = false",
7331
- "",
7332
- "[assets]",
7333
- 'directory = "./dist"',
7334
- 'binding = "ASSETS"',
7335
- 'not_found_handling = "single-page-application"',
7336
- ...api ? ['run_worker_first = ["/api/*"]'] : [],
7337
- "",
7338
- "[observability]",
7339
- "enabled = true",
7340
- "",
7341
- "[[routes]]",
7342
- `pattern = "${host}"`,
7343
- "custom_domain = true",
7344
- "",
7345
- "[vars]",
7346
- `APP_ID = "${application.appId ?? ""}"`,
7347
- `VITE_TOPOLO_APP_ID = "${application.appId ?? ""}"`,
7348
- "",
7349
- ...api ? [
7350
- "[[services]]",
7351
- 'binding = "API"',
7352
- `service = "topolo-${slug}-api"`,
7353
- ""
7354
- ] : [],
7355
- "[env.development]",
7356
- `name = "topolo-${slug}-development"`,
7357
- `account_id = "${DEVELOPMENT_CLOUDFLARE_ACCOUNT_ID}"`,
7358
- "workers_dev = true",
7359
- "",
7360
- "[env.development.vars]",
7361
- `APP_ID = "${application.appId ?? ""}"`,
7362
- `VITE_TOPOLO_APP_ID = "${application.appId ?? ""}"`,
7363
- "",
7364
- "[[env.development.routes]]",
7365
- `pattern = "${slug}.topolo.dev"`,
7366
- "custom_domain = true",
7367
- "",
7368
- "[env.development.assets]",
7369
- 'directory = "./dist"',
7370
- 'binding = "ASSETS"',
7371
- 'not_found_handling = "single-page-application"',
7372
- ...api ? ['run_worker_first = ["/api/*"]'] : [],
7373
- "",
7374
- ...api ? [
7375
- "[[env.development.services]]",
7376
- 'binding = "API"',
7377
- `service = "topolo-${slug}-api-development"`,
7378
- ""
7379
- ] : [],
7380
- "[env.staging]",
7381
- `name = "topolo-${slug}-staging"`,
7382
- `account_id = "${STAGING_CLOUDFLARE_ACCOUNT_ID}"`,
7383
- "workers_dev = true",
7384
- "",
7385
- "[env.staging.vars]",
7386
- `APP_ID = "${application.appId ?? ""}"`,
7387
- `VITE_TOPOLO_APP_ID = "${application.appId ?? ""}"`,
7388
- "",
7389
- "[[env.staging.routes]]",
7390
- `pattern = "${slug}.stg.topolo.us"`,
7391
- "custom_domain = true",
7392
- "",
7393
- "[env.staging.assets]",
7394
- 'directory = "./dist"',
7395
- 'binding = "ASSETS"',
7396
- 'not_found_handling = "single-page-application"',
7397
- ...api ? ['run_worker_first = ["/api/*"]'] : [],
7398
- "",
7399
- ...api ? [
7400
- "[[env.staging.services]]",
7401
- 'binding = "API"',
7402
- `service = "topolo-${slug}-api-staging"`,
7403
- ""
7404
- ] : [],
7405
- "[env.production]",
7406
- `name = "topolo-${slug}-production"`,
7407
- `account_id = "${CLOUDFLARE_ACCOUNT_ID}"`,
7408
- "workers_dev = false",
7409
- "",
7410
- "[env.production.vars]",
7411
- `APP_ID = "${application.appId ?? ""}"`,
7412
- `VITE_TOPOLO_APP_ID = "${application.appId ?? ""}"`,
7413
- "",
7414
- "[[env.production.routes]]",
7415
- `pattern = "${host}"`,
7416
- "custom_domain = true",
7417
- "",
7418
- "[env.production.assets]",
7419
- 'directory = "./dist"',
7420
- 'binding = "ASSETS"',
7421
- 'not_found_handling = "single-page-application"',
7422
- ...api ? ['run_worker_first = ["/api/*"]'] : [],
7423
- "",
7424
- ...api ? [
7425
- "[[env.production.services]]",
7426
- 'binding = "API"',
7427
- `service = "topolo-${slug}-api-production"`,
7428
- ""
7429
- ] : []
7430
- ].join("\n");
7431
- }
7432
- function platformApiWranglerToml(application, resources, profile) {
7433
- const slug = toSlug(application.id);
7434
- const resource = resourceConfig(application, profile);
7435
- const lines = [
7436
- `name = "topolo-${slug}-api"`,
7437
- 'main = "src/worker.ts"',
7438
- `compatibility_date = "${CLOUDFLARE_COMPATIBILITY_DATE}"`,
7439
- 'compatibility_flags = ["nodejs_compat"]',
7440
- `account_id = "${CLOUDFLARE_ACCOUNT_ID}"`,
7441
- "workers_dev = true",
7442
- "",
7443
- "[observability]",
7444
- "enabled = true",
7445
- "",
7446
- "[vars]",
7447
- 'AUTH_BASE_URL = "https://auth.topolo.app"',
7448
- 'NEXUS_GATEWAY_URL = "https://nexus.topolo.app"',
7449
- `NEXUS_APP_ID = "${slug}"`,
7450
- `APP_ID = "${application.appId ?? ""}"`,
7451
- `VITE_TOPOLO_APP_ID = "${application.appId ?? ""}"`,
7452
- ""
7453
- ];
7454
- appendResourceWranglerBlocks(lines, resources, resource, "production");
7455
- lines.push(
7456
- "[env.development]",
7457
- `name = "topolo-${slug}-api-development"`,
7458
- `account_id = "${DEVELOPMENT_CLOUDFLARE_ACCOUNT_ID}"`,
7459
- "workers_dev = true",
7460
- "",
7461
- "[env.development.vars]",
7462
- 'AUTH_BASE_URL = "https://auth.topolo.dev"',
7463
- 'NEXUS_GATEWAY_URL = "https://nexus.topolo.dev"',
7464
- `NEXUS_APP_ID = "${slug}"`,
7465
- `APP_ID = "${application.appId ?? ""}"`,
7466
- `VITE_TOPOLO_APP_ID = "${application.appId ?? ""}"`,
7467
- 'ENVIRONMENT = "development"',
7468
- ""
7469
- );
7470
- appendResourceWranglerBlocks(lines, resources, resource, "development");
7471
- lines.push(
7472
- "[env.staging]",
7473
- `name = "topolo-${slug}-api-staging"`,
7474
- `account_id = "${STAGING_CLOUDFLARE_ACCOUNT_ID}"`,
7475
- "workers_dev = true",
7476
- "",
7477
- "[env.staging.vars]",
7478
- 'AUTH_BASE_URL = "https://auth.stg.topolo.us"',
7479
- 'NEXUS_GATEWAY_URL = "https://nexus.stg.topolo.us"',
7480
- `NEXUS_APP_ID = "${slug}"`,
7481
- `APP_ID = "${application.appId ?? ""}"`,
7482
- `VITE_TOPOLO_APP_ID = "${application.appId ?? ""}"`,
7483
- ""
7484
- );
7485
- appendResourceWranglerBlocks(lines, resources, resource, "staging");
7486
- lines.push(
7487
- "[env.production]",
7488
- `name = "topolo-${slug}-api-production"`,
7489
- `account_id = "${CLOUDFLARE_ACCOUNT_ID}"`,
7490
- "workers_dev = false",
7491
- "",
7492
- "[env.production.vars]",
7493
- 'AUTH_BASE_URL = "https://auth.topolo.app"',
7494
- 'NEXUS_GATEWAY_URL = "https://nexus.topolo.app"',
7495
- `NEXUS_APP_ID = "${slug}"`,
7496
- `APP_ID = "${application.appId ?? ""}"`,
7497
- `VITE_TOPOLO_APP_ID = "${application.appId ?? ""}"`,
7498
- ""
7499
- );
7500
- appendResourceWranglerBlocks(lines, resources, resource, "production-env");
7501
- return lines.join("\n");
7502
- }
7503
- function appendResourceWranglerBlocks(lines, resources, resource, environment) {
7504
- const prefix = environment === "staging" ? "env.staging." : environment === "development" ? "env.development." : environment === "production-env" ? "env.production." : "";
7505
- const envName = environment === "staging" ? "Staging" : environment === "development" ? "Development" : "Production";
7506
- const pick = (dev, staging, prod) => environment === "development" ? dev : environment === "staging" ? staging : prod;
7507
- if (resources.d1) {
7508
- lines.push(
7509
- `[[${prefix}d1_databases]]`,
7510
- `binding = "${resource.d1Binding}"`,
7511
- `database_name = "${pick(resource.d1DevelopmentName, resource.d1StagingName, resource.d1ProductionName)}"`,
7512
- 'database_id = "provision-after-scaffold"',
7513
- 'migrations_dir = "migrations"',
7514
- ""
7515
- );
7516
- }
7517
- if (resources.r2) {
7518
- lines.push(
7519
- `[[${prefix}r2_buckets]]`,
7520
- `binding = "${resource.r2Binding}"`,
7521
- `bucket_name = "${pick(resource.r2DevelopmentName, resource.r2StagingName, resource.r2ProductionName)}"`,
7522
- ""
7523
- );
7524
- }
7525
- if (resources.queue) {
7526
- lines.push(
7527
- `[[${prefix}queues.producers]]`,
7528
- `binding = "${resource.queueBinding}"`,
7529
- `queue = "${pick(resource.queueDevelopmentName, resource.queueStagingName, resource.queueProductionName)}"`,
7530
- "",
7531
- `[[${prefix}queues.consumers]]`,
7532
- `queue = "${pick(resource.queueDevelopmentName, resource.queueStagingName, resource.queueProductionName)}"`,
7533
- ""
7534
- );
7535
- }
7536
- if (resources.workflow) {
7537
- lines.push(
7538
- `[[${prefix}workflows]]`,
7539
- `binding = "${resource.workflowBinding}"`,
7540
- `name = "${pick(resource.workflowDevelopmentName, resource.workflowStagingName, resource.workflowProductionName)}"`,
7541
- `class_name = "${resource.workflowClassName}"`,
7542
- ""
7543
- );
7544
- }
7545
- if (resources.containerProcessing) {
7546
- lines.push(`# ${envName} container metadata is declared in topolo.app.json and topolo.cloudcontrol.json.`);
7547
- lines.push("");
7548
- }
7549
- }
7550
7333
  function wranglerToml(application, target, resources, profile) {
7551
7334
  const slug = toSlug(application.id);
7552
7335
  const workerName = target === "platform" ? `topolo-${slug}` : application.packageName;
@@ -7766,7 +7549,10 @@ function platformWorkerTs(application, options) {
7766
7549
  " // Auth verification base URL is environment-driven (staging/prod/dev),",
7767
7550
  " // so rt.verifyAuth() targets the correct Auth service per deploy.",
7768
7551
  " authBaseUrl: (env) => env.AUTH_BASE_URL,",
7769
- " authGateway: { enabled: false },",
7552
+ " // The runtime's /api/auth/* gateway serves the app-switcher catalog,",
7553
+ " // preferences, and theme for the shared app shell \u2014 it must run before",
7554
+ " // the app's own auth so unentitled users still get the switcher.",
7555
+ " authGateway: { enabled: true },",
7770
7556
  " actionContracts: topoloActionContracts,",
7771
7557
  "});",
7772
7558
  "",
@@ -7919,8 +7705,13 @@ function developerWorkerTs(application) {
7919
7705
  " const token = authHeader.startsWith('Bearer ') ? authHeader.slice(7) : null;",
7920
7706
  " if (!token) return { ok: false, status: 401, error: 'Bearer token required' };",
7921
7707
  "",
7708
+ " // Fail loudly: defaulting to the production Auth host here would let a",
7709
+ " // misconfigured staging/dev worker validate tokens against prod. The",
7710
+ " // manifest declares AUTH_BASE_URL for every environment.",
7711
+ " if (!env.AUTH_BASE_URL) return { ok: false, status: 500, error: 'AUTH_BASE_URL not configured' };",
7712
+ "",
7922
7713
  " try {",
7923
- " const authBaseUrl = env.AUTH_BASE_URL || 'https://auth.topolo.app';",
7714
+ " const authBaseUrl = env.AUTH_BASE_URL;",
7924
7715
  " const response = await fetch(`${authBaseUrl}/validate`, {",
7925
7716
  " method: 'POST',",
7926
7717
  " headers: {",
@@ -7950,6 +7741,72 @@ function developerWorkerTs(application) {
7950
7741
  ].join("\n");
7951
7742
  }
7952
7743
 
7744
+ // src/commands/app-scaffold-generate.ts
7745
+ import { existsSync as existsSync3, mkdirSync, mkdtempSync, readFileSync as readFileSync4, rmSync, writeFileSync } from "fs";
7746
+ import { spawnSync } from "child_process";
7747
+ import { tmpdir } from "os";
7748
+ import path2 from "path";
7749
+ function resolveGeneratorPath() {
7750
+ const candidates = [
7751
+ // Published CLI: vendored alongside dist/index.js at build time.
7752
+ new URL("./generate-wrangler.mjs", import.meta.url),
7753
+ // In-repo (tests, `pnpm dev`): the platform's canonical copy, which is the
7754
+ // very file the vendor step copies.
7755
+ new URL("../../../../tooling/generate-wrangler.mjs", import.meta.url)
7756
+ ];
7757
+ for (const candidate of candidates) {
7758
+ const p = decodeURIComponent(candidate.pathname);
7759
+ if (existsSync3(p)) return p;
7760
+ }
7761
+ throw new Error(
7762
+ `topolo: could not locate generate-wrangler.mjs. A published CLI vendors it into dist/ at build time; in-repo it is read from topolo-platform/tooling/. Looked in:
7763
+ ${candidates.map((c) => ` ${decodeURIComponent(c.pathname)}`).join("\n")}`
7764
+ );
7765
+ }
7766
+ var GENERATED = {
7767
+ purpose: "GENERATED from topolo.app.json by topolo-platform/tooling/generate-wrangler.mjs \u2014 never hand-edit.",
7768
+ requiredSignals: [
7769
+ "byte-identical to what generate-wrangler emits for this manifest",
7770
+ "every change is made in topolo.app.json, never here"
7771
+ ]
7772
+ };
7773
+ function deriveGeneratedConfigs(files, base) {
7774
+ const generator = resolveGeneratorPath();
7775
+ const root = mkdtempSync(path2.join(tmpdir(), "topolo-scaffold-generate-"));
7776
+ try {
7777
+ const prefix = `${base}/`;
7778
+ for (const file of files) {
7779
+ if (!file.path.startsWith(prefix)) continue;
7780
+ const abs = path2.join(root, file.path.slice(prefix.length));
7781
+ mkdirSync(path2.dirname(abs), { recursive: true });
7782
+ writeFileSync(abs, file.content, "utf8");
7783
+ }
7784
+ const result = spawnSync(
7785
+ process.execPath,
7786
+ [generator, `--manifest=${path2.join(root, "topolo.app.json")}`],
7787
+ { cwd: root, encoding: "utf8" }
7788
+ );
7789
+ if (result.status !== 0) {
7790
+ throw new Error(
7791
+ `topolo: generate-wrangler failed for the scaffolded manifest (exit ${result.status}).
7792
+ ${result.stdout ?? ""}${result.stderr ?? ""}`
7793
+ );
7794
+ }
7795
+ const written = [...(result.stdout ?? "").matchAll(/^\[generate\] wrote (.+)$/gm)].map((m) => (m[1] ?? "").trim()).filter(Boolean);
7796
+ if (!written.length) {
7797
+ throw new Error(`topolo: generate-wrangler wrote no files.
7798
+ ${result.stdout ?? ""}`);
7799
+ }
7800
+ return written.map((rel) => ({
7801
+ path: `${base}/${rel}`,
7802
+ ...GENERATED,
7803
+ content: readFileSync4(path2.join(root, rel), "utf8")
7804
+ }));
7805
+ } finally {
7806
+ rmSync(root, { recursive: true, force: true });
7807
+ }
7808
+ }
7809
+
7953
7810
  // src/commands/app-scaffold-workflows.ts
7954
7811
  function scaffoldCommands(options) {
7955
7812
  const scaffoldId = toSlug(options.id);
@@ -8113,6 +7970,10 @@ function topoloAppManifestJson(application, options) {
8113
7970
  bindings.d1 = [
8114
7971
  {
8115
7972
  binding: resource.d1Binding,
7973
+ development: {
7974
+ databaseName: resource.d1DevelopmentName,
7975
+ databaseId: "provision-after-scaffold"
7976
+ },
8116
7977
  staging: {
8117
7978
  databaseName: resource.d1StagingName,
8118
7979
  databaseId: "provision-after-scaffold"
@@ -8129,26 +7990,29 @@ function topoloAppManifestJson(application, options) {
8129
7990
  bindings.r2 = [
8130
7991
  {
8131
7992
  binding: resource.r2Binding,
7993
+ development: { bucketName: resource.r2DevelopmentName },
8132
7994
  staging: { bucketName: resource.r2StagingName },
8133
7995
  production: { bucketName: resource.r2ProductionName }
8134
7996
  }
8135
7997
  ];
8136
7998
  }
8137
7999
  if (options.resources.queue) {
8138
- bindings.queues = [
8139
- {
8140
- binding: resource.queueBinding,
8141
- staging: { queueName: resource.queueStagingName },
8142
- production: { queueName: resource.queueProductionName },
8143
- consumer: true
8144
- }
8145
- ];
8000
+ const queue = {
8001
+ development: { queue: resource.queueDevelopmentName },
8002
+ staging: { queue: resource.queueStagingName },
8003
+ production: { queue: resource.queueProductionName }
8004
+ };
8005
+ bindings.queues = {
8006
+ producers: [{ binding: resource.queueBinding, ...queue }],
8007
+ consumers: [{ ...queue }]
8008
+ };
8146
8009
  }
8147
8010
  if (options.resources.workflow) {
8148
8011
  bindings.workflows = [
8149
8012
  {
8150
8013
  binding: resource.workflowBinding,
8151
8014
  className: resource.workflowClassName,
8015
+ development: { name: resource.workflowDevelopmentName },
8152
8016
  staging: { name: resource.workflowStagingName },
8153
8017
  production: { name: resource.workflowProductionName }
8154
8018
  }
@@ -8162,53 +8026,67 @@ function topoloAppManifestJson(application, options) {
8162
8026
  compatibilityFlags: ["nodejs_compat"],
8163
8027
  healthcheckPath: "/api/health",
8164
8028
  vars: {
8029
+ // Every environment the manifest declares must be listed. A per-env map
8030
+ // that omits one leaves that env with no value, and both of these fall
8031
+ // back to PRODUCTION when unset (@topolo-io/nexus-client defaults
8032
+ // gatewayUrl to https://nexus.topolo.app) — a staging or dev worker
8033
+ // silently talking to prod.
8165
8034
  AUTH_BASE_URL: {
8035
+ development: "https://auth.topolo.dev",
8166
8036
  staging: "https://auth.stg.topolo.us",
8167
8037
  production: "https://auth.topolo.app"
8168
8038
  },
8169
8039
  NEXUS_GATEWAY_URL: {
8040
+ development: "https://nexus.topolo.dev",
8170
8041
  staging: "https://nexus.stg.topolo.us",
8171
8042
  production: "https://nexus.topolo.app"
8172
- },
8173
- NEXUS_APP_ID: slug
8043
+ }
8044
+ // No NEXUS_APP_ID: generate-wrangler binds APP_ID (the app_id) into every
8045
+ // env, and worker-runtime reads env.APP_ID for Nexus usage attribution,
8046
+ // so a second copy of the same identity would only be a chance to drift.
8047
+ // It previously carried the SLUG, which reads as a working identity while
8048
+ // Nexus attributes by app_id.
8174
8049
  },
8175
8050
  secrets: ["TOPOLO_SERVICE_CLIENT_SECRET"],
8176
8051
  ...Object.keys(bindings).length ? { bindings } : {}
8177
8052
  };
8178
8053
  }
8179
- targets.web = {
8180
- kind: "worker",
8181
- dir: "apps/web",
8182
- wranglerName: `topolo-${slug}`,
8183
- subdomain: slug,
8184
- main: "src/worker.ts",
8185
- workersDev: false,
8186
- assets: {
8187
- directory: "./dist",
8188
- binding: "ASSETS",
8189
- notFoundHandling: "single-page-application",
8190
- runWorkerFirst: ["/api/*"]
8191
- },
8192
- compatibilityFlags: ["nodejs_compat"],
8193
- buildCommand: {
8194
- staging: "cd ../.. && DEPLOY_ENVIRONMENT=staging VITE_TOPOLO_AUTH_URL=https://auth.stg.topolo.us pnpm run build",
8195
- production: "cd ../.. && DEPLOY_ENVIRONMENT=production VITE_TOPOLO_AUTH_URL=https://auth.topolo.app pnpm run build"
8196
- },
8197
- healthcheckPath: options.api ? "/api/health" : "/",
8198
- ...options.api ? {
8199
- bindings: {
8200
- services: [
8201
- {
8202
- binding: "API",
8203
- service: {
8204
- staging: `topolo-${slug}-api-staging`,
8205
- production: `topolo-${slug}-api-production`
8054
+ if (options.browser) {
8055
+ targets.web = {
8056
+ kind: "worker",
8057
+ dir: "apps/web",
8058
+ wranglerName: `topolo-${slug}`,
8059
+ subdomain: slug,
8060
+ main: "src/worker.ts",
8061
+ workersDev: false,
8062
+ assets: {
8063
+ directory: "./dist",
8064
+ binding: "ASSETS",
8065
+ notFoundHandling: "single-page-application",
8066
+ // Only meaningful when there is an API Worker to run first.
8067
+ ...options.api ? { runWorkerFirst: ["/api/*"] } : {}
8068
+ },
8069
+ compatibilityFlags: ["nodejs_compat"],
8070
+ buildCommand: {
8071
+ staging: "cd ../.. && DEPLOY_ENVIRONMENT=staging VITE_TOPOLO_AUTH_URL=https://auth.stg.topolo.us pnpm run build",
8072
+ production: "cd ../.. && DEPLOY_ENVIRONMENT=production VITE_TOPOLO_AUTH_URL=https://auth.topolo.app pnpm run build"
8073
+ },
8074
+ healthcheckPath: options.api ? "/api/health" : "/",
8075
+ ...options.api ? {
8076
+ bindings: {
8077
+ services: [
8078
+ {
8079
+ binding: "API",
8080
+ service: {
8081
+ staging: `topolo-${slug}-api-staging`,
8082
+ production: `topolo-${slug}-api-production`
8083
+ }
8206
8084
  }
8207
- }
8208
- ]
8209
- }
8210
- } : {}
8211
- };
8085
+ ]
8086
+ }
8087
+ } : {}
8088
+ };
8089
+ }
8212
8090
  const actions = options.api ? baselineApiActions() : [];
8213
8091
  return `${json({
8214
8092
  $schema: "./../../topolo-platform/tooling/app-manifest-schema.json",
@@ -8352,263 +8230,6 @@ function baselineApiActions() {
8352
8230
  }
8353
8231
  ];
8354
8232
  }
8355
- function cloudcontrolJson(application, options) {
8356
- const slug = toSlug(application.id);
8357
- const appPath = firstPartyAppPath(application.appDir, options.audience);
8358
- const host = application.productionUrl ? new URL(application.productionUrl).host : `${slug}.topolo.app`;
8359
- const stagingHost = `${slug}.stg.topolo.us`;
8360
- const serviceSlug = application.serviceSlug ?? `topolo-${slug}`;
8361
- const resources = resourceConfig(application, options.profile);
8362
- const resourceBindings = [];
8363
- if (options.resources.d1) {
8364
- resourceBindings.push(
8365
- {
8366
- resource_type: "d1",
8367
- binding: resources.d1Binding,
8368
- environment: "production",
8369
- account_id: CLOUDFLARE_ACCOUNT_ID,
8370
- resource_name: resources.d1ProductionName,
8371
- resource_id: "provision-after-scaffold",
8372
- migrations_dir: "migrations"
8373
- },
8374
- {
8375
- resource_type: "d1",
8376
- binding: resources.d1Binding,
8377
- environment: "staging",
8378
- account_id: STAGING_CLOUDFLARE_ACCOUNT_ID,
8379
- resource_name: resources.d1StagingName,
8380
- resource_id: "provision-after-scaffold",
8381
- migrations_dir: "migrations"
8382
- },
8383
- {
8384
- resource_type: "d1",
8385
- binding: resources.d1Binding,
8386
- environment: "development",
8387
- account_id: DEVELOPMENT_CLOUDFLARE_ACCOUNT_ID,
8388
- resource_name: resources.d1DevelopmentName,
8389
- resource_id: "provision-after-scaffold",
8390
- migrations_dir: "migrations"
8391
- }
8392
- );
8393
- }
8394
- if (options.resources.r2) {
8395
- resourceBindings.push(
8396
- {
8397
- resource_type: "r2",
8398
- binding: resources.r2Binding,
8399
- environment: "production",
8400
- account_id: CLOUDFLARE_ACCOUNT_ID,
8401
- resource_name: resources.r2ProductionName
8402
- },
8403
- {
8404
- resource_type: "r2",
8405
- binding: resources.r2Binding,
8406
- environment: "staging",
8407
- account_id: STAGING_CLOUDFLARE_ACCOUNT_ID,
8408
- resource_name: resources.r2StagingName
8409
- }
8410
- );
8411
- }
8412
- if (options.resources.queue) {
8413
- resourceBindings.push(
8414
- {
8415
- resource_type: "queue",
8416
- binding: resources.queueBinding,
8417
- environment: "production",
8418
- account_id: CLOUDFLARE_ACCOUNT_ID,
8419
- resource_name: resources.queueProductionName
8420
- },
8421
- {
8422
- resource_type: "queue",
8423
- binding: resources.queueBinding,
8424
- environment: "staging",
8425
- account_id: STAGING_CLOUDFLARE_ACCOUNT_ID,
8426
- resource_name: resources.queueStagingName
8427
- }
8428
- );
8429
- }
8430
- if (options.resources.workflow) {
8431
- resourceBindings.push(
8432
- {
8433
- resource_type: "workflow",
8434
- binding: resources.workflowBinding,
8435
- environment: "production",
8436
- account_id: CLOUDFLARE_ACCOUNT_ID,
8437
- resource_name: resources.workflowProductionName,
8438
- class_name: resources.workflowClassName
8439
- },
8440
- {
8441
- resource_type: "workflow",
8442
- binding: resources.workflowBinding,
8443
- environment: "staging",
8444
- account_id: STAGING_CLOUDFLARE_ACCOUNT_ID,
8445
- resource_name: resources.workflowStagingName,
8446
- class_name: resources.workflowClassName
8447
- }
8448
- );
8449
- }
8450
- if (options.resources.containerProcessing) {
8451
- resourceBindings.push(
8452
- {
8453
- resource_type: "container",
8454
- environment: "production",
8455
- account_id: CLOUDFLARE_ACCOUNT_ID,
8456
- resource_name: resources.containerProductionName,
8457
- purpose: "processing"
8458
- },
8459
- {
8460
- resource_type: "container",
8461
- environment: "staging",
8462
- account_id: STAGING_CLOUDFLARE_ACCOUNT_ID,
8463
- resource_name: resources.containerStagingName,
8464
- purpose: "processing"
8465
- }
8466
- );
8467
- }
8468
- const deployTargets = options.api ? [
8469
- {
8470
- kind: "worker",
8471
- environment: "production",
8472
- target_name: `topolo-${slug}-api-production`,
8473
- cloudflare_account_id: CLOUDFLARE_ACCOUNT_ID,
8474
- config_path: "apps/api/wrangler.toml",
8475
- workdir: "apps/api",
8476
- build_command: null,
8477
- output_dir: null,
8478
- production_branch: "main",
8479
- healthcheck_path: "/api/health",
8480
- domains: [],
8481
- routes: [],
8482
- service_slug: serviceSlug
8483
- },
8484
- {
8485
- kind: "worker",
8486
- environment: "staging",
8487
- target_name: `topolo-${slug}-staging`,
8488
- cloudflare_account_id: STAGING_CLOUDFLARE_ACCOUNT_ID,
8489
- config_path: "apps/web/wrangler.toml",
8490
- workdir: "apps/web",
8491
- build_command: "cd ../.. && DEPLOY_ENVIRONMENT=staging VITE_TOPOLO_AUTH_URL=https://auth.stg.topolo.us pnpm run build",
8492
- output_dir: null,
8493
- production_branch: "staging",
8494
- healthcheck_path: "/api/health",
8495
- domains: [stagingHost],
8496
- routes: [stagingHost],
8497
- api_url: `https://${stagingHost}`,
8498
- service_slug: serviceSlug,
8499
- sdk_app_id: application.id,
8500
- agent_callable: true
8501
- },
8502
- {
8503
- kind: "worker",
8504
- environment: "staging",
8505
- target_name: `topolo-${slug}-api-staging`,
8506
- cloudflare_account_id: STAGING_CLOUDFLARE_ACCOUNT_ID,
8507
- config_path: "apps/api/wrangler.toml",
8508
- workdir: "apps/api",
8509
- build_command: null,
8510
- output_dir: null,
8511
- production_branch: "staging",
8512
- healthcheck_path: "/api/health",
8513
- domains: [],
8514
- routes: [],
8515
- service_slug: serviceSlug
8516
- },
8517
- {
8518
- kind: "worker",
8519
- environment: "production",
8520
- target_name: `topolo-${slug}-production`,
8521
- cloudflare_account_id: CLOUDFLARE_ACCOUNT_ID,
8522
- config_path: "apps/web/wrangler.toml",
8523
- workdir: "apps/web",
8524
- build_command: "cd ../.. && DEPLOY_ENVIRONMENT=production VITE_TOPOLO_AUTH_URL=https://auth.topolo.app pnpm run build",
8525
- output_dir: null,
8526
- production_branch: "main",
8527
- healthcheck_path: "/api/health",
8528
- domains: [host],
8529
- routes: [host],
8530
- api_url: application.productionUrl,
8531
- service_slug: serviceSlug,
8532
- sdk_app_id: application.id,
8533
- agent_callable: true
8534
- }
8535
- ] : [
8536
- {
8537
- kind: "worker",
8538
- environment: "production",
8539
- target_name: `topolo-${slug}`,
8540
- cloudflare_account_id: CLOUDFLARE_ACCOUNT_ID,
8541
- config_path: "wrangler.toml",
8542
- build_command: "npm run build",
8543
- output_dir: "dist",
8544
- production_branch: "main",
8545
- healthcheck_path: "/",
8546
- domains: [host],
8547
- routes: [host],
8548
- api_url: application.productionUrl,
8549
- agent_callable: false
8550
- }
8551
- ];
8552
- return `${json({
8553
- name: `topolo-${slug}`,
8554
- description: `Canonical documentation for ${toTitle(application.name)} lives in system-apps/TopoloDocs.`,
8555
- deploy_method: "wrangler",
8556
- ci_provider: "github_actions",
8557
- repo_identity: {
8558
- provider: "github",
8559
- owner: "Topolo-io",
8560
- repo: application.appDir,
8561
- remote_url: `git@github.com:Topolo-io/${application.appDir}.git`,
8562
- default_branch: "main"
8563
- },
8564
- environments: application.productionUrl ? [
8565
- {
8566
- name: "production",
8567
- url: application.productionUrl,
8568
- branch: "main",
8569
- status: "unknown"
8570
- },
8571
- {
8572
- name: "staging",
8573
- url: `https://${stagingHost}`,
8574
- branch: "main",
8575
- status: "unknown"
8576
- }
8577
- ] : [],
8578
- deploy_targets: deployTargets,
8579
- resource_bindings: resourceBindings,
8580
- ci_workflows: [
8581
- {
8582
- provider: "github_actions",
8583
- workflow_name: "topolo-conformance.yml",
8584
- path: ".github/workflows/topolo-conformance.yml",
8585
- branch: null,
8586
- required: true
8587
- }
8588
- ],
8589
- ci_targets: [
8590
- {
8591
- name: "typecheck",
8592
- command: "pnpm run typecheck",
8593
- required: true
8594
- },
8595
- {
8596
- name: "test",
8597
- command: "pnpm run test",
8598
- required: true
8599
- },
8600
- {
8601
- name: "build",
8602
- command: "pnpm run build",
8603
- required: true
8604
- }
8605
- ],
8606
- tags: ["application", "cloudflare", "typescript", ...hasScaffoldResources(options.resources) ? ["resource-backed"] : []],
8607
- source_paths: [appPath],
8608
- cloudflare_account_id: CLOUDFLARE_ACCOUNT_ID
8609
- })}
8610
- `;
8611
- }
8612
8233
  function systemJson(application, options) {
8613
8234
  const slug = toSlug(application.id);
8614
8235
  const suiteLabel = options.audience === "personal" ? "personal" : "platform";
@@ -9686,6 +9307,7 @@ function scaffoldFiles(options) {
9686
9307
  ],
9687
9308
  content: topoloAppManifestJson(application, {
9688
9309
  api: options.api,
9310
+ browser: options.browser,
9689
9311
  resources: options.payload.resources,
9690
9312
  profile: options.payload.profile,
9691
9313
  payload: options.payload
@@ -9711,20 +9333,6 @@ function scaffoldFiles(options) {
9711
9333
  content: topoloActionContractsDmts()
9712
9334
  }
9713
9335
  ] : [],
9714
- {
9715
- path: `${base}/topolo.cloudcontrol.json`,
9716
- purpose: "CloudControl source of truth for deploy targets, routes, bindings, and CI metadata.",
9717
- requiredSignals: [
9718
- "production URLs and Cloudflare targets are explicit",
9719
- "resource bindings and required secrets are declared without secret values"
9720
- ],
9721
- content: cloudcontrolJson(application, {
9722
- api: options.api,
9723
- audience: options.payload.audience,
9724
- profile: options.payload.profile,
9725
- resources: options.payload.resources
9726
- })
9727
- },
9728
9336
  {
9729
9337
  path: `${base}/.github/workflows/topolo-conformance.yml`,
9730
9338
  purpose: "PR/push gate that runs the local Topolo app audit with docs evidence.",
@@ -9933,15 +9541,6 @@ export default defineConfig({
9933
9541
  "only declares an API service binding for API-backed scaffolds"
9934
9542
  ],
9935
9543
  content: webWorkerTs(application, options.api)
9936
- },
9937
- {
9938
- path: `${browserBase}/wrangler.toml`,
9939
- purpose: "Cloudflare Worker static-assets configuration for the browser surface.",
9940
- requiredSignals: [
9941
- "serves Vite assets through a Worker with assets",
9942
- "only binds an API Worker when the scaffold includes one"
9943
- ],
9944
- content: platformWebWranglerToml(application, options.api)
9945
9544
  }
9946
9545
  );
9947
9546
  }
@@ -9949,15 +9548,15 @@ export default defineConfig({
9949
9548
  }
9950
9549
  if (options.api) {
9951
9550
  files.push(
9952
- {
9551
+ ...target === "platform" ? [] : [{
9953
9552
  path: `${apiBase}/wrangler.toml`,
9954
9553
  purpose: "Cloudflare Worker runtime configuration.",
9955
9554
  requiredSignals: [
9956
- "bindings match topolo.cloudcontrol.json or topolo.app.json",
9555
+ "bindings match the declared resources",
9957
9556
  "secrets are referenced by name only"
9958
9557
  ],
9959
- content: target === "platform" ? platformApiWranglerToml(application, options.payload.resources, options.payload.profile) : wranglerToml(application, target, options.payload.resources, options.payload.profile)
9960
- },
9558
+ content: wranglerToml(application, target, options.payload.resources, options.payload.profile)
9559
+ }],
9961
9560
  {
9962
9561
  path: `${apiBase}/src/worker.ts`,
9963
9562
  purpose: "Protected service boundary.",
@@ -10025,6 +9624,9 @@ export default defineConfig({
10025
9624
  }
10026
9625
  }
10027
9626
  }
9627
+ if (target === "platform") {
9628
+ files.push(...deriveGeneratedConfigs(files, base));
9629
+ }
10028
9630
  return files;
10029
9631
  }
10030
9632
 
@@ -10271,7 +9873,7 @@ import { join as join3 } from "path";
10271
9873
 
10272
9874
  // src/commands/app-reconcile.ts
10273
9875
  import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
10274
- import path2 from "path";
9876
+ import path3 from "path";
10275
9877
  import { createInterface as createInterface2 } from "readline/promises";
10276
9878
  import { stdin as input2, stdout as output } from "process";
10277
9879
  function record(value) {
@@ -10330,7 +9932,7 @@ function titleFromSlug(value) {
10330
9932
  return value.replace(/^topolo[-_]/, "").split(/[-_]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
10331
9933
  }
10332
9934
  async function completeApplicationRegistrationInteractively(options) {
10333
- const manifestPath = path2.resolve(options.manifestPath || "topolo.app.json");
9935
+ const manifestPath = path3.resolve(options.manifestPath || "topolo.app.json");
10334
9936
  const manifest = JSON.parse(await readFile3(manifestPath, "utf8"));
10335
9937
  const existing = record(manifest.registration) || {};
10336
9938
  const appId = String(manifest.appId || manifest.app_id || "").trim();
@@ -10410,7 +10012,7 @@ async function completeApplicationRegistrationInteractively(options) {
10410
10012
  }
10411
10013
  }
10412
10014
  async function loadApplicationReconcileRequest(options) {
10413
- const manifestPath = path2.resolve(options.manifestPath || "topolo.app.json");
10015
+ const manifestPath = path3.resolve(options.manifestPath || "topolo.app.json");
10414
10016
  const raw = await readFile3(manifestPath, "utf8");
10415
10017
  const manifest = JSON.parse(raw);
10416
10018
  assertRegistration(manifest.registration);
@@ -10796,8 +10398,8 @@ function applicationAuditPayload(application, options = {}) {
10796
10398
  }
10797
10399
  function recordApplicationEvidence(application, options = {}) {
10798
10400
  const appId = resolveApplication(application).id;
10799
- const root = path3.resolve(options.appRoot ?? process.cwd());
10800
- if (!existsSync3(root)) {
10401
+ const root = path4.resolve(options.appRoot ?? process.cwd());
10402
+ if (!existsSync4(root)) {
10801
10403
  throw new Error(`Application root does not exist: ${root}`);
10802
10404
  }
10803
10405
  let commit = options.commit;
@@ -10808,12 +10410,12 @@ function recordApplicationEvidence(application, options = {}) {
10808
10410
  commit = void 0;
10809
10411
  }
10810
10412
  }
10811
- const evidenceDir = path3.join(root, ".topolo");
10812
- const evidencePath = path3.join(evidenceDir, "evidence.json");
10413
+ const evidenceDir = path4.join(root, ".topolo");
10414
+ const evidencePath = path4.join(evidenceDir, "evidence.json");
10813
10415
  let before = null;
10814
- if (existsSync3(evidencePath)) {
10416
+ if (existsSync4(evidencePath)) {
10815
10417
  try {
10816
- const raw = JSON.parse(readFileSync4(evidencePath, "utf8"));
10418
+ const raw = JSON.parse(readFileSync5(evidencePath, "utf8"));
10817
10419
  if (raw && typeof raw === "object" && raw.version === 1) {
10818
10420
  before = raw;
10819
10421
  }
@@ -10851,8 +10453,8 @@ function recordApplicationEvidence(application, options = {}) {
10851
10453
  "record-evidence: pass at least one of --build, --typecheck, --test, --docs-validate, --smoke <url>"
10852
10454
  );
10853
10455
  }
10854
- mkdirSync(evidenceDir, { recursive: true });
10855
- writeFileSync(evidencePath, JSON.stringify(after, null, 2) + "\n");
10456
+ mkdirSync2(evidenceDir, { recursive: true });
10457
+ writeFileSync2(evidencePath, JSON.stringify(after, null, 2) + "\n");
10856
10458
  return { appId, evidencePath, before, after, updatedFields };
10857
10459
  }
10858
10460
  function formatEvidence(result) {
@@ -11108,8 +10710,8 @@ function statusesAtOrWorse(threshold) {
11108
10710
  }
11109
10711
 
11110
10712
  // src/commands/platform.ts
11111
- import { readdirSync as readdirSync2, readFileSync as readFileSync5, statSync as statSync2 } from "fs";
11112
- import path4 from "path";
10713
+ import { readdirSync as readdirSync2, readFileSync as readFileSync6, statSync as statSync2 } from "fs";
10714
+ import path5 from "path";
11113
10715
  function registerPlatformCommand(program2) {
11114
10716
  const platform3 = program2.command("platform").description("Internal Topolo first-party platform and personal app commands");
11115
10717
  const platformApps = platform3.command("apps").description("Scaffold and operate first-party Topolo platform and personal applications");
@@ -11227,12 +10829,12 @@ var SKIPPED_SCAN_DIRS = /* @__PURE__ */ new Set([
11227
10829
  "output"
11228
10830
  ]);
11229
10831
  function scanStagingProductionUrls(rootInput, allowPatterns) {
11230
- const root = path4.resolve(rootInput);
10832
+ const root = path5.resolve(rootInput);
11231
10833
  const allowlist = allowPatterns.map((pattern) => new RegExp(pattern));
11232
10834
  const violations = [];
11233
10835
  function shouldSkipFile(filePath) {
11234
- const basename = path4.basename(filePath);
11235
- const relativePath = path4.relative(root, filePath);
10836
+ const basename = path5.basename(filePath);
10837
+ const relativePath = path5.relative(root, filePath);
11236
10838
  if (basename === "package-lock.json" || basename === "pnpm-lock.yaml" || basename === "yarn.lock") return true;
11237
10839
  if (basename === "topolo.cloudcontrol.json") return true;
11238
10840
  if (basename === "wrangler.toml" || basename === "wrangler.jsonc") return true;
@@ -11241,15 +10843,15 @@ function scanStagingProductionUrls(rootInput, allowPatterns) {
11241
10843
  if (basename.endsWith(".generated.ts")) return true;
11242
10844
  if (basename.endsWith(".test.ts") || basename.endsWith(".test.tsx") || basename.endsWith(".spec.ts")) return true;
11243
10845
  if (basename === ".env.production") return true;
11244
- if (relativePath === path4.join("src", "environment.ts") || relativePath === path4.join("src", "environmentUrls.ts") || relativePath.endsWith(path4.join("src", "environment.ts")) || relativePath.endsWith(path4.join("src", "lib", "environment.ts")) || relativePath === path4.join("packages", "topolo-ui-kit", "src", "environmentUrls.ts")) return true;
11245
- if (relativePath.includes(`${path4.sep}test${path4.sep}`) || relativePath.includes(`${path4.sep}tests${path4.sep}`) || relativePath.includes(`${path4.sep}e2e${path4.sep}`)) return true;
11246
- if (relativePath.startsWith(`scripts${path4.sep}`) || relativePath.includes(`${path4.sep}scripts${path4.sep}`)) return true;
11247
- return !RUNTIME_EXTENSIONS.has(path4.extname(filePath));
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));
11248
10850
  }
11249
10851
  function scanFile(filePath) {
11250
10852
  if (shouldSkipFile(filePath)) return;
11251
- const relativePath = path4.relative(root, filePath);
11252
- const content = readFileSync5(filePath, "utf8");
10853
+ const relativePath = path5.relative(root, filePath);
10854
+ const content = readFileSync6(filePath, "utf8");
11253
10855
  const lines = content.split(/\r?\n/);
11254
10856
  lines.forEach((line, index) => {
11255
10857
  const matches = [...line.matchAll(PRODUCTION_TOPOLO_URL_PATTERN)].map((match) => match[0]);
@@ -11263,7 +10865,7 @@ function scanStagingProductionUrls(rootInput, allowPatterns) {
11263
10865
  }
11264
10866
  function walk(dir) {
11265
10867
  for (const entry of readdirSync2(dir, { withFileTypes: true })) {
11266
- const fullPath = path4.join(dir, entry.name);
10868
+ const fullPath = path5.join(dir, entry.name);
11267
10869
  if (entry.isDirectory()) {
11268
10870
  if (!SKIPPED_SCAN_DIRS.has(entry.name)) walk(fullPath);
11269
10871
  continue;
@@ -11431,7 +11033,7 @@ var DEFAULT_SEED_BASE_URL = "https://seed.stg.topolo.us";
11431
11033
  function seedBaseUrl() {
11432
11034
  return process.env.TOPOLO_SEED_URL || DEFAULT_SEED_BASE_URL;
11433
11035
  }
11434
- async function authedFetch(path5, init = {}) {
11036
+ async function authedFetch(path6, init = {}) {
11435
11037
  const credential = await refreshCredentialIfNeeded("staging");
11436
11038
  if (!credential) {
11437
11039
  throw new Error("No staging credential configured. Run `topolo auth login --env staging` or set TOPOLO_STAGING_API_KEY.");
@@ -11441,7 +11043,7 @@ async function authedFetch(path5, init = {}) {
11441
11043
  else headers.set("Authorization", `Bearer ${credential.accessToken}`);
11442
11044
  headers.set("X-Topolo-Client", `topolo-cli/${CLI_VERSION}`);
11443
11045
  if (init.body && !headers.has("Content-Type")) headers.set("Content-Type", "application/json");
11444
- const response = await fetch(`${seedBaseUrl()}${path5}`, { ...init, headers });
11046
+ const response = await fetch(`${seedBaseUrl()}${path6}`, { ...init, headers });
11445
11047
  const text = await response.text();
11446
11048
  let body;
11447
11049
  try {
@@ -11718,7 +11320,7 @@ function createOAuthClientPayload(options) {
11718
11320
  logo_url: options.logoUrl
11719
11321
  };
11720
11322
  }
11721
- async function authFetchJson(path5, init = {}) {
11323
+ async function authFetchJson(path6, init = {}) {
11722
11324
  const environment = selectedEnvironment();
11723
11325
  const credential = await refreshCredentialIfNeeded(environment);
11724
11326
  if (!credential) {
@@ -11740,7 +11342,7 @@ async function authFetchJson(path5, init = {}) {
11740
11342
  throw err;
11741
11343
  }
11742
11344
  headers.set("Authorization", `Bearer ${credential.accessToken}`);
11743
- const response = await fetch(`${authBaseUrl}${path5}`, { ...init, headers });
11345
+ const response = await fetch(`${authBaseUrl}${path6}`, { ...init, headers });
11744
11346
  const text = await response.text();
11745
11347
  const payload = text ? JSON.parse(text) : {};
11746
11348
  if (!response.ok || payload.success === false) {
@@ -11939,8 +11541,8 @@ function formatCheckResult(result) {
11939
11541
  }
11940
11542
 
11941
11543
  // src/commands/skills.ts
11942
- import { spawnSync } from "child_process";
11943
- import { existsSync as existsSync4 } from "fs";
11544
+ import { spawnSync as spawnSync2 } from "child_process";
11545
+ import { existsSync as existsSync5 } from "fs";
11944
11546
  import { mkdir as mkdir3, copyFile, readFile as readFile5, writeFile as writeFile5, rm } from "fs/promises";
11945
11547
  import { homedir as homedir2 } from "os";
11946
11548
  import { dirname as dirname4, join as join5, resolve as resolve3 } from "path";
@@ -11952,19 +11554,19 @@ function skillsSourceDir() {
11952
11554
  const distCandidate = resolve3(here, "../skills");
11953
11555
  const srcCandidate = resolve3(here, "../../skills");
11954
11556
  for (const candidate of [distCandidate, srcCandidate]) {
11955
- if (existsSync4(join5(candidate, "topolo", "SKILL.md"))) return candidate;
11557
+ if (existsSync5(join5(candidate, "topolo", "SKILL.md"))) return candidate;
11956
11558
  }
11957
11559
  return distCandidate;
11958
11560
  }
11959
11561
  function resolveMcpCommand() {
11960
- const probe = spawnSync(process.platform === "win32" ? "where" : "which", ["topolo-mcp"], {
11562
+ const probe = spawnSync2(process.platform === "win32" ? "where" : "which", ["topolo-mcp"], {
11961
11563
  stdio: "ignore"
11962
11564
  });
11963
11565
  if (probe.status === 0) return { command: "topolo-mcp", args: [] };
11964
11566
  return { command: "npx", args: ["-y", "@topolo/mcp"] };
11965
11567
  }
11966
11568
  function agentBinaryExists(bin) {
11967
- const probe = spawnSync(process.platform === "win32" ? "where" : "which", [bin], {
11569
+ const probe = spawnSync2(process.platform === "win32" ? "where" : "which", [bin], {
11968
11570
  stdio: "ignore"
11969
11571
  });
11970
11572
  return probe.status === 0;
@@ -11985,9 +11587,9 @@ async function installSkillFile(sourceDir, targetDir) {
11985
11587
  function registerViaOfficialCli(cli, mcp, agentName) {
11986
11588
  if (!agentBinaryExists(cli)) return false;
11987
11589
  const scopeArgs = cli === "claude" ? ["-s", "user"] : [];
11988
- spawnSync(cli, ["mcp", "remove", "topolo", ...scopeArgs], { stdio: "ignore" });
11590
+ spawnSync2(cli, ["mcp", "remove", "topolo", ...scopeArgs], { stdio: "ignore" });
11989
11591
  const envArgs = cli === "claude" ? ["-e", `TOPOLO_AGENT_NAME=${agentName}`] : ["--env", `TOPOLO_AGENT_NAME=${agentName}`];
11990
- const result = spawnSync(
11592
+ const result = spawnSync2(
11991
11593
  cli,
11992
11594
  ["mcp", "add", "topolo", ...scopeArgs, ...envArgs, "--", mcp.command, ...mcp.args],
11993
11595
  { stdio: "ignore" }
@@ -11995,11 +11597,11 @@ function registerViaOfficialCli(cli, mcp, agentName) {
11995
11597
  return result.status === 0;
11996
11598
  }
11997
11599
  async function registerClaudeViaConfig(mcp) {
11998
- const path5 = join5(homedir2(), ".claude.json");
11600
+ const path6 = join5(homedir2(), ".claude.json");
11999
11601
  let config = {};
12000
- if (existsSync4(path5)) {
11602
+ if (existsSync5(path6)) {
12001
11603
  try {
12002
- config = JSON.parse(await readFile5(path5, "utf8"));
11604
+ config = JSON.parse(await readFile5(path6, "utf8"));
12003
11605
  } catch {
12004
11606
  config = {};
12005
11607
  }
@@ -12012,13 +11614,13 @@ async function registerClaudeViaConfig(mcp) {
12012
11614
  env: { TOPOLO_AGENT_NAME: AGENT_NAME_FOR.claude }
12013
11615
  };
12014
11616
  config.mcpServers = servers;
12015
- await writeFile5(path5, `${JSON.stringify(config, null, 2)}
11617
+ await writeFile5(path6, `${JSON.stringify(config, null, 2)}
12016
11618
  `, "utf8");
12017
11619
  }
12018
11620
  async function registerCodexViaConfig(mcp) {
12019
- const path5 = join5(homedir2(), ".codex", "config.toml");
11621
+ const path6 = join5(homedir2(), ".codex", "config.toml");
12020
11622
  let text = "";
12021
- if (existsSync4(path5)) text = await readFile5(path5, "utf8");
11623
+ if (existsSync5(path6)) text = await readFile5(path6, "utf8");
12022
11624
  text = text.replace(
12023
11625
  /(^|\n)\[mcp_servers\.topolo(?:\.[a-z]+)?\][^\[]*/g,
12024
11626
  (match) => match.startsWith("\n") ? "\n" : ""
@@ -12034,8 +11636,8 @@ command = "${mcp.command}"${argsToml}
12034
11636
  [mcp_servers.topolo.env]
12035
11637
  TOPOLO_AGENT_NAME = "${AGENT_NAME_FOR.codex}"
12036
11638
  `;
12037
- await mkdir3(dirname4(path5), { recursive: true });
12038
- await writeFile5(path5, `${text}${block}`, "utf8");
11639
+ await mkdir3(dirname4(path6), { recursive: true });
11640
+ await writeFile5(path6, `${text}${block}`, "utf8");
12039
11641
  }
12040
11642
  async function installForAgent(agent, sourceDir, mcp, opts) {
12041
11643
  const skillDir = agent === "claude" ? claudeSkillDir(opts.projectDir) : codexSkillDir();
@@ -12073,7 +11675,7 @@ function registerSkillsCommand(program2) {
12073
11675
  const mode = resolveOutputMode(opts.json);
12074
11676
  try {
12075
11677
  const sourceDir = skillsSourceDir();
12076
- if (!existsSync4(join5(sourceDir, "topolo", "SKILL.md"))) {
11678
+ if (!existsSync5(join5(sourceDir, "topolo", "SKILL.md"))) {
12077
11679
  throw new Error(
12078
11680
  "Bundled skill file not found. Reinstall the CLI: npm install -g @topolo/cli"
12079
11681
  );
@@ -12102,7 +11704,7 @@ function registerSkillsCommand(program2) {
12102
11704
  const skillDir = agent === "claude" ? claudeSkillDir() : codexSkillDir();
12103
11705
  return {
12104
11706
  agent,
12105
- skillInstalled: existsSync4(join5(skillDir, "SKILL.md")),
11707
+ skillInstalled: existsSync5(join5(skillDir, "SKILL.md")),
12106
11708
  skillPath: join5(skillDir, "SKILL.md"),
12107
11709
  cliAvailable: agentBinaryExists(agent)
12108
11710
  };
@@ -12119,10 +11721,10 @@ function registerSkillsCommand(program2) {
12119
11721
  for (const agent of resolveTargets(opts)) {
12120
11722
  const skillDir = agent === "claude" ? claudeSkillDir() : codexSkillDir();
12121
11723
  const skillFile = join5(skillDir, "SKILL.md");
12122
- const skillRemoved = existsSync4(skillFile);
11724
+ const skillRemoved = existsSync5(skillFile);
12123
11725
  if (skillRemoved) await rm(skillFile, { force: true });
12124
11726
  if (agentBinaryExists(agent)) {
12125
- spawnSync(agent, ["mcp", "remove", "topolo"], { stdio: "ignore" });
11727
+ spawnSync2(agent, ["mcp", "remove", "topolo"], { stdio: "ignore" });
12126
11728
  }
12127
11729
  removed.push({ agent, skillRemoved });
12128
11730
  }