@topolo/cli 0.6.4 → 0.6.6

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
@@ -37,19 +37,54 @@ var TopoloSdkError = class extends Error {
37
37
  }
38
38
  };
39
39
  var TopoloAuthError = class extends TopoloSdkError {
40
- constructor(message, code = "auth_error") {
40
+ status;
41
+ body;
42
+ service;
43
+ path;
44
+ requestId;
45
+ retryAfter;
46
+ constructor(message, code = "auth_error", options = {}) {
41
47
  super(code, message);
42
48
  this.name = "TopoloAuthError";
49
+ this.status = options.status ?? 401;
50
+ this.body = options.body;
51
+ this.service = options.service;
52
+ this.path = options.path;
53
+ this.requestId = options.requestId;
54
+ this.retryAfter = options.retryAfter;
43
55
  }
44
56
  };
45
57
  var TopoloPermissionError = class extends TopoloSdkError {
46
58
  required;
47
- constructor(message, required) {
59
+ status;
60
+ body;
61
+ service;
62
+ path;
63
+ requestId;
64
+ retryAfter;
65
+ constructor(message, required, options = {}) {
48
66
  super("permission_denied", message);
49
67
  this.name = "TopoloPermissionError";
50
68
  this.required = required;
69
+ this.status = options.status ?? 403;
70
+ this.body = options.body;
71
+ this.service = options.service;
72
+ this.path = options.path;
73
+ this.requestId = options.requestId;
74
+ this.retryAfter = options.retryAfter;
51
75
  }
52
76
  };
77
+ function extractHttpErrorCode(body) {
78
+ if (!body || typeof body !== "object" || Array.isArray(body)) return "http_error";
79
+ const record4 = body;
80
+ if (typeof record4.code === "string" && record4.code.trim()) return record4.code.trim();
81
+ const nested = record4.error;
82
+ if (nested && typeof nested === "object" && !Array.isArray(nested)) {
83
+ const code = nested.code;
84
+ if (typeof code === "string" && code.trim()) return code.trim();
85
+ }
86
+ return "http_error";
87
+ }
53
88
  var TopoloConfirmationRequiredError = class extends TopoloSdkError {
54
89
  constructor(message) {
55
90
  super("confirmation_required", message);
@@ -61,19 +96,24 @@ var TopoloHttpError = class extends TopoloSdkError {
61
96
  body;
62
97
  service;
63
98
  path;
64
- constructor(service, path8, status, body, message) {
65
- super("http_error", message ?? `HTTP ${status} from ${service}${path8}`);
99
+ requestId;
100
+ retryAfter;
101
+ constructor(service, path9, status, body, message, options = {}) {
102
+ super(extractHttpErrorCode(body), message ?? `HTTP ${status} from ${service}${path9}`);
66
103
  this.name = "TopoloHttpError";
67
104
  this.status = status;
68
105
  this.body = body;
69
106
  this.service = service;
70
- this.path = path8;
107
+ this.path = path9;
108
+ this.requestId = options.requestId;
109
+ this.retryAfter = options.retryAfter;
71
110
  }
72
111
  };
73
112
  var APP_API_URLS = {
74
113
  agent: "https://agent.topolo.app",
75
114
  auth: "https://auth.topolo.app",
76
115
  backup: "https://backup.topolo.app",
116
+ blog: "https://blog.topolo.app",
77
117
  books: "https://books.topolo.app",
78
118
  brand: "https://brand.topolo.app",
79
119
  bugfix: "https://bugfix.topolo.app",
@@ -86,6 +126,7 @@ var APP_API_URLS = {
86
126
  compose: "https://compose.topolo.app",
87
127
  consent: "https://consent.topolo.app",
88
128
  crm: "https://crm.topolo.app",
129
+ design: "https://design.topolo.app",
89
130
  developers: "https://developers.topolo.app",
90
131
  director: "https://director.topolo.app",
91
132
  feed: "https://feed.topolo.app",
@@ -169,7 +210,9 @@ var APPLICATIONS = {
169
210
  "domains": [
170
211
  "agent.topolo.app"
171
212
  ],
172
- "routes": [],
213
+ "routes": [
214
+ "agent.topolo.app"
215
+ ],
173
216
  "appId": "agent"
174
217
  },
175
218
  {
@@ -230,6 +273,38 @@ var APPLICATIONS = {
230
273
  }
231
274
  ]
232
275
  },
276
+ "blog": {
277
+ "id": "blog",
278
+ "appDir": "TopoloBlog",
279
+ "name": "topolo-blog",
280
+ "packageName": "topolo-blog",
281
+ "productionUrl": "https://blog.topolo.app",
282
+ "services": [
283
+ "blog"
284
+ ],
285
+ "deployTargets": [
286
+ {
287
+ "name": "topolo-blog",
288
+ "kind": "worker",
289
+ "apiUrl": "https://blog.topolo.app",
290
+ "domains": [
291
+ "blog.topolo.app"
292
+ ],
293
+ "routes": [
294
+ "blog.topolo.app"
295
+ ],
296
+ "appId": "blog"
297
+ },
298
+ {
299
+ "name": "topolo-blog-api",
300
+ "kind": "worker",
301
+ "apiUrl": null,
302
+ "domains": [],
303
+ "routes": [],
304
+ "appId": null
305
+ }
306
+ ]
307
+ },
233
308
  "books": {
234
309
  "id": "books",
235
310
  "appDir": "TopoloBooks",
@@ -247,7 +322,9 @@ var APPLICATIONS = {
247
322
  "domains": [
248
323
  "books.topolo.app"
249
324
  ],
250
- "routes": [],
325
+ "routes": [
326
+ "books.topolo.app"
327
+ ],
251
328
  "appId": "books"
252
329
  },
253
330
  {
@@ -397,7 +474,9 @@ var APPLICATIONS = {
397
474
  "domains": [
398
475
  "campaigns.topolo.app"
399
476
  ],
400
- "routes": [],
477
+ "routes": [
478
+ "campaigns.topolo.app"
479
+ ],
401
480
  "appId": "campaigns"
402
481
  },
403
482
  {
@@ -607,11 +686,45 @@ var APPLICATIONS = {
607
686
  "domains": [
608
687
  "crm.topolo.app"
609
688
  ],
610
- "routes": [],
689
+ "routes": [
690
+ "crm.topolo.app"
691
+ ],
611
692
  "appId": "crm"
612
693
  }
613
694
  ]
614
695
  },
696
+ "design": {
697
+ "id": "design",
698
+ "appDir": "TopoloDesign",
699
+ "name": "topolo-design",
700
+ "packageName": "@topolo-io/design",
701
+ "productionUrl": "https://design.topolo.app",
702
+ "services": [
703
+ "design"
704
+ ],
705
+ "deployTargets": [
706
+ {
707
+ "name": "topolo-design",
708
+ "kind": "worker",
709
+ "apiUrl": "https://design.topolo.app",
710
+ "domains": [
711
+ "design.topolo.app"
712
+ ],
713
+ "routes": [
714
+ "design.topolo.app"
715
+ ],
716
+ "appId": "design"
717
+ },
718
+ {
719
+ "name": "topolo-design-api",
720
+ "kind": "worker",
721
+ "apiUrl": null,
722
+ "domains": [],
723
+ "routes": [],
724
+ "appId": null
725
+ }
726
+ ]
727
+ },
615
728
  "developers": {
616
729
  "id": "developers",
617
730
  "appDir": "TopoloDevelopers",
@@ -835,7 +948,9 @@ var APPLICATIONS = {
835
948
  "domains": [
836
949
  "forms.topolo.app"
837
950
  ],
838
- "routes": [],
951
+ "routes": [
952
+ "forms.topolo.app"
953
+ ],
839
954
  "appId": "forms"
840
955
  },
841
956
  {
@@ -897,7 +1012,9 @@ var APPLICATIONS = {
897
1012
  "domains": [
898
1013
  "insights.topolo.app"
899
1014
  ],
900
- "routes": [],
1015
+ "routes": [
1016
+ "insights.topolo.app"
1017
+ ],
901
1018
  "appId": "insights"
902
1019
  },
903
1020
  {
@@ -1281,7 +1398,9 @@ var APPLICATIONS = {
1281
1398
  "domains": [
1282
1399
  "pay.topolo.app"
1283
1400
  ],
1284
- "routes": [],
1401
+ "routes": [
1402
+ "pay.topolo.app"
1403
+ ],
1285
1404
  "appId": "pay"
1286
1405
  },
1287
1406
  {
@@ -1366,7 +1485,9 @@ var APPLICATIONS = {
1366
1485
  "domains": [
1367
1486
  "quro.topolo.app"
1368
1487
  ],
1369
- "routes": [],
1488
+ "routes": [
1489
+ "quro.topolo.app"
1490
+ ],
1370
1491
  "appId": null
1371
1492
  }
1372
1493
  ]
@@ -1650,7 +1771,9 @@ var APPLICATIONS = {
1650
1771
  "domains": [
1651
1772
  "survey.topolo.app"
1652
1773
  ],
1653
- "routes": [],
1774
+ "routes": [
1775
+ "survey.topolo.app"
1776
+ ],
1654
1777
  "appId": "survey"
1655
1778
  },
1656
1779
  {
@@ -2232,17 +2355,17 @@ function validateTopoloActionInput(schema, input3) {
2232
2355
  validateNode(schema, input3, "$", schema, errors, 0);
2233
2356
  return { valid: errors.length === 0, errors };
2234
2357
  }
2235
- function inspectSchema(schema, path8, root, depth) {
2236
- if (depth > 128) return { path: path8, code: "schema_depth", message: "Schema recursion exceeded 128 levels." };
2358
+ function inspectSchema(schema, path9, root, depth) {
2359
+ if (depth > 128) return { path: path9, code: "schema_depth", message: "Schema recursion exceeded 128 levels." };
2237
2360
  const unsupportedKeyword = Object.keys(schema).find((keyword) => !SUPPORTED_SCHEMA_KEYWORDS.has(keyword));
2238
2361
  if (unsupportedKeyword) {
2239
- return { path: path8, code: "unsupported_schema_keyword", message: `Action schema uses unsupported keyword ${unsupportedKeyword}.` };
2362
+ return { path: path9, code: "unsupported_schema_keyword", message: `Action schema uses unsupported keyword ${unsupportedKeyword}.` };
2240
2363
  }
2241
2364
  if (typeof schema.format === "string" && !["uri", "email", "date-time"].includes(schema.format)) {
2242
- return { path: path8, code: "unsupported_schema_format", message: `Action schema uses unsupported format ${schema.format}.` };
2365
+ return { path: path9, code: "unsupported_schema_format", message: `Action schema uses unsupported format ${schema.format}.` };
2243
2366
  }
2244
2367
  if (typeof schema.$ref === "string" && !resolveLocalReference(root, schema.$ref)) {
2245
- return { path: path8, code: "unsupported_schema_ref", message: `Cannot resolve schema reference ${schema.$ref}.` };
2368
+ return { path: path9, code: "unsupported_schema_ref", message: `Cannot resolve schema reference ${schema.$ref}.` };
2246
2369
  }
2247
2370
  const children = [];
2248
2371
  if (isRecord(schema.properties)) children.push(...Object.entries(schema.properties));
@@ -2258,7 +2381,7 @@ function inspectSchema(schema, path8, root, depth) {
2258
2381
  );
2259
2382
  for (const [key, child] of children) {
2260
2383
  if (!isRecord(child)) continue;
2261
- const issue = inspectSchema(child, `${path8}.${key}`, root, depth + 1);
2384
+ const issue = inspectSchema(child, `${path9}.${key}`, root, depth + 1);
2262
2385
  if (issue) return issue;
2263
2386
  }
2264
2387
  return null;
@@ -2266,129 +2389,129 @@ function inspectSchema(schema, path8, root, depth) {
2266
2389
  function arraySchemaEntries(keyword, value) {
2267
2390
  return Array.isArray(value) ? value.map((candidate, index) => [`${keyword}[${index}]`, candidate]) : [];
2268
2391
  }
2269
- function validateNode(schema, value, path8, root, errors, depth) {
2392
+ function validateNode(schema, value, path9, root, errors, depth) {
2270
2393
  if (depth > 128) {
2271
- errors.push({ path: path8, code: "schema_depth", message: "Schema recursion exceeded 128 levels." });
2394
+ errors.push({ path: path9, code: "schema_depth", message: "Schema recursion exceeded 128 levels." });
2272
2395
  return;
2273
2396
  }
2274
2397
  if (typeof schema.$ref === "string") {
2275
2398
  const resolved = resolveLocalReference(root, schema.$ref);
2276
2399
  if (!resolved) {
2277
- errors.push({ path: path8, code: "unsupported_schema_ref", message: `Cannot resolve schema reference ${schema.$ref}.` });
2400
+ errors.push({ path: path9, code: "unsupported_schema_ref", message: `Cannot resolve schema reference ${schema.$ref}.` });
2278
2401
  return;
2279
2402
  }
2280
- validateNode(resolved, value, path8, root, errors, depth + 1);
2403
+ validateNode(resolved, value, path9, root, errors, depth + 1);
2281
2404
  }
2282
2405
  if (Array.isArray(schema.allOf)) {
2283
2406
  for (const candidate of schema.allOf) {
2284
- if (isRecord(candidate)) validateNode(candidate, value, path8, root, errors, depth + 1);
2407
+ if (isRecord(candidate)) validateNode(candidate, value, path9, root, errors, depth + 1);
2285
2408
  }
2286
2409
  }
2287
2410
  if (Array.isArray(schema.anyOf) && !schema.anyOf.some((candidate) => candidateMatches(candidate, value, root, depth))) {
2288
- errors.push({ path: path8, code: "any_of", message: "Value does not match any allowed schema." });
2411
+ errors.push({ path: path9, code: "any_of", message: "Value does not match any allowed schema." });
2289
2412
  return;
2290
2413
  }
2291
2414
  if (Array.isArray(schema.oneOf)) {
2292
2415
  const matches = schema.oneOf.filter((candidate) => candidateMatches(candidate, value, root, depth)).length;
2293
2416
  if (matches !== 1) {
2294
- errors.push({ path: path8, code: "one_of", message: "Value must match exactly one allowed schema." });
2417
+ errors.push({ path: path9, code: "one_of", message: "Value must match exactly one allowed schema." });
2295
2418
  return;
2296
2419
  }
2297
2420
  }
2298
2421
  if (value === null && schema.nullable === true) return;
2299
2422
  if (Object.prototype.hasOwnProperty.call(schema, "const") && !sameJsonValue(value, schema.const)) {
2300
- errors.push({ path: path8, code: "const", message: `Value must equal ${JSON.stringify(schema.const)}.` });
2423
+ errors.push({ path: path9, code: "const", message: `Value must equal ${JSON.stringify(schema.const)}.` });
2301
2424
  return;
2302
2425
  }
2303
2426
  if (Array.isArray(schema.enum) && !schema.enum.some((candidate) => sameJsonValue(value, candidate))) {
2304
- errors.push({ path: path8, code: "enum", message: `Value must be one of ${schema.enum.map((item) => JSON.stringify(item)).join(", ")}.` });
2427
+ errors.push({ path: path9, code: "enum", message: `Value must be one of ${schema.enum.map((item) => JSON.stringify(item)).join(", ")}.` });
2305
2428
  return;
2306
2429
  }
2307
2430
  if (!matchesType(schema.type, value)) {
2308
- errors.push({ path: path8, code: "type", message: `Expected ${String(schema.type)}.` });
2431
+ errors.push({ path: path9, code: "type", message: `Expected ${String(schema.type)}.` });
2309
2432
  return;
2310
2433
  }
2311
- if (typeof value === "string") validateString(schema, value, path8, errors);
2312
- if (typeof value === "number") validateNumber(schema, value, path8, errors);
2313
- if (Array.isArray(value)) validateArray(schema, value, path8, root, errors, depth);
2314
- if (isRecord(value)) validateObject(schema, value, path8, root, errors, depth);
2434
+ if (typeof value === "string") validateString(schema, value, path9, errors);
2435
+ if (typeof value === "number") validateNumber(schema, value, path9, errors);
2436
+ if (Array.isArray(value)) validateArray(schema, value, path9, root, errors, depth);
2437
+ if (isRecord(value)) validateObject(schema, value, path9, root, errors, depth);
2315
2438
  }
2316
- function validateObject(schema, value, path8, root, errors, depth) {
2439
+ function validateObject(schema, value, path9, root, errors, depth) {
2317
2440
  if (typeof schema.minProperties === "number" && Object.keys(value).length < schema.minProperties) {
2318
- errors.push({ path: path8, code: "min_properties", message: `Expected at least ${schema.minProperties} properties.` });
2441
+ errors.push({ path: path9, code: "min_properties", message: `Expected at least ${schema.minProperties} properties.` });
2319
2442
  }
2320
2443
  if (typeof schema.maxProperties === "number" && Object.keys(value).length > schema.maxProperties) {
2321
- errors.push({ path: path8, code: "max_properties", message: `Expected at most ${schema.maxProperties} properties.` });
2444
+ errors.push({ path: path9, code: "max_properties", message: `Expected at most ${schema.maxProperties} properties.` });
2322
2445
  }
2323
2446
  const properties = isRecord(schema.properties) ? schema.properties : {};
2324
2447
  const required = Array.isArray(schema.required) ? schema.required.map(String) : [];
2325
2448
  for (const key of required) {
2326
2449
  if (!Object.prototype.hasOwnProperty.call(value, key)) {
2327
- errors.push({ path: `${path8}.${key}`, code: "required", message: "Required property is missing." });
2450
+ errors.push({ path: `${path9}.${key}`, code: "required", message: "Required property is missing." });
2328
2451
  }
2329
2452
  }
2330
2453
  if (isRecord(schema.propertyNames)) {
2331
2454
  for (const key of Object.keys(value)) {
2332
- validateNode(schema.propertyNames, key, `${path8}.${key}`, root, errors, depth + 1);
2455
+ validateNode(schema.propertyNames, key, `${path9}.${key}`, root, errors, depth + 1);
2333
2456
  }
2334
2457
  }
2335
2458
  for (const [key, child] of Object.entries(value)) {
2336
2459
  const childSchema = properties[key];
2337
2460
  if (isRecord(childSchema)) {
2338
- validateNode(childSchema, child, `${path8}.${key}`, root, errors, depth + 1);
2461
+ validateNode(childSchema, child, `${path9}.${key}`, root, errors, depth + 1);
2339
2462
  } else if (schema.additionalProperties === false) {
2340
- errors.push({ path: `${path8}.${key}`, code: "additional_property", message: "Property is not declared by the action schema." });
2463
+ errors.push({ path: `${path9}.${key}`, code: "additional_property", message: "Property is not declared by the action schema." });
2341
2464
  } else if (isRecord(schema.additionalProperties)) {
2342
- validateNode(schema.additionalProperties, child, `${path8}.${key}`, root, errors, depth + 1);
2465
+ validateNode(schema.additionalProperties, child, `${path9}.${key}`, root, errors, depth + 1);
2343
2466
  }
2344
2467
  }
2345
2468
  }
2346
- function validateArray(schema, value, path8, root, errors, depth) {
2469
+ function validateArray(schema, value, path9, root, errors, depth) {
2347
2470
  if (typeof schema.minItems === "number" && value.length < schema.minItems) {
2348
- errors.push({ path: path8, code: "min_items", message: `Expected at least ${schema.minItems} items.` });
2471
+ errors.push({ path: path9, code: "min_items", message: `Expected at least ${schema.minItems} items.` });
2349
2472
  }
2350
2473
  if (typeof schema.maxItems === "number" && value.length > schema.maxItems) {
2351
- errors.push({ path: path8, code: "max_items", message: `Expected at most ${schema.maxItems} items.` });
2474
+ errors.push({ path: path9, code: "max_items", message: `Expected at most ${schema.maxItems} items.` });
2352
2475
  }
2353
2476
  if (schema.uniqueItems === true) {
2354
2477
  const unique4 = new Set(value.map((item) => JSON.stringify(item)));
2355
- if (unique4.size !== value.length) errors.push({ path: path8, code: "unique_items", message: "Expected every item to be unique." });
2478
+ if (unique4.size !== value.length) errors.push({ path: path9, code: "unique_items", message: "Expected every item to be unique." });
2356
2479
  }
2357
2480
  if (isRecord(schema.items)) {
2358
- value.forEach((item, index) => validateNode(schema.items, item, `${path8}[${index}]`, root, errors, depth + 1));
2481
+ value.forEach((item, index) => validateNode(schema.items, item, `${path9}[${index}]`, root, errors, depth + 1));
2359
2482
  }
2360
2483
  }
2361
- function validateString(schema, value, path8, errors) {
2484
+ function validateString(schema, value, path9, errors) {
2362
2485
  if (typeof schema.minLength === "number" && value.length < schema.minLength) {
2363
- errors.push({ path: path8, code: "min_length", message: `Expected at least ${schema.minLength} characters.` });
2486
+ errors.push({ path: path9, code: "min_length", message: `Expected at least ${schema.minLength} characters.` });
2364
2487
  }
2365
2488
  if (typeof schema.maxLength === "number" && value.length > schema.maxLength) {
2366
- errors.push({ path: path8, code: "max_length", message: `Expected at most ${schema.maxLength} characters.` });
2489
+ errors.push({ path: path9, code: "max_length", message: `Expected at most ${schema.maxLength} characters.` });
2367
2490
  }
2368
2491
  if (typeof schema.pattern === "string") {
2369
2492
  try {
2370
- if (!new RegExp(schema.pattern).test(value)) errors.push({ path: path8, code: "pattern", message: "Value does not match the required pattern." });
2493
+ if (!new RegExp(schema.pattern).test(value)) errors.push({ path: path9, code: "pattern", message: "Value does not match the required pattern." });
2371
2494
  } catch {
2372
- errors.push({ path: path8, code: "invalid_schema_pattern", message: "Action schema contains an invalid pattern." });
2495
+ errors.push({ path: path9, code: "invalid_schema_pattern", message: "Action schema contains an invalid pattern." });
2373
2496
  }
2374
2497
  }
2375
- if (schema.format === "uri" && !isUrl(value)) errors.push({ path: path8, code: "format_uri", message: "Expected an absolute URL." });
2376
- if (schema.format === "email" && !/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(value)) errors.push({ path: path8, code: "format_email", message: "Expected an email address." });
2377
- if (schema.format === "date-time" && !isDateTime(value)) errors.push({ path: path8, code: "format_date_time", message: "Expected an ISO-8601 date-time." });
2498
+ if (schema.format === "uri" && !isUrl(value)) errors.push({ path: path9, code: "format_uri", message: "Expected an absolute URL." });
2499
+ if (schema.format === "email" && !/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(value)) errors.push({ path: path9, code: "format_email", message: "Expected an email address." });
2500
+ if (schema.format === "date-time" && !isDateTime(value)) errors.push({ path: path9, code: "format_date_time", message: "Expected an ISO-8601 date-time." });
2378
2501
  }
2379
- function validateNumber(schema, value, path8, errors) {
2380
- if (typeof schema.minimum === "number" && value < schema.minimum) errors.push({ path: path8, code: "minimum", message: `Expected a value of at least ${schema.minimum}.` });
2381
- if (typeof schema.maximum === "number" && value > schema.maximum) errors.push({ path: path8, code: "maximum", message: `Expected a value of at most ${schema.maximum}.` });
2502
+ function validateNumber(schema, value, path9, errors) {
2503
+ if (typeof schema.minimum === "number" && value < schema.minimum) errors.push({ path: path9, code: "minimum", message: `Expected a value of at least ${schema.minimum}.` });
2504
+ if (typeof schema.maximum === "number" && value > schema.maximum) errors.push({ path: path9, code: "maximum", message: `Expected a value of at most ${schema.maximum}.` });
2382
2505
  const exclusiveMinimum = typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : schema.exclusiveMinimum === true && typeof schema.minimum === "number" ? schema.minimum : null;
2383
2506
  const exclusiveMaximum = typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : schema.exclusiveMaximum === true && typeof schema.maximum === "number" ? schema.maximum : null;
2384
2507
  if (exclusiveMinimum !== null && value <= exclusiveMinimum) {
2385
- errors.push({ path: path8, code: "exclusive_minimum", message: `Expected a value greater than ${exclusiveMinimum}.` });
2508
+ errors.push({ path: path9, code: "exclusive_minimum", message: `Expected a value greater than ${exclusiveMinimum}.` });
2386
2509
  }
2387
2510
  if (exclusiveMaximum !== null && value >= exclusiveMaximum) {
2388
- errors.push({ path: path8, code: "exclusive_maximum", message: `Expected a value less than ${exclusiveMaximum}.` });
2511
+ errors.push({ path: path9, code: "exclusive_maximum", message: `Expected a value less than ${exclusiveMaximum}.` });
2389
2512
  }
2390
2513
  if (typeof schema.multipleOf === "number" && !isMultipleOf(value, schema.multipleOf)) {
2391
- errors.push({ path: path8, code: "multiple_of", message: `Expected a multiple of ${schema.multipleOf}.` });
2514
+ errors.push({ path: path9, code: "multiple_of", message: `Expected a multiple of ${schema.multipleOf}.` });
2392
2515
  }
2393
2516
  }
2394
2517
  function candidateMatches(candidate, value, root, depth) {
@@ -2441,6 +2564,166 @@ function isMultipleOf(value, divisor) {
2441
2564
  const quotient = value / divisor;
2442
2565
  return Math.abs(quotient - Math.round(quotient)) < Number.EPSILON * Math.max(1, Math.abs(quotient)) * 4;
2443
2566
  }
2567
+ function parseTopoloActionUploadContract(value) {
2568
+ if (!isRecord2(value) || value.profile !== "inline-base64" && value.profile !== "url-session") {
2569
+ throw new TopoloSdkError("upload_contract_unavailable", "This action does not publish a supported upload contract.");
2570
+ }
2571
+ const contentTypeField = requiredField(value.contentTypeField, "contentTypeField");
2572
+ const fileNameField = requiredField(value.fileNameField, "fileNameField");
2573
+ const maxBytes = Number(value.maxBytes);
2574
+ const acceptedContentTypes = Array.isArray(value.acceptedContentTypes) ? value.acceptedContentTypes.map((item) => String(item).trim().toLowerCase()).filter(Boolean) : [];
2575
+ if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0 || acceptedContentTypes.length === 0) {
2576
+ throw new TopoloSdkError("invalid_upload_contract", "The action upload contract must declare maxBytes and acceptedContentTypes.");
2577
+ }
2578
+ if (value.profile === "inline-base64") {
2579
+ return {
2580
+ profile: "inline-base64",
2581
+ contentField: requiredField(value.contentField, "contentField"),
2582
+ contentTypeField,
2583
+ fileNameField,
2584
+ maxBytes,
2585
+ acceptedContentTypes
2586
+ };
2587
+ }
2588
+ const uploadMethod = value.uploadMethod === "PUT" || value.uploadMethod === "POST" ? value.uploadMethod : null;
2589
+ const uploadUrlPath = requiredPath(value.uploadUrlPath, "uploadUrlPath");
2590
+ const complete = value.complete === void 0 ? void 0 : parseCompleteContract(value.complete);
2591
+ if (!uploadMethod) throw new TopoloSdkError("invalid_upload_contract", "The action upload contract has an invalid uploadMethod.");
2592
+ return {
2593
+ profile: "url-session",
2594
+ fileNameField,
2595
+ contentTypeField,
2596
+ sizeField: requiredField(value.sizeField, "sizeField"),
2597
+ maxBytes,
2598
+ acceptedContentTypes,
2599
+ uploadUrlPath,
2600
+ uploadMethod,
2601
+ ...complete ? { complete } : {}
2602
+ };
2603
+ }
2604
+ function prepareTopoloActionUploadInput(contractValue, content, input3, options) {
2605
+ const contract = parseTopoloActionUploadContract(contractValue);
2606
+ if (contract.profile !== "inline-base64") throw unsupportedInlineProfile();
2607
+ const bytes = content instanceof Uint8Array ? content : new Uint8Array(content);
2608
+ if (bytes.byteLength === 0) {
2609
+ throw new TopoloSdkError("invalid_upload_content", "Upload content must not be empty.");
2610
+ }
2611
+ if (bytes.byteLength > contract.maxBytes) {
2612
+ throw new TopoloSdkError("upload_too_large", `Upload content exceeds the ${contract.maxBytes}-byte action limit.`);
2613
+ }
2614
+ return mapUploadInput(contract, bytesToBase64(bytes), input3, options);
2615
+ }
2616
+ function prepareTopoloActionBase64UploadInput(contractValue, contentBase64, input3, options) {
2617
+ const contract = parseTopoloActionUploadContract(contractValue);
2618
+ if (contract.profile !== "inline-base64") throw unsupportedInlineProfile();
2619
+ if (!isCanonicalBase64(contentBase64)) {
2620
+ throw new TopoloSdkError("invalid_upload_content", "Upload content must be canonical base64.");
2621
+ }
2622
+ const padding = contentBase64.endsWith("==") ? 2 : contentBase64.endsWith("=") ? 1 : 0;
2623
+ const byteLength = contentBase64.length / 4 * 3 - padding;
2624
+ if (byteLength === 0) throw new TopoloSdkError("invalid_upload_content", "Upload content must not be empty.");
2625
+ if (byteLength > contract.maxBytes) {
2626
+ throw new TopoloSdkError("upload_too_large", `Upload content exceeds the ${contract.maxBytes}-byte action limit.`);
2627
+ }
2628
+ return mapUploadInput(contract, contentBase64, input3, options);
2629
+ }
2630
+ function prepareTopoloUrlSessionUploadInput(contractValue, byteLength, input3, options) {
2631
+ const contract = parseTopoloActionUploadContract(contractValue);
2632
+ if (contract.profile !== "url-session") {
2633
+ throw new TopoloSdkError("invalid_upload_contract", "This action does not publish a URL-session upload contract.");
2634
+ }
2635
+ validateUploadMetadata(contract, byteLength, options);
2636
+ return {
2637
+ contract,
2638
+ input: {
2639
+ ...input3,
2640
+ [contract.fileNameField]: options.fileName.trim(),
2641
+ [contract.contentTypeField]: options.contentType.trim().toLowerCase(),
2642
+ [contract.sizeField]: byteLength
2643
+ }
2644
+ };
2645
+ }
2646
+ function topoloUploadResponseValue(response, path9) {
2647
+ return path9.split(".").reduce((value, segment) => isRecord2(value) ? value[segment] : void 0, response);
2648
+ }
2649
+ function decodeTopoloActionBase64(contentBase64) {
2650
+ if (!isCanonicalBase64(contentBase64)) {
2651
+ throw new TopoloSdkError("invalid_upload_content", "Upload content must be canonical base64.");
2652
+ }
2653
+ const binary = atob(contentBase64);
2654
+ return Uint8Array.from(binary, (character) => character.charCodeAt(0));
2655
+ }
2656
+ function mapUploadInput(contract, contentBase64, input3, options) {
2657
+ validateUploadMetadata(contract, Math.max(1, decodedBase64Size(contentBase64)), options);
2658
+ const fileName = options.fileName.trim();
2659
+ const contentType = options.contentType.trim().toLowerCase();
2660
+ return {
2661
+ ...input3,
2662
+ [contract.contentField]: contentBase64,
2663
+ [contract.contentTypeField]: contentType,
2664
+ [contract.fileNameField]: fileName
2665
+ };
2666
+ }
2667
+ function validateUploadMetadata(contract, byteLength, options) {
2668
+ const fileName = options.fileName.trim();
2669
+ const contentType = options.contentType.trim().toLowerCase();
2670
+ if (!fileName) throw new TopoloSdkError("invalid_upload_file_name", "Upload fileName must not be empty.");
2671
+ if (!Number.isSafeInteger(byteLength) || byteLength <= 0) {
2672
+ throw new TopoloSdkError("invalid_upload_content", "Upload content must not be empty.");
2673
+ }
2674
+ if (byteLength > contract.maxBytes) {
2675
+ throw new TopoloSdkError("upload_too_large", `Upload content exceeds the ${contract.maxBytes}-byte action limit.`);
2676
+ }
2677
+ if (!matchesContentType(contentType, contract.acceptedContentTypes)) {
2678
+ throw new TopoloSdkError("unsupported_upload_content_type", `Content type "${contentType}" is not accepted by this action.`);
2679
+ }
2680
+ }
2681
+ function decodedBase64Size(contentBase64) {
2682
+ const padding = contentBase64.endsWith("==") ? 2 : contentBase64.endsWith("=") ? 1 : 0;
2683
+ return contentBase64.length / 4 * 3 - padding;
2684
+ }
2685
+ function parseCompleteContract(value) {
2686
+ if (!isRecord2(value)) throw new TopoloSdkError("invalid_upload_contract", "The action upload completion contract is invalid.");
2687
+ return {
2688
+ actionId: requiredPath(value.actionId, "complete.actionId"),
2689
+ inputField: requiredField(value.inputField, "complete.inputField"),
2690
+ responsePath: requiredPath(value.responsePath, "complete.responsePath")
2691
+ };
2692
+ }
2693
+ function requiredPath(value, name) {
2694
+ const path9 = typeof value === "string" ? value.trim() : "";
2695
+ if (!/^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*$/.test(path9)) {
2696
+ throw new TopoloSdkError("invalid_upload_contract", `The action upload contract has an invalid ${name}.`);
2697
+ }
2698
+ return path9;
2699
+ }
2700
+ function unsupportedInlineProfile() {
2701
+ return new TopoloSdkError("invalid_upload_contract", "This action does not publish an inline-base64 upload contract.");
2702
+ }
2703
+ function isCanonicalBase64(value) {
2704
+ return value.length > 0 && value.length % 4 === 0 && /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value);
2705
+ }
2706
+ function requiredField(value, name) {
2707
+ const field = typeof value === "string" ? value.trim() : "";
2708
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(field)) {
2709
+ throw new TopoloSdkError("invalid_upload_contract", `The action upload contract has an invalid ${name}.`);
2710
+ }
2711
+ return field;
2712
+ }
2713
+ function matchesContentType(contentType, accepted) {
2714
+ return accepted.some((candidate) => candidate === "*/*" || candidate === contentType || candidate.endsWith("/*") && contentType.startsWith(candidate.slice(0, -1)));
2715
+ }
2716
+ function bytesToBase64(bytes) {
2717
+ let binary = "";
2718
+ const chunkSize = 32768;
2719
+ for (let offset = 0; offset < bytes.length; offset += chunkSize) {
2720
+ binary += String.fromCharCode(...bytes.subarray(offset, offset + chunkSize));
2721
+ }
2722
+ return btoa(binary);
2723
+ }
2724
+ function isRecord2(value) {
2725
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
2726
+ }
2444
2727
  var WRITE_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
2445
2728
  var TOPOLO_ACTION_ID_HEADER = "X-Topolo-Action-Id";
2446
2729
  var TOPOLO_ACTION_NAME_HEADER = "X-Topolo-Action-Name";
@@ -2517,6 +2800,9 @@ var TopoloClient = class _TopoloClient {
2517
2800
  if (v !== void 0) url.searchParams.set(k, String(v));
2518
2801
  }
2519
2802
  }
2803
+ if (opts.body !== void 0 && opts.rawBody !== void 0) {
2804
+ throw new TopoloSdkError("invalid_request_body", "body and rawBody are mutually exclusive.");
2805
+ }
2520
2806
  const headers = new Headers();
2521
2807
  headers.set("Accept", "application/json");
2522
2808
  if (opts.body !== void 0) headers.set("Content-Type", "application/json");
@@ -2551,7 +2837,7 @@ var TopoloClient = class _TopoloClient {
2551
2837
  return await this.fetchImpl(urlStr, {
2552
2838
  method,
2553
2839
  headers: new Headers(headers),
2554
- body: opts.body !== void 0 ? JSON.stringify(opts.body) : null,
2840
+ body: opts.rawBody ?? (opts.body !== void 0 ? JSON.stringify(opts.body) : null),
2555
2841
  signal
2556
2842
  });
2557
2843
  } finally {
@@ -2599,14 +2885,32 @@ var TopoloClient = class _TopoloClient {
2599
2885
  error: message,
2600
2886
  status: res.status
2601
2887
  });
2602
- if (res.status === 401) throw new TopoloAuthError(describeError(parsed, "Unauthorized"));
2888
+ const errorContext = {
2889
+ status: res.status,
2890
+ body: parsed,
2891
+ service: resolvedService.serviceKey,
2892
+ path: opts.path,
2893
+ requestId: res.headers.get("X-Topolo-Request-Id") || requestId,
2894
+ retryAfter: res.headers.get("Retry-After") || void 0
2895
+ };
2896
+ if (res.status === 401) {
2897
+ throw new TopoloAuthError(describeError(parsed, "Unauthorized"), "auth_error", errorContext);
2898
+ }
2603
2899
  if (res.status === 403) {
2604
2900
  throw new TopoloPermissionError(
2605
2901
  describeError(parsed, "Permission denied"),
2606
- extractRequired(parsed)
2902
+ extractRequired(parsed),
2903
+ errorContext
2607
2904
  );
2608
2905
  }
2609
- throw new TopoloHttpError(resolvedService.serviceKey, opts.path, res.status, parsed);
2906
+ throw new TopoloHttpError(
2907
+ resolvedService.serviceKey,
2908
+ opts.path,
2909
+ res.status,
2910
+ parsed,
2911
+ message,
2912
+ errorContext
2913
+ );
2610
2914
  }
2611
2915
  this.emit({
2612
2916
  phase: "response",
@@ -2765,7 +3069,7 @@ var TopoloClient = class _TopoloClient {
2765
3069
  }
2766
3070
  const entries = [];
2767
3071
  for (const item of raw) {
2768
- if (!isRecord2(item)) continue;
3072
+ if (!isRecord3(item)) continue;
2769
3073
  const s = item;
2770
3074
  const appId = typeof s.app_id === "string" ? s.app_id : typeof s.id === "string" ? s.id : null;
2771
3075
  if (!appId) continue;
@@ -2840,8 +3144,8 @@ var TopoloClient = class _TopoloClient {
2840
3144
  "app_topolo_auth.service_api_key_resources.list",
2841
3145
  { appId: service.appId }
2842
3146
  );
2843
- const payload = isRecord2(response) && isRecord2(response.data) ? response.data : response;
2844
- if (!isRecord2(payload) || !Array.isArray(payload.resources)) {
3147
+ const payload = isRecord3(response) && isRecord3(response.data) ? response.data : response;
3148
+ if (!isRecord3(payload) || !Array.isArray(payload.resources)) {
2845
3149
  throw new TopoloSdkError("resource_catalog_unavailable", "TopoloAuth did not return bindable resources.");
2846
3150
  }
2847
3151
  return {
@@ -2947,6 +3251,71 @@ var TopoloClient = class _TopoloClient {
2947
3251
  const entry = await this.getAction(action);
2948
3252
  return this.executeResolvedAction(entry, input3, options);
2949
3253
  }
3254
+ async uploadAction(action, content, input3, options) {
3255
+ const entry = await this.getAction(action);
3256
+ const contract = parseTopoloActionUploadContract(entry.agentContract?.upload);
3257
+ if (contract.profile === "url-session") {
3258
+ const bytes = content instanceof Uint8Array ? content : new Uint8Array(content);
3259
+ return this.executeUrlSessionUpload(entry, contract, bytes, input3, options);
3260
+ }
3261
+ const uploadInput = prepareTopoloActionUploadInput(entry.agentContract?.upload, content, input3, options);
3262
+ return this.executeResolvedAction(entry, uploadInput, options);
3263
+ }
3264
+ async uploadActionBase64(action, contentBase64, input3, options) {
3265
+ const entry = await this.getAction(action);
3266
+ const contract = parseTopoloActionUploadContract(entry.agentContract?.upload);
3267
+ if (contract.profile === "url-session") {
3268
+ return this.executeUrlSessionUpload(entry, contract, decodeTopoloActionBase64(contentBase64), input3, options);
3269
+ }
3270
+ const uploadInput = prepareTopoloActionBase64UploadInput(entry.agentContract?.upload, contentBase64, input3, options);
3271
+ return this.executeResolvedAction(entry, uploadInput, options);
3272
+ }
3273
+ async executeUrlSessionUpload(entry, contract, bytes, input3, options) {
3274
+ const prepared = prepareTopoloUrlSessionUploadInput(contract, bytes.byteLength, input3, options);
3275
+ const initiation = await this.executeResolvedAction(entry, prepared.input, options);
3276
+ const uploadUrl = topoloUploadResponseValue(initiation, contract.uploadUrlPath);
3277
+ if (typeof uploadUrl !== "string" || !uploadUrl.trim()) {
3278
+ throw new TopoloSdkError("invalid_upload_response", `Upload action response is missing ${contract.uploadUrlPath}.`);
3279
+ }
3280
+ await this.putUrlSessionContent(entry, uploadUrl, contract.uploadMethod, bytes, options);
3281
+ if (!contract.complete) return initiation;
3282
+ const completionValue = topoloUploadResponseValue(initiation, contract.complete.responsePath);
3283
+ if (typeof completionValue !== "string" || !completionValue.trim()) {
3284
+ throw new TopoloSdkError("invalid_upload_response", `Upload action response is missing ${contract.complete.responsePath}.`);
3285
+ }
3286
+ const completion = await this.getAction(contract.complete.actionId);
3287
+ return this.executeResolvedAction(completion, {
3288
+ ...options.completionInput ?? {},
3289
+ [contract.complete.inputField]: completionValue
3290
+ }, options);
3291
+ }
3292
+ async putUrlSessionContent(entry, uploadUrl, method, bytes, options) {
3293
+ const service = entry.appSlug || entry.appId;
3294
+ const resolved = entry.apiBaseUrl ? { serviceKey: normalizeLookupKey(service), baseUrl: requireHttpOrigin(entry.apiBaseUrl) } : await this.resolveRequestService(service);
3295
+ const target = new URL(uploadUrl, resolved.baseUrl.endsWith("/") ? resolved.baseUrl : `${resolved.baseUrl}/`);
3296
+ const body = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
3297
+ if (target.origin === new URL(resolved.baseUrl).origin) {
3298
+ await this.request({
3299
+ service,
3300
+ baseUrl: resolved.baseUrl,
3301
+ path: target.toString(),
3302
+ method,
3303
+ rawBody: body,
3304
+ headers: { "Content-Type": options.contentType.trim().toLowerCase() },
3305
+ resource: options.resource,
3306
+ confirm: options.confirm
3307
+ });
3308
+ return;
3309
+ }
3310
+ const response = await this.fetchImpl(target, {
3311
+ method,
3312
+ headers: { "Content-Type": options.contentType.trim().toLowerCase() },
3313
+ body
3314
+ });
3315
+ if (!response.ok) {
3316
+ throw new TopoloHttpError(service, target.pathname, response.status, await response.text(), `Upload transport failed with HTTP ${response.status}.`);
3317
+ }
3318
+ }
2950
3319
  async validateActionInput(action, input3 = {}) {
2951
3320
  const entry = await this.getAction(action);
2952
3321
  return { action: entry, validation: validateTopoloActionInput(entry.inputSchema, input3) };
@@ -3105,7 +3474,7 @@ function resolveConfiguredAppId(slug, appIds) {
3105
3474
  return null;
3106
3475
  }
3107
3476
  function normalizeApiKeyResourceMetadata(input3) {
3108
- if (!isRecord2(input3)) return null;
3477
+ if (!isRecord3(input3)) return null;
3109
3478
  const resourceType = readString(input3.resourceType ?? input3.resource_type);
3110
3479
  const label = readString(input3.label);
3111
3480
  const emptyLabel = readString(input3.emptyLabel ?? input3.empty_label);
@@ -3139,7 +3508,7 @@ function normalizeApiKeyResourceMetadataList(input3) {
3139
3508
  return resources;
3140
3509
  }
3141
3510
  function normalizeBindableResource(input3) {
3142
- if (!isRecord2(input3)) return null;
3511
+ if (!isRecord3(input3)) return null;
3143
3512
  const resourceType = readString(input3.resourceType ?? input3.resource_type);
3144
3513
  const resourceId = readString(input3.resourceId ?? input3.resource_id);
3145
3514
  if (!resourceType || !resourceId) return null;
@@ -3150,13 +3519,13 @@ function normalizeBindableResource(input3) {
3150
3519
  slug: readOptionalString(input3.slug),
3151
3520
  status: readOptionalString(input3.status),
3152
3521
  organizationId: readOptionalString(input3.organizationId ?? input3.organization_id),
3153
- metadata: isRecord2(input3.metadata) ? input3.metadata : {}
3522
+ metadata: isRecord3(input3.metadata) ? input3.metadata : {}
3154
3523
  };
3155
3524
  }
3156
3525
  function normalizeResourceBindings(input3) {
3157
3526
  if (!Array.isArray(input3)) return [];
3158
3527
  return input3.flatMap((binding) => {
3159
- if (!isRecord2(binding)) return [];
3528
+ if (!isRecord3(binding)) return [];
3160
3529
  const resourceType = readString(binding.resourceType ?? binding.resource_type);
3161
3530
  const resourceId = readString(binding.resourceId ?? binding.resource_id);
3162
3531
  return resourceType && resourceId ? [{ resourceType, resourceId }] : [];
@@ -3169,7 +3538,7 @@ function readOptionalString(value) {
3169
3538
  const normalized = readString(value);
3170
3539
  return normalized || null;
3171
3540
  }
3172
- function isRecord2(value) {
3541
+ function isRecord3(value) {
3173
3542
  return typeof value === "object" && value !== null && !Array.isArray(value);
3174
3543
  }
3175
3544
  function resolveConfiguredServiceUrl(slug, serviceUrls) {
@@ -3241,14 +3610,14 @@ function catalogSearchQuery(options) {
3241
3610
  return query;
3242
3611
  }
3243
3612
  function normalizeCatalogPage(value) {
3244
- if (!isRecord2(value)) return { nextCursor: null, hasMore: false };
3613
+ if (!isRecord3(value)) return { nextCursor: null, hasMore: false };
3245
3614
  return {
3246
3615
  nextCursor: typeof value.nextCursor === "string" && value.nextCursor ? value.nextCursor : null,
3247
3616
  hasMore: value.hasMore === true
3248
3617
  };
3249
3618
  }
3250
3619
  function normalizeServiceCatalogEntry(item) {
3251
- if (!isRecord2(item)) return null;
3620
+ if (!isRecord3(item)) return null;
3252
3621
  const appId = typeof item.app_id === "string" ? item.app_id : typeof item.id === "string" ? item.id : null;
3253
3622
  if (!appId) return null;
3254
3623
  const permissions = Array.isArray(item.granted_permissions) ? item.granted_permissions.filter(
@@ -3281,8 +3650,8 @@ function normalizeActionCatalogEntry(item) {
3281
3650
  const name = pickActionString(a.name);
3282
3651
  const appId = pickActionString(a.app_id) ?? pickActionString(a.appId);
3283
3652
  const method = pickActionString(a.method)?.toUpperCase();
3284
- const path8 = pickActionString(a.path);
3285
- if (!actionId || !name || !appId || !method || !path8 || !isHttpMethod(method)) {
3653
+ const path9 = pickActionString(a.path);
3654
+ if (!actionId || !name || !appId || !method || !path9 || !isHttpMethod(method)) {
3286
3655
  return null;
3287
3656
  }
3288
3657
  return {
@@ -3297,7 +3666,7 @@ function normalizeActionCatalogEntry(item) {
3297
3666
  appSlug: pickActionString(a.app_slug) ?? pickActionString(a.appSlug) ?? null,
3298
3667
  appName: pickActionString(a.app_name) ?? pickActionString(a.appName) ?? null,
3299
3668
  method,
3300
- path: path8,
3669
+ path: path9,
3301
3670
  permissionName: pickActionString(a.permission_name) ?? pickActionString(a.permissionName) ?? "",
3302
3671
  requiredPermission: pickActionString(a.required_permission) ?? pickActionString(a.requiredPermission) ?? "",
3303
3672
  authorizationMode: pickAuthorizationMode(a.authorization_mode ?? a.authorizationMode),
@@ -3333,7 +3702,7 @@ function stringArray(value) {
3333
3702
  }
3334
3703
  function mapActionInput(action, input3) {
3335
3704
  const remaining = { ...input3 || {} };
3336
- const path8 = action.path.replace(/\{([a-zA-Z0-9_]+)\}/g, (_match, key) => {
3705
+ const path9 = action.path.replace(/\{([a-zA-Z0-9_]+)\}/g, (_match, key) => {
3337
3706
  const value = remaining[key];
3338
3707
  if (value === void 0 || value === null || value === "") {
3339
3708
  throw new TopoloSdkError(
@@ -3350,14 +3719,14 @@ function mapActionInput(action, input3) {
3350
3719
  delete remaining.body;
3351
3720
  if (action.method === "GET") {
3352
3721
  return {
3353
- path: path8,
3722
+ path: path9,
3354
3723
  query: objectToQuery({ ...remaining, ...explicitQuery })
3355
3724
  };
3356
3725
  }
3357
3726
  const query = objectToQuery(explicitQuery);
3358
3727
  const body = explicitBody !== void 0 ? explicitBody : remaining;
3359
3728
  return {
3360
- path: path8,
3729
+ path: path9,
3361
3730
  ...query ? { query } : {},
3362
3731
  body
3363
3732
  };
@@ -3527,8 +3896,8 @@ var TopoloOAuth = class {
3527
3896
  if (params.clientSecret) body.set("client_secret", params.clientSecret);
3528
3897
  return this.#postForm("/api/developer-oauth/token", body);
3529
3898
  }
3530
- async #postForm(path8, body) {
3531
- const url = new URL(path8, this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`);
3899
+ async #postForm(path9, body) {
3900
+ const url = new URL(path9, this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`);
3532
3901
  const res = await this.fetchImpl(url.toString(), {
3533
3902
  method: "POST",
3534
3903
  headers: {
@@ -3544,7 +3913,7 @@ var TopoloOAuth = class {
3544
3913
  const err = parsed;
3545
3914
  throw new TopoloAuthError(err.error_description || err.error, err.error);
3546
3915
  }
3547
- throw new TopoloHttpError("auth", path8, res.status, parsed);
3916
+ throw new TopoloHttpError("auth", path9, res.status, parsed);
3548
3917
  }
3549
3918
  return parsed;
3550
3919
  }
@@ -3572,29 +3941,57 @@ var TopoloSdkError2 = class extends Error {
3572
3941
  }
3573
3942
  };
3574
3943
  var TopoloAuthError2 = class extends TopoloSdkError2 {
3575
- constructor(message, code = "auth_error") {
3944
+ status;
3945
+ body;
3946
+ service;
3947
+ path;
3948
+ requestId;
3949
+ retryAfter;
3950
+ constructor(message, code = "auth_error", options = {}) {
3576
3951
  super(code, message);
3577
3952
  this.name = "TopoloAuthError";
3953
+ this.status = options.status ?? 401;
3954
+ this.body = options.body;
3955
+ this.service = options.service;
3956
+ this.path = options.path;
3957
+ this.requestId = options.requestId;
3958
+ this.retryAfter = options.retryAfter;
3578
3959
  }
3579
3960
  };
3961
+ function extractHttpErrorCode2(body) {
3962
+ if (!body || typeof body !== "object" || Array.isArray(body)) return "http_error";
3963
+ const record4 = body;
3964
+ if (typeof record4.code === "string" && record4.code.trim()) return record4.code.trim();
3965
+ const nested = record4.error;
3966
+ if (nested && typeof nested === "object" && !Array.isArray(nested)) {
3967
+ const code = nested.code;
3968
+ if (typeof code === "string" && code.trim()) return code.trim();
3969
+ }
3970
+ return "http_error";
3971
+ }
3580
3972
  var TopoloHttpError2 = class extends TopoloSdkError2 {
3581
3973
  status;
3582
3974
  body;
3583
3975
  service;
3584
3976
  path;
3585
- constructor(service, path8, status, body, message) {
3586
- super("http_error", message ?? `HTTP ${status} from ${service}${path8}`);
3977
+ requestId;
3978
+ retryAfter;
3979
+ constructor(service, path9, status, body, message, options = {}) {
3980
+ super(extractHttpErrorCode2(body), message ?? `HTTP ${status} from ${service}${path9}`);
3587
3981
  this.name = "TopoloHttpError";
3588
3982
  this.status = status;
3589
3983
  this.body = body;
3590
3984
  this.service = service;
3591
- this.path = path8;
3985
+ this.path = path9;
3986
+ this.requestId = options.requestId;
3987
+ this.retryAfter = options.retryAfter;
3592
3988
  }
3593
3989
  };
3594
3990
  var APP_API_URLS2 = {
3595
3991
  agent: "https://agent.topolo.app",
3596
3992
  auth: "https://auth.topolo.app",
3597
3993
  backup: "https://backup.topolo.app",
3994
+ blog: "https://blog.topolo.app",
3598
3995
  books: "https://books.topolo.app",
3599
3996
  brand: "https://brand.topolo.app",
3600
3997
  bugfix: "https://bugfix.topolo.app",
@@ -3607,6 +4004,7 @@ var APP_API_URLS2 = {
3607
4004
  compose: "https://compose.topolo.app",
3608
4005
  consent: "https://consent.topolo.app",
3609
4006
  crm: "https://crm.topolo.app",
4007
+ design: "https://design.topolo.app",
3610
4008
  developers: "https://developers.topolo.app",
3611
4009
  director: "https://director.topolo.app",
3612
4010
  feed: "https://feed.topolo.app",
@@ -3719,8 +4117,8 @@ var TopoloOAuth2 = class {
3719
4117
  if (params.clientSecret) body.set("client_secret", params.clientSecret);
3720
4118
  return this.#postForm("/api/developer-oauth/token", body);
3721
4119
  }
3722
- async #postForm(path8, body) {
3723
- const url = new URL(path8, this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`);
4120
+ async #postForm(path9, body) {
4121
+ const url = new URL(path9, this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`);
3724
4122
  const res = await this.fetchImpl(url.toString(), {
3725
4123
  method: "POST",
3726
4124
  headers: {
@@ -3736,7 +4134,7 @@ var TopoloOAuth2 = class {
3736
4134
  const err = parsed;
3737
4135
  throw new TopoloAuthError2(err.error_description || err.error, err.error);
3738
4136
  }
3739
- throw new TopoloHttpError2("auth", path8, res.status, parsed);
4137
+ throw new TopoloHttpError2("auth", path9, res.status, parsed);
3740
4138
  }
3741
4139
  return parsed;
3742
4140
  }
@@ -3766,22 +4164,22 @@ function configPath() {
3766
4164
  return join(base, "topolo", "config.json");
3767
4165
  }
3768
4166
  async function readConfig() {
3769
- const path8 = configPath();
3770
- if (!existsSync(path8)) return {};
4167
+ const path9 = configPath();
4168
+ if (!existsSync(path9)) return {};
3771
4169
  try {
3772
- const raw = await readFile(path8, "utf8");
4170
+ const raw = await readFile(path9, "utf8");
3773
4171
  return JSON.parse(raw);
3774
4172
  } catch {
3775
4173
  return {};
3776
4174
  }
3777
4175
  }
3778
4176
  async function writeConfig(config) {
3779
- const path8 = configPath();
3780
- await mkdir(dirname(path8), { recursive: true });
3781
- await writeFile(path8, JSON.stringify(config, null, 2) + "\n", "utf8");
4177
+ const path9 = configPath();
4178
+ await mkdir(dirname(path9), { recursive: true });
4179
+ await writeFile(path9, JSON.stringify(config, null, 2) + "\n", "utf8");
3782
4180
  if (platform() !== "win32") {
3783
4181
  try {
3784
- await chmod(path8, 384);
4182
+ await chmod(path9, 384);
3785
4183
  } catch {
3786
4184
  }
3787
4185
  }
@@ -4065,6 +4463,10 @@ function fail(err, mode) {
4065
4463
  process.stderr.write(`Error: ${payload.message}
4066
4464
  `);
4067
4465
  if (payload.code) process.stderr.write(`Code: ${payload.code}
4466
+ `);
4467
+ if (payload.status) process.stderr.write(`HTTP: ${payload.status}
4468
+ `);
4469
+ if (payload.requestId) process.stderr.write(`Request: ${payload.requestId}
4068
4470
  `);
4069
4471
  if (payload.hint) process.stderr.write(`Hint: ${payload.hint}
4070
4472
  `);
@@ -4083,6 +4485,12 @@ function normalizeError(err) {
4083
4485
  return {
4084
4486
  message: err.message,
4085
4487
  code,
4488
+ status: err.status,
4489
+ service: err.service,
4490
+ path: err.path,
4491
+ requestId: err.requestId,
4492
+ retryAfter: err.retryAfter,
4493
+ details: err.body,
4086
4494
  hint: code ? hintMap[code] : void 0,
4087
4495
  exitCode: code === "auth_error" || code === "confirmation_required" ? 2 : code === "permission_denied" ? 3 : code === "immutable_drift" ? 4 : 1
4088
4496
  };
@@ -4101,9 +4509,9 @@ function readCliVersion() {
4101
4509
  join2(here, "package.json"),
4102
4510
  join2(here, "..", "package.json")
4103
4511
  ];
4104
- for (const path8 of candidates) {
4512
+ for (const path9 of candidates) {
4105
4513
  try {
4106
- const pkg = JSON.parse(readFileSync(path8, "utf8"));
4514
+ const pkg = JSON.parse(readFileSync(path9, "utf8"));
4107
4515
  if (typeof pkg.version === "string" && pkg.version) return pkg.version;
4108
4516
  } catch {
4109
4517
  }
@@ -4451,6 +4859,7 @@ function formatPretty(info) {
4451
4859
 
4452
4860
  // src/commands/actions.ts
4453
4861
  import { readFileSync as readFileSync2 } from "fs";
4862
+ import { basename, extname } from "path";
4454
4863
 
4455
4864
  // src/commands/api.ts
4456
4865
  var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
@@ -4462,7 +4871,7 @@ function registerApiCommand(program2) {
4462
4871
  "-q, --query <kv...>",
4463
4872
  "Query params as key=value (repeatable), e.g. --query source=cli page=2"
4464
4873
  ).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(
4465
- async (service, method, path8, opts) => {
4874
+ async (service, method, path9, opts) => {
4466
4875
  const mode = resolveOutputMode(opts.json);
4467
4876
  try {
4468
4877
  const upperMethod = method.toUpperCase();
@@ -4501,7 +4910,7 @@ function registerApiCommand(program2) {
4501
4910
  const result = await topolo.client.request({
4502
4911
  service,
4503
4912
  method: httpMethod,
4504
- path: path8,
4913
+ path: path9,
4505
4914
  body,
4506
4915
  ...query ? { query } : {},
4507
4916
  ...resource ? { resource } : {},
@@ -4605,6 +5014,30 @@ function registerActionsCommand(program2) {
4605
5014
  fail(err, mode);
4606
5015
  }
4607
5016
  });
5017
+ actions.command("upload <action>").description("Upload one local file through an action-declared upload contract").requiredOption("--file <path>", "Local file to upload").option("--content-type <type>", "MIME type; inferred for common file extensions").option("-d, --data <json-or-@file>", "Additional JSON action input or @path to a JSON file", "{}").option("--complete-data <json-or-@file>", "Additional input for a declared completion action", "{}").option("--resource-type <type>", "App-local resource alias, such as workspace").option("--resource-id <id>", "Resource id for the selected resource type").option("--confirm", "Acknowledge a mutating upload action").option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(async (action, opts, command) => {
5018
+ const mode = resolveOutputMode(opts.json ?? command.parent?.opts().json);
5019
+ try {
5020
+ const topolo = await requiredClient();
5021
+ const contentType = opts.contentType?.trim().toLowerCase() || inferContentType(opts.file);
5022
+ if (!contentType) throw new Error("--content-type is required for this file extension");
5023
+ const resource = parseResourceOption(opts.resourceType, opts.resourceId);
5024
+ const result = await topolo.client.uploadAction(
5025
+ action,
5026
+ readFileSync2(opts.file),
5027
+ parseJsonObject(opts.data || "{}"),
5028
+ {
5029
+ fileName: basename(opts.file),
5030
+ contentType,
5031
+ completionInput: parseJsonObject(opts.completeData || "{}"),
5032
+ confirm: opts.confirm === true,
5033
+ ...resource ? { resource } : {}
5034
+ }
5035
+ );
5036
+ emit(result, mode, () => JSON.stringify(result, null, 2));
5037
+ } catch (err) {
5038
+ fail(err, mode);
5039
+ }
5040
+ });
4608
5041
  actions.command("validate <action>").description("Validate action input locally against the published JSON Schema").option("-d, --data <json-or-@file>", "JSON action input or @path to a JSON file", "{}").option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(async (action, opts, command) => {
4609
5042
  const mode = resolveOutputMode(opts.json ?? command.parent?.opts().json);
4610
5043
  try {
@@ -4665,13 +5098,13 @@ async function requiredClient() {
4665
5098
  function parseJsonObject(value) {
4666
5099
  let source = value;
4667
5100
  if (value.startsWith("@")) {
4668
- const path8 = value.slice(1);
4669
- if (!path8) throw new Error("--data @file path must not be empty");
5101
+ const path9 = value.slice(1);
5102
+ if (!path9) throw new Error("--data @file path must not be empty");
4670
5103
  try {
4671
- source = readFileSync2(path8, "utf8");
5104
+ source = readFileSync2(path9, "utf8");
4672
5105
  } catch (err) {
4673
5106
  const detail = err instanceof Error ? err.message : String(err);
4674
- throw new Error(`Could not read --data file ${path8}: ${detail}`);
5107
+ throw new Error(`Could not read --data file ${path9}: ${detail}`);
4675
5108
  }
4676
5109
  }
4677
5110
  let parsed;
@@ -4685,6 +5118,21 @@ function parseJsonObject(value) {
4685
5118
  }
4686
5119
  return parsed;
4687
5120
  }
5121
+ function inferContentType(path9) {
5122
+ return {
5123
+ ".avif": "image/avif",
5124
+ ".gif": "image/gif",
5125
+ ".heic": "image/heic",
5126
+ ".jpeg": "image/jpeg",
5127
+ ".jpg": "image/jpeg",
5128
+ ".mov": "video/quicktime",
5129
+ ".mp4": "video/mp4",
5130
+ ".pdf": "application/pdf",
5131
+ ".png": "image/png",
5132
+ ".svg": "image/svg+xml",
5133
+ ".webp": "image/webp"
5134
+ }[extname(path9).toLowerCase()] ?? null;
5135
+ }
4688
5136
  function formatActionRows(rows, nextCursor = null) {
4689
5137
  if (rows.length === 0) return "No actions available for this credential.";
4690
5138
  const serviceWidth = Math.max(7, ...rows.map((row) => (row.appSlug || row.appId).length));
@@ -4745,8 +5193,8 @@ function formatActionPlan(plan) {
4745
5193
 
4746
5194
  // src/commands/apps.ts
4747
5195
  import { execSync } from "child_process";
4748
- import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
4749
- import path5 from "path";
5196
+ import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "fs";
5197
+ import path6 from "path";
4750
5198
 
4751
5199
  // src/commands/app-audit-local.ts
4752
5200
  import {
@@ -5883,14 +6331,14 @@ import { constants } from "fs";
5883
6331
  import { access } from "fs/promises";
5884
6332
  import { relative, resolve } from "path";
5885
6333
  function normalizeHomePath(value) {
5886
- const path8 = String(value || "/dashboard").trim();
5887
- if (!path8.startsWith("/") || path8.startsWith("//") || /[?#]/.test(path8)) {
6334
+ const path9 = String(value || "/dashboard").trim();
6335
+ if (!path9.startsWith("/") || path9.startsWith("//") || /[?#]/.test(path9)) {
5888
6336
  throw new Error("Invalid home path. Use an absolute application path such as /dashboard.");
5889
6337
  }
5890
- if (["/", "/login", "/auth/callback", "/getting-started"].includes(path8)) {
6338
+ if (["/", "/login", "/auth/callback", "/getting-started"].includes(path9)) {
5891
6339
  throw new Error("Home path must be a protected product route, not a reserved entry route.");
5892
6340
  }
5893
- return path8.replace(/\/$/, "") || "/dashboard";
6341
+ return path9.replace(/\/$/, "") || "/dashboard";
5894
6342
  }
5895
6343
  function normalizeHomeLabel(value) {
5896
6344
  const label = String(value || "Dashboard").trim();
@@ -6021,9 +6469,9 @@ function toTitle(value) {
6021
6469
  function json(value) {
6022
6470
  return JSON.stringify(value, null, 2);
6023
6471
  }
6024
- async function pathExists(path8) {
6472
+ async function pathExists(path9) {
6025
6473
  try {
6026
- await access(path8, constants.F_OK);
6474
+ await access(path9, constants.F_OK);
6027
6475
  return true;
6028
6476
  } catch {
6029
6477
  return false;
@@ -6053,6 +6501,54 @@ function firstPartyAppPath(appDir, _audience) {
6053
6501
  return `apps/${appDir}`;
6054
6502
  }
6055
6503
 
6504
+ // src/commands/app-scaffold-platform-versions.ts
6505
+ import { readdirSync as readdirSync2, readFileSync as readFileSync4 } from "fs";
6506
+ import path2 from "path";
6507
+ var versionCache = /* @__PURE__ */ new Map();
6508
+ function platformPackagePin(packageName, options = {}) {
6509
+ const workspaceRoot = resolveTopoloRepoRoot(
6510
+ options.repoRoot ?? process.env.TOPOLO_WORKSPACE_ROOT ?? void 0,
6511
+ options.cwd ?? process.cwd()
6512
+ );
6513
+ if (!workspaceRoot) {
6514
+ throw new Error(
6515
+ `Cannot resolve a concrete version for ${packageName}: platform-target scaffolds must run inside the Topolo workspace checkout (a directory containing apps/ and system-apps/), or receive --repo-root. The fleet pinning policy requires exact ^<latest> pins, never "latest".`
6516
+ );
6517
+ }
6518
+ const version = platformPackageVersions(workspaceRoot).get(packageName);
6519
+ if (!version) {
6520
+ throw new Error(
6521
+ `Unknown Topolo platform package: ${packageName} \u2014 no published package with that name under ${path2.join(workspaceRoot, "topolo-platform", "packages")}.`
6522
+ );
6523
+ }
6524
+ return `^${version}`;
6525
+ }
6526
+ function platformPackageVersions(workspaceRoot) {
6527
+ const packagesRoot = path2.join(workspaceRoot, "topolo-platform", "packages");
6528
+ const cached = versionCache.get(packagesRoot);
6529
+ if (cached) return cached;
6530
+ const versions = /* @__PURE__ */ new Map();
6531
+ let entries = [];
6532
+ try {
6533
+ entries = readdirSync2(packagesRoot);
6534
+ } catch {
6535
+ }
6536
+ for (const entry of entries) {
6537
+ let packageJson2;
6538
+ try {
6539
+ packageJson2 = JSON.parse(readFileSync4(path2.join(packagesRoot, entry, "package.json"), "utf8"));
6540
+ } catch {
6541
+ continue;
6542
+ }
6543
+ const version = packageJson2.topoloPublishedVersion || packageJson2.version;
6544
+ if (packageJson2.name && /^@topolo(-io)?\//.test(packageJson2.name) && !packageJson2.private && version) {
6545
+ versions.set(packageJson2.name, version);
6546
+ }
6547
+ }
6548
+ versionCache.set(packagesRoot, versions);
6549
+ return versions;
6550
+ }
6551
+
6056
6552
  // src/commands/app-scaffold-cloudflare-constants.ts
6057
6553
  var CLOUDFLARE_ACCOUNT_ID = "49ef1ba682ad8cfd720c86699ae17521";
6058
6554
  var STAGING_CLOUDFLARE_ACCOUNT_ID = "4f4e1c69a3830946f9fea7b1eb7531ac";
@@ -6085,7 +6581,7 @@ function packageJson(application, target, options) {
6085
6581
  devDependencies.tailwindcss = "3.4.19";
6086
6582
  dependencies["react-router-dom"] = "^7.6.2";
6087
6583
  } else {
6088
- dependencies["@topolo/login"] = topoloPackageVersion("@topolo/login", target);
6584
+ dependencies["@topolo/login"] = topoloPackageVersion("@topolo/login", target, options.repoRoot);
6089
6585
  }
6090
6586
  }
6091
6587
  if (options.api) {
@@ -6097,13 +6593,13 @@ function packageJson(application, target, options) {
6097
6593
  scripts["deploy:staging"] = `DEPLOY_ENVIRONMENT=staging CLOUDFLARE_ACCOUNT_ID=${STAGING_CLOUDFLARE_ACCOUNT_ID} python3 .github/scripts/standard_deploy.py local-deploy`;
6098
6594
  scripts["deploy:production"] = `DEPLOY_ENVIRONMENT=production CLOUDFLARE_ACCOUNT_ID=${CLOUDFLARE_ACCOUNT_ID} python3 .github/scripts/standard_deploy.py local-deploy`;
6099
6595
  } else {
6100
- dependencies["@topolo/sdk"] = topoloPackageVersion("@topolo/sdk", target);
6596
+ dependencies["@topolo/sdk"] = topoloPackageVersion("@topolo/sdk", target, options.repoRoot);
6101
6597
  scripts.deploy = "npm run build && wrangler deploy";
6102
6598
  }
6103
6599
  devDependencies.wrangler = "^4.91.0";
6104
6600
  }
6105
6601
  for (const packageName of options.packages) {
6106
- dependencies[packageName] = topoloPackageVersion(packageName, target);
6602
+ dependencies[packageName] = topoloPackageVersion(packageName, target, options.repoRoot);
6107
6603
  }
6108
6604
  if (target === "platform") {
6109
6605
  const testDirs = [];
@@ -6145,14 +6641,16 @@ function packageJson(application, target, options) {
6145
6641
  })}
6146
6642
  `;
6147
6643
  }
6148
- function topoloPackageVersion(packageName, target) {
6644
+ function topoloPackageVersion(packageName, target, repoRoot) {
6149
6645
  if (target === "developer") {
6150
6646
  if (packageName === "@topolo/login") return "^0.1.0";
6151
6647
  if (packageName === "@topolo-io/auth-client") return "^1.0.0";
6152
6648
  if (packageName === "@topolo/sdk") return "^0.8.1";
6153
6649
  return "^0.1.0";
6154
6650
  }
6155
- if (packageName.startsWith("@topolo/") || packageName.startsWith("@topolo-io/")) return "latest";
6651
+ if (packageName.startsWith("@topolo/") || packageName.startsWith("@topolo-io/")) {
6652
+ return platformPackagePin(packageName, { repoRoot });
6653
+ }
6156
6654
  throw new Error(`Unknown Topolo platform package: ${packageName}`);
6157
6655
  }
6158
6656
  function hasScaffoldResources(resources) {
@@ -8121,10 +8619,10 @@ function developerWorkerTs(application) {
8121
8619
  }
8122
8620
 
8123
8621
  // src/commands/app-scaffold-generate.ts
8124
- import { existsSync as existsSync3, mkdirSync, mkdtempSync, readFileSync as readFileSync4, rmSync, writeFileSync } from "fs";
8622
+ import { existsSync as existsSync3, mkdirSync, mkdtempSync, readFileSync as readFileSync5, rmSync, writeFileSync } from "fs";
8125
8623
  import { spawnSync } from "child_process";
8126
8624
  import { tmpdir } from "os";
8127
- import path2 from "path";
8625
+ import path3 from "path";
8128
8626
  function resolveGeneratorPath() {
8129
8627
  const candidates = [
8130
8628
  // Published CLI: vendored alongside dist/index.js at build time.
@@ -8151,18 +8649,18 @@ var GENERATED = {
8151
8649
  };
8152
8650
  function deriveGeneratedConfigs(files, base) {
8153
8651
  const generator = resolveGeneratorPath();
8154
- const root = mkdtempSync(path2.join(tmpdir(), "topolo-scaffold-generate-"));
8652
+ const root = mkdtempSync(path3.join(tmpdir(), "topolo-scaffold-generate-"));
8155
8653
  try {
8156
8654
  const prefix = `${base}/`;
8157
8655
  for (const file of files) {
8158
8656
  if (!file.path.startsWith(prefix)) continue;
8159
- const abs = path2.join(root, file.path.slice(prefix.length));
8160
- mkdirSync(path2.dirname(abs), { recursive: true });
8657
+ const abs = path3.join(root, file.path.slice(prefix.length));
8658
+ mkdirSync(path3.dirname(abs), { recursive: true });
8161
8659
  writeFileSync(abs, file.content, "utf8");
8162
8660
  }
8163
8661
  const result = spawnSync(
8164
8662
  process.execPath,
8165
- [generator, `--manifest=${path2.join(root, "topolo.app.json")}`],
8663
+ [generator, `--manifest=${path3.join(root, "topolo.app.json")}`],
8166
8664
  { cwd: root, encoding: "utf8" }
8167
8665
  );
8168
8666
  if (result.status !== 0) {
@@ -8179,7 +8677,7 @@ ${result.stdout ?? ""}`);
8179
8677
  return written.map((rel) => ({
8180
8678
  path: `${base}/${rel}`,
8181
8679
  ...GENERATED,
8182
- content: readFileSync4(path2.join(root, rel), "utf8")
8680
+ content: readFileSync5(path3.join(root, rel), "utf8")
8183
8681
  }));
8184
8682
  } finally {
8185
8683
  rmSync(root, { recursive: true, force: true });
@@ -9413,7 +9911,8 @@ function applicationScaffoldPayload(application, options = {}) {
9413
9911
  target,
9414
9912
  browser,
9415
9913
  api,
9416
- ciWorkflow
9914
+ ciWorkflow,
9915
+ repoRoot: options.repoRoot
9417
9916
  }),
9418
9917
  commands: scaffoldCommands({
9419
9918
  id,
@@ -9501,7 +10000,8 @@ function scaffoldFiles(options) {
9501
10000
  browser: options.browser,
9502
10001
  api: options.api,
9503
10002
  resources: options.payload.resources,
9504
- packages: options.payload.capabilityComposition.packages
10003
+ packages: options.payload.capabilityComposition.packages,
10004
+ repoRoot: options.repoRoot
9505
10005
  })
9506
10006
  },
9507
10007
  {
@@ -10312,18 +10812,18 @@ import { join as join3 } from "path";
10312
10812
 
10313
10813
  // src/commands/app-reconcile.ts
10314
10814
  import { readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
10315
- import path4 from "path";
10815
+ import path5 from "path";
10316
10816
  import { createInterface as createInterface2 } from "readline/promises";
10317
10817
  import { stdin as input2, stdout as output } from "process";
10318
10818
 
10319
10819
  // src/commands/app-capability-composition.ts
10320
10820
  import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
10321
- import path3 from "path";
10821
+ import path4 from "path";
10322
10822
  async function reconcileApplicationCapabilitySource(options) {
10323
- const manifestPath = path3.resolve(options.manifestPath || "topolo.app.json");
10324
- const root = path3.dirname(manifestPath);
10325
- const packagePath = path3.join(root, "package.json");
10326
- const contractPath = path3.join(root, "topolo.capability-contract.json");
10823
+ const manifestPath = path4.resolve(options.manifestPath || "topolo.app.json");
10824
+ const root = path4.dirname(manifestPath);
10825
+ const packagePath = path4.join(root, "package.json");
10826
+ const contractPath = path4.join(root, "topolo.capability-contract.json");
10327
10827
  const manifest = parseRecord(await readFile3(manifestPath, "utf8"), manifestPath);
10328
10828
  const registration = record(manifest.registration);
10329
10829
  if (!registration) throw new Error("topolo.app.json must declare registration before capability composition.");
@@ -10368,7 +10868,7 @@ async function reconcileApplicationCapabilitySource(options) {
10368
10868
  const nextDependencies = { ...dependencies };
10369
10869
  for (const packageName of capabilities.packages) {
10370
10870
  if (typeof nextDependencies[packageName] !== "string") {
10371
- nextDependencies[packageName] = packageVersion(packageName, target);
10871
+ nextDependencies[packageName] = packageVersion(packageName, target, { repoRoot: options.repoRoot, cwd: root });
10372
10872
  }
10373
10873
  }
10374
10874
  const sortedDependencies = sortRecord(nextDependencies);
@@ -10378,9 +10878,9 @@ async function reconcileApplicationCapabilitySource(options) {
10378
10878
  compareChange(changes, "topolo.capability-contract.json", existingContract, expectedContract);
10379
10879
  const adapter = target === "platform" ? managedCapabilityAdapter(capabilities.selected) : null;
10380
10880
  const apiDirectory = resolveApiDirectory(root, manifest, target);
10381
- const adapterPath = path3.join(apiDirectory, "src", "topolo-capabilities.ts");
10881
+ const adapterPath = path4.join(apiDirectory, "src", "topolo-capabilities.ts");
10382
10882
  if (adapter) compareChange(changes, relativePath(root, adapterPath), await readOptionalText(adapterPath), adapter);
10383
- const pricingPath = path3.join(root, "pricing-manifest.json");
10883
+ const pricingPath = path4.join(root, "pricing-manifest.json");
10384
10884
  const pricingManifest = capabilities.selected.includes("billing") ? defaultPricingManifest({
10385
10885
  appId: String(manifest.appId || manifest.app_id || "").trim(),
10386
10886
  displayName: String(registration.displayName || manifest.name || "Application"),
@@ -10392,7 +10892,7 @@ async function reconcileApplicationCapabilitySource(options) {
10392
10892
  const missingResources = capabilities.resources.filter((resource) => !manifestProvidesResource(manifest, resource));
10393
10893
  const verticalConfiguration = [];
10394
10894
  for (const [capability2, fileName, description] of VERTICAL_CAPABILITY_FILES) {
10395
- if (capabilities.selected.includes(capability2) && !await fileExists(path3.join(root, fileName))) {
10895
+ if (capabilities.selected.includes(capability2) && !await fileExists(path4.join(root, fileName))) {
10396
10896
  verticalConfiguration.push(`${fileName}: ${description}`);
10397
10897
  }
10398
10898
  }
@@ -10407,7 +10907,7 @@ async function reconcileApplicationCapabilitySource(options) {
10407
10907
  await writeJson(contractPath, expectedContract);
10408
10908
  if (pricingManifest) await writeJson(pricingPath, pricingManifest);
10409
10909
  if (adapter) {
10410
- await mkdir3(path3.dirname(adapterPath), { recursive: true });
10910
+ await mkdir3(path4.dirname(adapterPath), { recursive: true });
10411
10911
  await writeFile4(adapterPath, adapter, "utf8");
10412
10912
  }
10413
10913
  }
@@ -10510,11 +11010,8 @@ function manifestProvidesResource(manifest, resource) {
10510
11010
  }
10511
11011
  return false;
10512
11012
  }
10513
- function packageVersion(packageName, target) {
10514
- if (target === "platform") {
10515
- if (packageName === "@topolo-io/notifications") return "^0.6.8";
10516
- return "latest";
10517
- }
11013
+ function packageVersion(packageName, target, resolution) {
11014
+ if (target === "platform") return platformPackagePin(packageName, resolution);
10518
11015
  if (packageName === "@topolo/login") return "^0.1.0";
10519
11016
  if (packageName === "@topolo/sdk") return "^0.8.1";
10520
11017
  throw new Error(`Capability registry exposed private package ${packageName} to a third-party application.`);
@@ -10591,13 +11088,13 @@ function resolveApiDirectory(root, manifest, target) {
10591
11088
  for (const value of Object.values(record(manifest.targets) ?? {})) {
10592
11089
  const targetRecord = record(value);
10593
11090
  if (targetRecord?.kind === "worker" && !targetRecord.assets && typeof targetRecord.dir === "string") {
10594
- return path3.resolve(root, targetRecord.dir);
11091
+ return path4.resolve(root, targetRecord.dir);
10595
11092
  }
10596
11093
  }
10597
- return path3.join(root, "apps", "api");
11094
+ return path4.join(root, "apps", "api");
10598
11095
  }
10599
11096
  function relativePath(root, filePath) {
10600
- return path3.relative(root, filePath).split(path3.sep).join("/");
11097
+ return path4.relative(root, filePath).split(path4.sep).join("/");
10601
11098
  }
10602
11099
 
10603
11100
  // src/commands/app-reconcile.ts
@@ -10667,7 +11164,7 @@ function titleFromSlug(value) {
10667
11164
  return value.replace(/^topolo[-_]/, "").split(/[-_]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
10668
11165
  }
10669
11166
  async function completeApplicationRegistrationInteractively(options) {
10670
- const manifestPath = path4.resolve(options.manifestPath || "topolo.app.json");
11167
+ const manifestPath = path5.resolve(options.manifestPath || "topolo.app.json");
10671
11168
  const manifest = JSON.parse(await readFile4(manifestPath, "utf8"));
10672
11169
  const existing = record2(manifest.registration) || {};
10673
11170
  const appId = String(manifest.appId || manifest.app_id || "").trim();
@@ -10747,7 +11244,7 @@ async function completeApplicationRegistrationInteractively(options) {
10747
11244
  }
10748
11245
  }
10749
11246
  async function loadApplicationReconcileRequest(options) {
10750
- const manifestPath = path4.resolve(options.manifestPath || "topolo.app.json");
11247
+ const manifestPath = path5.resolve(options.manifestPath || "topolo.app.json");
10751
11248
  const raw = await readFile4(manifestPath, "utf8");
10752
11249
  const manifest = JSON.parse(raw);
10753
11250
  assertRegistration(manifest.registration);
@@ -11151,7 +11648,7 @@ function applicationAuditPayload(application, options = {}) {
11151
11648
  }
11152
11649
  function recordApplicationEvidence(application, options = {}) {
11153
11650
  const appId = resolveApplication(application).id;
11154
- const root = path5.resolve(options.appRoot ?? process.cwd());
11651
+ const root = path6.resolve(options.appRoot ?? process.cwd());
11155
11652
  if (!existsSync4(root)) {
11156
11653
  throw new Error(`Application root does not exist: ${root}`);
11157
11654
  }
@@ -11163,12 +11660,12 @@ function recordApplicationEvidence(application, options = {}) {
11163
11660
  commit = void 0;
11164
11661
  }
11165
11662
  }
11166
- const evidenceDir = path5.join(root, ".topolo");
11167
- const evidencePath = path5.join(evidenceDir, "evidence.json");
11663
+ const evidenceDir = path6.join(root, ".topolo");
11664
+ const evidencePath = path6.join(evidenceDir, "evidence.json");
11168
11665
  let before = null;
11169
11666
  if (existsSync4(evidencePath)) {
11170
11667
  try {
11171
- const raw = JSON.parse(readFileSync5(evidencePath, "utf8"));
11668
+ const raw = JSON.parse(readFileSync6(evidencePath, "utf8"));
11172
11669
  if (raw && typeof raw === "object" && raw.version === 1) {
11173
11670
  before = raw;
11174
11671
  }
@@ -11463,14 +11960,14 @@ function statusesAtOrWorse(threshold) {
11463
11960
  }
11464
11961
 
11465
11962
  // src/commands/platform.ts
11466
- import { readdirSync as readdirSync2, readFileSync as readFileSync6, statSync as statSync2 } from "fs";
11467
- import path7 from "path";
11963
+ import { readdirSync as readdirSync3, readFileSync as readFileSync7, statSync as statSync2 } from "fs";
11964
+ import path8 from "path";
11468
11965
 
11469
11966
  // src/commands/app-capability-provision.ts
11470
11967
  import { readFile as readFile5, writeFile as writeFile6 } from "fs/promises";
11471
- import path6 from "path";
11968
+ import path7 from "path";
11472
11969
  async function provisionApplicationResources(options) {
11473
- const manifestPath = path6.resolve(options.manifestPath || "topolo.app.json");
11970
+ const manifestPath = path7.resolve(options.manifestPath || "topolo.app.json");
11474
11971
  const manifest = parseRecord2(await readFile5(manifestPath, "utf8"), manifestPath);
11475
11972
  const environments = requiredRecord(manifest.environments, "topolo.app.json#environments");
11476
11973
  const environment = requiredRecord(environments[options.environment], `topolo.app.json#environments.${options.environment}`);
@@ -11675,7 +12172,8 @@ function registerPlatformCommand(program2) {
11675
12172
  navigationMode: opts.navigationMode,
11676
12173
  workspaceSwitcher: opts.workspaceSwitcher,
11677
12174
  uiTemplate: opts.uiTemplate,
11678
- tagline: opts.tagline
12175
+ tagline: opts.tagline,
12176
+ repoRoot: opts.repoRoot
11679
12177
  });
11680
12178
  if (opts.workflowOnly && !payload.ciWorkflow) {
11681
12179
  throw new Error("--workflow-only is only available for first-party scaffolds.");
@@ -11742,29 +12240,29 @@ var SKIPPED_SCAN_DIRS = /* @__PURE__ */ new Set([
11742
12240
  "output"
11743
12241
  ]);
11744
12242
  function scanStagingProductionUrls(rootInput, allowPatterns) {
11745
- const root = path7.resolve(rootInput);
12243
+ const root = path8.resolve(rootInput);
11746
12244
  const allowlist = allowPatterns.map((pattern) => new RegExp(pattern));
11747
12245
  const violations = [];
11748
12246
  function shouldSkipFile(filePath) {
11749
- const basename = path7.basename(filePath);
11750
- const relativePath2 = path7.relative(root, filePath);
11751
- if (basename === "package-lock.json" || basename === "pnpm-lock.yaml" || basename === "yarn.lock") return true;
11752
- if (basename === "topolo.cloudcontrol.json") return true;
11753
- if (basename === "wrangler.toml" || basename === "wrangler.jsonc") return true;
11754
- if (basename === "consumer-graph.json") return true;
11755
- if (/^cloudcontrol-deployment-report-.+\.json$/.test(basename)) return true;
11756
- if (basename.endsWith(".generated.ts")) return true;
11757
- if (basename.endsWith(".test.ts") || basename.endsWith(".test.tsx") || basename.endsWith(".spec.ts")) return true;
11758
- if (basename === ".env.production") return true;
11759
- 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;
11760
- if (relativePath2.includes(`${path7.sep}test${path7.sep}`) || relativePath2.includes(`${path7.sep}tests${path7.sep}`) || relativePath2.includes(`${path7.sep}e2e${path7.sep}`)) return true;
11761
- if (relativePath2.startsWith(`scripts${path7.sep}`) || relativePath2.includes(`${path7.sep}scripts${path7.sep}`)) return true;
11762
- return !RUNTIME_EXTENSIONS.has(path7.extname(filePath));
12247
+ const basename2 = path8.basename(filePath);
12248
+ const relativePath2 = path8.relative(root, filePath);
12249
+ if (basename2 === "package-lock.json" || basename2 === "pnpm-lock.yaml" || basename2 === "yarn.lock") return true;
12250
+ if (basename2 === "topolo.cloudcontrol.json") return true;
12251
+ if (basename2 === "wrangler.toml" || basename2 === "wrangler.jsonc") return true;
12252
+ if (basename2 === "consumer-graph.json") return true;
12253
+ if (/^cloudcontrol-deployment-report-.+\.json$/.test(basename2)) return true;
12254
+ if (basename2.endsWith(".generated.ts")) return true;
12255
+ if (basename2.endsWith(".test.ts") || basename2.endsWith(".test.tsx") || basename2.endsWith(".spec.ts")) return true;
12256
+ if (basename2 === ".env.production") return true;
12257
+ if (relativePath2 === path8.join("src", "environment.ts") || relativePath2 === path8.join("src", "environmentUrls.ts") || relativePath2.endsWith(path8.join("src", "environment.ts")) || relativePath2.endsWith(path8.join("src", "lib", "environment.ts")) || relativePath2 === path8.join("packages", "topolo-ui-kit", "src", "environmentUrls.ts")) return true;
12258
+ if (relativePath2.includes(`${path8.sep}test${path8.sep}`) || relativePath2.includes(`${path8.sep}tests${path8.sep}`) || relativePath2.includes(`${path8.sep}e2e${path8.sep}`)) return true;
12259
+ if (relativePath2.startsWith(`scripts${path8.sep}`) || relativePath2.includes(`${path8.sep}scripts${path8.sep}`)) return true;
12260
+ return !RUNTIME_EXTENSIONS.has(path8.extname(filePath));
11763
12261
  }
11764
12262
  function scanFile(filePath) {
11765
12263
  if (shouldSkipFile(filePath)) return;
11766
- const relativePath2 = path7.relative(root, filePath);
11767
- const content = readFileSync6(filePath, "utf8");
12264
+ const relativePath2 = path8.relative(root, filePath);
12265
+ const content = readFileSync7(filePath, "utf8");
11768
12266
  const lines = content.split(/\r?\n/);
11769
12267
  lines.forEach((line, index) => {
11770
12268
  const matches = [...line.matchAll(PRODUCTION_TOPOLO_URL_PATTERN)].map((match) => match[0]);
@@ -11777,8 +12275,8 @@ function scanStagingProductionUrls(rootInput, allowPatterns) {
11777
12275
  });
11778
12276
  }
11779
12277
  function walk(dir) {
11780
- for (const entry of readdirSync2(dir, { withFileTypes: true })) {
11781
- const fullPath = path7.join(dir, entry.name);
12278
+ for (const entry of readdirSync3(dir, { withFileTypes: true })) {
12279
+ const fullPath = path8.join(dir, entry.name);
11782
12280
  if (entry.isDirectory()) {
11783
12281
  if (!SKIPPED_SCAN_DIRS.has(entry.name)) walk(fullPath);
11784
12282
  continue;
@@ -11946,7 +12444,7 @@ var DEFAULT_SEED_BASE_URL = "https://seed.stg.topolo.us";
11946
12444
  function seedBaseUrl() {
11947
12445
  return process.env.TOPOLO_SEED_URL || DEFAULT_SEED_BASE_URL;
11948
12446
  }
11949
- async function authedFetch(path8, init = {}) {
12447
+ async function authedFetch(path9, init = {}) {
11950
12448
  const credential = await refreshCredentialIfNeeded("staging");
11951
12449
  if (!credential) {
11952
12450
  throw new Error("No staging credential configured. Run `topolo auth login --env staging` or set TOPOLO_STAGING_API_KEY.");
@@ -11956,7 +12454,7 @@ async function authedFetch(path8, init = {}) {
11956
12454
  else headers.set("Authorization", `Bearer ${credential.accessToken}`);
11957
12455
  headers.set("X-Topolo-Client", `topolo-cli/${CLI_VERSION}`);
11958
12456
  if (init.body && !headers.has("Content-Type")) headers.set("Content-Type", "application/json");
11959
- const response = await fetch(`${seedBaseUrl()}${path8}`, { ...init, headers });
12457
+ const response = await fetch(`${seedBaseUrl()}${path9}`, { ...init, headers });
11960
12458
  const text = await response.text();
11961
12459
  let body;
11962
12460
  try {
@@ -12233,7 +12731,7 @@ function createOAuthClientPayload(options) {
12233
12731
  logo_url: options.logoUrl
12234
12732
  };
12235
12733
  }
12236
- async function authFetchJson(path8, init = {}) {
12734
+ async function authFetchJson(path9, init = {}) {
12237
12735
  const environment = selectedEnvironment();
12238
12736
  const credential = await refreshCredentialIfNeeded(environment);
12239
12737
  if (!credential) {
@@ -12255,7 +12753,7 @@ async function authFetchJson(path8, init = {}) {
12255
12753
  throw err;
12256
12754
  }
12257
12755
  headers.set("Authorization", `Bearer ${credential.accessToken}`);
12258
- const response = await fetch(`${authBaseUrl}${path8}`, { ...init, headers });
12756
+ const response = await fetch(`${authBaseUrl}${path9}`, { ...init, headers });
12259
12757
  const text = await response.text();
12260
12758
  const payload = text ? JSON.parse(text) : {};
12261
12759
  if (!response.ok || payload.success === false) {
@@ -12510,11 +13008,11 @@ function registerViaOfficialCli(cli, mcp, agentName) {
12510
13008
  return result.status === 0;
12511
13009
  }
12512
13010
  async function registerClaudeViaConfig(mcp) {
12513
- const path8 = join5(homedir2(), ".claude.json");
13011
+ const path9 = join5(homedir2(), ".claude.json");
12514
13012
  let config = {};
12515
- if (existsSync5(path8)) {
13013
+ if (existsSync5(path9)) {
12516
13014
  try {
12517
- config = JSON.parse(await readFile7(path8, "utf8"));
13015
+ config = JSON.parse(await readFile7(path9, "utf8"));
12518
13016
  } catch {
12519
13017
  config = {};
12520
13018
  }
@@ -12527,13 +13025,13 @@ async function registerClaudeViaConfig(mcp) {
12527
13025
  env: { TOPOLO_AGENT_NAME: AGENT_NAME_FOR.claude }
12528
13026
  };
12529
13027
  config.mcpServers = servers;
12530
- await writeFile7(path8, `${JSON.stringify(config, null, 2)}
13028
+ await writeFile7(path9, `${JSON.stringify(config, null, 2)}
12531
13029
  `, "utf8");
12532
13030
  }
12533
13031
  async function registerCodexViaConfig(mcp) {
12534
- const path8 = join5(homedir2(), ".codex", "config.toml");
13032
+ const path9 = join5(homedir2(), ".codex", "config.toml");
12535
13033
  let text = "";
12536
- if (existsSync5(path8)) text = await readFile7(path8, "utf8");
13034
+ if (existsSync5(path9)) text = await readFile7(path9, "utf8");
12537
13035
  text = text.replace(
12538
13036
  /(^|\n)\[mcp_servers\.topolo(?:\.[a-z]+)?\][^\[]*/g,
12539
13037
  (match) => match.startsWith("\n") ? "\n" : ""
@@ -12549,8 +13047,8 @@ command = "${mcp.command}"${argsToml}
12549
13047
  [mcp_servers.topolo.env]
12550
13048
  TOPOLO_AGENT_NAME = "${AGENT_NAME_FOR.codex}"
12551
13049
  `;
12552
- await mkdir4(dirname4(path8), { recursive: true });
12553
- await writeFile7(path8, `${text}${block}`, "utf8");
13050
+ await mkdir4(dirname4(path9), { recursive: true });
13051
+ await writeFile7(path9, `${text}${block}`, "utf8");
12554
13052
  }
12555
13053
  async function installForAgent(agent, sourceDir, mcp, opts) {
12556
13054
  const skillDir = agent === "claude" ? claudeSkillDir(opts.projectDir) : codexSkillDir();