@topolo/cli 0.6.2 → 0.6.5

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/README.md CHANGED
@@ -109,9 +109,13 @@ this too:
109
109
 
110
110
  ```bash
111
111
  topolo actions call app_topolo_mail.messages.send --data '{"mailboxId":"primary","subject":"Hello"}' --confirm
112
+ topolo actions upload app_topolo_brand.brand_assets.upload --file ./logo.png --data '{"assetId":"asset_1"}' --confirm
113
+ topolo actions upload app_topolo_chat.uploads.request --file ./launch.mp4 --data '{}' --complete-data '{"conversationKind":"channel","conversationId":"channel_1"}' --confirm
112
114
  topolo api mail POST /api/mail/mailboxes/primary/messages --data '{"subject":"Hello"}' --confirm
113
115
  ```
114
116
 
117
+ `actions upload` requires the resolved action to publish an upload contract. It reads the file, infers common MIME types or accepts `--content-type`, then follows either the declared inline-base64 mapping or the complete initiate/upload/finish URL-session flow. `--complete-data` supplies only fields required by a declared completion action; the upload/session id is mapped from the initiation response by contract.
118
+
115
119
  ## Commands
116
120
 
117
121
  ### `topolo whoami`
@@ -288,6 +288,7 @@ function emitWranglerToml(targetName, target) {
288
288
  lines.push(`directory = ${tomlString(target.assets.directory || target.outputDir || 'dist')}`);
289
289
  if (target.assets.binding) lines.push(`binding = ${tomlString(target.assets.binding)}`);
290
290
  else lines.push(`binding = "ASSETS"`);
291
+ if (target.assets.htmlHandling) lines.push(`html_handling = ${tomlString(target.assets.htmlHandling)}`);
291
292
  if (target.assets.notFoundHandling) lines.push(`not_found_handling = ${tomlString(target.assets.notFoundHandling)}`);
292
293
  if (target.assets.runWorkerFirst !== undefined) lines.push(`run_worker_first = ${tomlString(target.assets.runWorkerFirst)}`);
293
294
  lines.push('');
@@ -390,6 +391,7 @@ function emitWranglerToml(targetName, target) {
390
391
  lines.push(`[env.${env}.assets]`);
391
392
  lines.push(`directory = ${tomlString(target.assets.directory || target.outputDir || 'dist')}`);
392
393
  lines.push(`binding = ${tomlString(target.assets.binding || 'ASSETS')}`);
394
+ if (target.assets.htmlHandling) lines.push(`html_handling = ${tomlString(target.assets.htmlHandling)}`);
393
395
  if (target.assets.notFoundHandling) lines.push(`not_found_handling = ${tomlString(target.assets.notFoundHandling)}`);
394
396
  if (target.assets.runWorkerFirst !== undefined) lines.push(`run_worker_first = ${tomlString(target.assets.runWorkerFirst)}`);
395
397
  lines.push('');
@@ -572,6 +574,20 @@ function emitBindings(lines, bindings, env) {
572
574
  lines.push(`service = ${tomlString(service)}`);
573
575
  lines.push('');
574
576
  }
577
+ // Cloudflare Secrets Store bindings — the durable data-at-rest key escrow
578
+ // (docs/data-at-rest-encryption-architecture.md). Store ids differ per env
579
+ // (per-env Cloudflare accounts), so the shape mirrors d1/kv: per-env config
580
+ // objects under the binding.
581
+ for (const s of bindings.secretsStore || []) {
582
+ if (!bindingAppliesToEnv(s, env)) continue;
583
+ const cfg = s[env];
584
+ if (!cfg) continue;
585
+ lines.push(`[[env.${env}.secrets_store_secrets]]`);
586
+ lines.push(`binding = ${tomlString(s.binding)}`);
587
+ lines.push(`store_id = ${tomlString(cfg.storeId)}`);
588
+ lines.push(`secret_name = ${tomlString(cfg.secretName)}`);
589
+ lines.push('');
590
+ }
575
591
  for (const a of bindings.ai || []) {
576
592
  lines.push(`[env.${env}.ai]`);
577
593
  lines.push(`binding = ${tomlString(a.binding)}`);
@@ -595,7 +611,7 @@ function emitCloudcontrol() {
595
611
  deployableTargets.flatMap((target) => target.environments || ENVS),
596
612
  );
597
613
  cc.deploy_method = deployableTargets.length ? 'wrangler' : 'none';
598
- cc.ci_provider = existing.ci_provider || 'github_actions';
614
+ cc.ci_provider = existing.ci_provider || 'local';
599
615
  cc.quality_policy = existing.quality_policy || {
600
616
  profile: 'first_party_app',
601
617
  coverage: { mode: 'ratchet' },
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import { platform as platform2 } from "os";
9
9
  import { createInterface } from "readline/promises";
10
10
  import { stdin as input } from "process";
11
11
 
12
- // node_modules/@topolo/sdk/dist/index.js
12
+ // ../topolo-sdk/dist/index.js
13
13
  function applyAuthHeaders(headers, credential) {
14
14
  if (credential.kind === "api_key") {
15
15
  headers.set("X-Api-Key", credential.apiKey);
@@ -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,13 +96,17 @@ 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, path8, status, body, message, options = {}) {
102
+ super(extractHttpErrorCode(body), message ?? `HTTP ${status} from ${service}${path8}`);
66
103
  this.name = "TopoloHttpError";
67
104
  this.status = status;
68
105
  this.body = body;
69
106
  this.service = service;
70
107
  this.path = path8;
108
+ this.requestId = options.requestId;
109
+ this.retryAfter = options.retryAfter;
71
110
  }
72
111
  };
73
112
  var APP_API_URLS = {
@@ -137,7 +176,9 @@ var APPLICATIONS = {
137
176
  "domains": [
138
177
  "admin.topolo.app"
139
178
  ],
140
- "routes": [],
179
+ "routes": [
180
+ "admin.topolo.app"
181
+ ],
141
182
  "appId": null
142
183
  },
143
184
  {
@@ -185,7 +226,7 @@ var APPLICATIONS = {
185
226
  "appDir": "TopoloAuth",
186
227
  "name": "topolo-auth",
187
228
  "packageName": "topolo-auth",
188
- "productionUrl": "https://auth.topolo.app/login",
229
+ "productionUrl": "https://auth.topolo.app",
189
230
  "services": [
190
231
  "auth"
191
232
  ],
@@ -221,7 +262,9 @@ var APPLICATIONS = {
221
262
  "domains": [
222
263
  "backup.topolo.app"
223
264
  ],
224
- "routes": [],
265
+ "routes": [
266
+ "backup.topolo.app"
267
+ ],
225
268
  "appId": "backup"
226
269
  }
227
270
  ]
@@ -305,7 +348,9 @@ var APPLICATIONS = {
305
348
  "domains": [
306
349
  "bugfix.topolo.app"
307
350
  ],
308
- "routes": [],
351
+ "routes": [
352
+ "bugfix.topolo.app"
353
+ ],
309
354
  "appId": "bugfix"
310
355
  }
311
356
  ]
@@ -327,7 +372,9 @@ var APPLICATIONS = {
327
372
  "domains": [
328
373
  "bytes.topolo.app"
329
374
  ],
330
- "routes": [],
375
+ "routes": [
376
+ "bytes.topolo.app"
377
+ ],
331
378
  "appId": "bytes"
332
379
  },
333
380
  {
@@ -357,7 +404,9 @@ var APPLICATIONS = {
357
404
  "domains": [
358
405
  "calendar.topolo.app"
359
406
  ],
360
- "routes": [],
407
+ "routes": [
408
+ "calendar.topolo.app"
409
+ ],
361
410
  "appId": "calendar"
362
411
  },
363
412
  {
@@ -387,9 +436,7 @@ var APPLICATIONS = {
387
436
  "domains": [
388
437
  "campaigns.topolo.app"
389
438
  ],
390
- "routes": [
391
- "campaigns.topolo.app"
392
- ],
439
+ "routes": [],
393
440
  "appId": "campaigns"
394
441
  },
395
442
  {
@@ -419,7 +466,9 @@ var APPLICATIONS = {
419
466
  "domains": [
420
467
  "capacity.topolo.app"
421
468
  ],
422
- "routes": [],
469
+ "routes": [
470
+ "capacity.topolo.app"
471
+ ],
423
472
  "appId": "capacity"
424
473
  }
425
474
  ]
@@ -441,7 +490,9 @@ var APPLICATIONS = {
441
490
  "domains": [
442
491
  "chat.topolo.app"
443
492
  ],
444
- "routes": [],
493
+ "routes": [
494
+ "chat.topolo.app"
495
+ ],
445
496
  "appId": "chat"
446
497
  },
447
498
  {
@@ -479,7 +530,9 @@ var APPLICATIONS = {
479
530
  "domains": [
480
531
  "guest.topolo.app"
481
532
  ],
482
- "routes": [],
533
+ "routes": [
534
+ "guest.topolo.app"
535
+ ],
483
536
  "appId": null
484
537
  },
485
538
  {
@@ -489,7 +542,9 @@ var APPLICATIONS = {
489
542
  "domains": [
490
543
  "commerce.topolo.app"
491
544
  ],
492
- "routes": [],
545
+ "routes": [
546
+ "commerce.topolo.app"
547
+ ],
493
548
  "appId": "commerce"
494
549
  },
495
550
  {
@@ -499,7 +554,9 @@ var APPLICATIONS = {
499
554
  "domains": [
500
555
  "station-commerce.topolo.app"
501
556
  ],
502
- "routes": [],
557
+ "routes": [
558
+ "station-commerce.topolo.app"
559
+ ],
503
560
  "appId": null
504
561
  }
505
562
  ]
@@ -677,7 +734,9 @@ var APPLICATIONS = {
677
734
  "domains": [
678
735
  "docs.topolo.app"
679
736
  ],
680
- "routes": [],
737
+ "routes": [
738
+ "docs.topolo.app"
739
+ ],
681
740
  "appId": null
682
741
  },
683
742
  {
@@ -707,7 +766,9 @@ var APPLICATIONS = {
707
766
  "domains": [
708
767
  "feed-analytics-api.topolo.app"
709
768
  ],
710
- "routes": [],
769
+ "routes": [
770
+ "feed-analytics-api.topolo.app"
771
+ ],
711
772
  "appId": null
712
773
  },
713
774
  {
@@ -717,7 +778,9 @@ var APPLICATIONS = {
717
778
  "domains": [
718
779
  "feed-api.topolo.app"
719
780
  ],
720
- "routes": [],
781
+ "routes": [
782
+ "feed-api.topolo.app"
783
+ ],
721
784
  "appId": null
722
785
  },
723
786
  {
@@ -727,7 +790,9 @@ var APPLICATIONS = {
727
790
  "domains": [
728
791
  "feed.topolo.app"
729
792
  ],
730
- "routes": [],
793
+ "routes": [
794
+ "feed.topolo.app"
795
+ ],
731
796
  "appId": "feed"
732
797
  }
733
798
  ]
@@ -809,7 +874,9 @@ var APPLICATIONS = {
809
874
  "domains": [
810
875
  "forms.topolo.app"
811
876
  ],
812
- "routes": [],
877
+ "routes": [
878
+ "forms.topolo.app"
879
+ ],
813
880
  "appId": "forms"
814
881
  },
815
882
  {
@@ -839,7 +906,9 @@ var APPLICATIONS = {
839
906
  "domains": [
840
907
  "home.topolo.app"
841
908
  ],
842
- "routes": [],
909
+ "routes": [
910
+ "home.topolo.app"
911
+ ],
843
912
  "appId": "home"
844
913
  },
845
914
  {
@@ -929,7 +998,9 @@ var APPLICATIONS = {
929
998
  "domains": [
930
999
  "learn.topolo.app"
931
1000
  ],
932
- "routes": [],
1001
+ "routes": [
1002
+ "learn.topolo.app"
1003
+ ],
933
1004
  "appId": "learn"
934
1005
  },
935
1006
  {
@@ -983,7 +1054,9 @@ var APPLICATIONS = {
983
1054
  "domains": [
984
1055
  "mail.topolo.app"
985
1056
  ],
986
- "routes": [],
1057
+ "routes": [
1058
+ "mail.topolo.app"
1059
+ ],
987
1060
  "appId": "mail"
988
1061
  },
989
1062
  {
@@ -1021,7 +1094,9 @@ var APPLICATIONS = {
1021
1094
  "domains": [
1022
1095
  "mdm.topolo.app"
1023
1096
  ],
1024
- "routes": [],
1097
+ "routes": [
1098
+ "mdm.topolo.app"
1099
+ ],
1025
1100
  "appId": "mdm"
1026
1101
  }
1027
1102
  ]
@@ -1043,7 +1118,9 @@ var APPLICATIONS = {
1043
1118
  "domains": [
1044
1119
  "messages.topolo.app"
1045
1120
  ],
1046
- "routes": [],
1121
+ "routes": [
1122
+ "messages.topolo.app"
1123
+ ],
1047
1124
  "appId": "messages"
1048
1125
  },
1049
1126
  {
@@ -1077,11 +1154,13 @@ var APPLICATIONS = {
1077
1154
  {
1078
1155
  "name": "topolo-nexus-dashboard",
1079
1156
  "kind": "worker",
1080
- "apiUrl": null,
1157
+ "apiUrl": "https://nexus.topolo.app",
1081
1158
  "domains": [
1082
1159
  "nexus.topolo.app"
1083
1160
  ],
1084
- "routes": [],
1161
+ "routes": [
1162
+ "nexus.topolo.app"
1163
+ ],
1085
1164
  "appId": null
1086
1165
  },
1087
1166
  {
@@ -1137,7 +1216,9 @@ var APPLICATIONS = {
1137
1216
  "domains": [
1138
1217
  "observability-watch.topolo.app"
1139
1218
  ],
1140
- "routes": [],
1219
+ "routes": [
1220
+ "observability-watch.topolo.app"
1221
+ ],
1141
1222
  "appId": "observability_watch"
1142
1223
  }
1143
1224
  ]
@@ -1209,7 +1290,9 @@ var APPLICATIONS = {
1209
1290
  "domains": [
1210
1291
  "p2p.topolo.app"
1211
1292
  ],
1212
- "routes": [],
1293
+ "routes": [
1294
+ "p2p.topolo.app"
1295
+ ],
1213
1296
  "appId": "p2p"
1214
1297
  },
1215
1298
  {
@@ -1324,7 +1407,9 @@ var APPLICATIONS = {
1324
1407
  "domains": [
1325
1408
  "quro.topolo.app"
1326
1409
  ],
1327
- "routes": [],
1410
+ "routes": [
1411
+ "quro.topolo.app"
1412
+ ],
1328
1413
  "appId": null
1329
1414
  }
1330
1415
  ]
@@ -1406,7 +1491,9 @@ var APPLICATIONS = {
1406
1491
  "domains": [
1407
1492
  "studio.topolo.app"
1408
1493
  ],
1409
- "routes": [],
1494
+ "routes": [
1495
+ "studio.topolo.app"
1496
+ ],
1410
1497
  "appId": "social_studio"
1411
1498
  },
1412
1499
  {
@@ -1444,7 +1531,9 @@ var APPLICATIONS = {
1444
1531
  "domains": [
1445
1532
  "socialize.topolo.app"
1446
1533
  ],
1447
- "routes": [],
1534
+ "routes": [
1535
+ "socialize.topolo.app"
1536
+ ],
1448
1537
  "appId": "socialize"
1449
1538
  },
1450
1539
  {
@@ -1454,7 +1543,9 @@ var APPLICATIONS = {
1454
1543
  "domains": [
1455
1544
  "socialize-api.topolo.app"
1456
1545
  ],
1457
- "routes": [],
1546
+ "routes": [
1547
+ "socialize-api.topolo.app"
1548
+ ],
1458
1549
  "appId": null
1459
1550
  },
1460
1551
  {
@@ -1514,7 +1605,9 @@ var APPLICATIONS = {
1514
1605
  "domains": [
1515
1606
  "status.topolo.app"
1516
1607
  ],
1517
- "routes": [],
1608
+ "routes": [
1609
+ "status.topolo.app"
1610
+ ],
1518
1611
  "appId": "status"
1519
1612
  }
1520
1613
  ]
@@ -1600,7 +1693,9 @@ var APPLICATIONS = {
1600
1693
  "domains": [
1601
1694
  "survey.topolo.app"
1602
1695
  ],
1603
- "routes": [],
1696
+ "routes": [
1697
+ "survey.topolo.app"
1698
+ ],
1604
1699
  "appId": "survey"
1605
1700
  },
1606
1701
  {
@@ -1630,7 +1725,9 @@ var APPLICATIONS = {
1630
1725
  "domains": [
1631
1726
  "voice.topolo.app"
1632
1727
  ],
1633
- "routes": [],
1728
+ "routes": [
1729
+ "voice.topolo.app"
1730
+ ],
1634
1731
  "appId": "voice"
1635
1732
  },
1636
1733
  {
@@ -1681,7 +1778,9 @@ var APPLICATIONS = {
1681
1778
  "domains": [
1682
1779
  "web.topolo.app"
1683
1780
  ],
1684
- "routes": [],
1781
+ "routes": [
1782
+ "web.topolo.app"
1783
+ ],
1685
1784
  "appId": "web"
1686
1785
  }
1687
1786
  ]
@@ -1758,7 +1857,7 @@ var APPLICATION_REQUIREMENTS = [
1758
1857
  "Use TopoloAppMetricCard, TopoloAppEmptyState, TopoloAppStatusBadge, TopoloAppGrid, and TopoloAppSection for repeated app body patterns",
1759
1858
  "Keep visual QA fixtures available through visualQa=1 or __devpreview=1 so screenshot checks do not depend on live API state",
1760
1859
  "Keep mobile and desktop shell behavior aligned with the platform UI kit",
1761
- "Constrain app-owned workspace wrappers with min-width: 0, width: auto, and viewport-aware max-width so shell padding cannot create page-level horizontal overflow"
1860
+ "Constrain the app workspace-view layout wrappers with min-width: 0, width: auto, and viewport-aware max-width so shell padding cannot create page-level horizontal overflow"
1762
1861
  ]
1763
1862
  },
1764
1863
  {
@@ -2387,6 +2486,166 @@ function isMultipleOf(value, divisor) {
2387
2486
  const quotient = value / divisor;
2388
2487
  return Math.abs(quotient - Math.round(quotient)) < Number.EPSILON * Math.max(1, Math.abs(quotient)) * 4;
2389
2488
  }
2489
+ function parseTopoloActionUploadContract(value) {
2490
+ if (!isRecord2(value) || value.profile !== "inline-base64" && value.profile !== "url-session") {
2491
+ throw new TopoloSdkError("upload_contract_unavailable", "This action does not publish a supported upload contract.");
2492
+ }
2493
+ const contentTypeField = requiredField(value.contentTypeField, "contentTypeField");
2494
+ const fileNameField = requiredField(value.fileNameField, "fileNameField");
2495
+ const maxBytes = Number(value.maxBytes);
2496
+ const acceptedContentTypes = Array.isArray(value.acceptedContentTypes) ? value.acceptedContentTypes.map((item) => String(item).trim().toLowerCase()).filter(Boolean) : [];
2497
+ if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0 || acceptedContentTypes.length === 0) {
2498
+ throw new TopoloSdkError("invalid_upload_contract", "The action upload contract must declare maxBytes and acceptedContentTypes.");
2499
+ }
2500
+ if (value.profile === "inline-base64") {
2501
+ return {
2502
+ profile: "inline-base64",
2503
+ contentField: requiredField(value.contentField, "contentField"),
2504
+ contentTypeField,
2505
+ fileNameField,
2506
+ maxBytes,
2507
+ acceptedContentTypes
2508
+ };
2509
+ }
2510
+ const uploadMethod = value.uploadMethod === "PUT" || value.uploadMethod === "POST" ? value.uploadMethod : null;
2511
+ const uploadUrlPath = requiredPath(value.uploadUrlPath, "uploadUrlPath");
2512
+ const complete = value.complete === void 0 ? void 0 : parseCompleteContract(value.complete);
2513
+ if (!uploadMethod) throw new TopoloSdkError("invalid_upload_contract", "The action upload contract has an invalid uploadMethod.");
2514
+ return {
2515
+ profile: "url-session",
2516
+ fileNameField,
2517
+ contentTypeField,
2518
+ sizeField: requiredField(value.sizeField, "sizeField"),
2519
+ maxBytes,
2520
+ acceptedContentTypes,
2521
+ uploadUrlPath,
2522
+ uploadMethod,
2523
+ ...complete ? { complete } : {}
2524
+ };
2525
+ }
2526
+ function prepareTopoloActionUploadInput(contractValue, content, input3, options) {
2527
+ const contract = parseTopoloActionUploadContract(contractValue);
2528
+ if (contract.profile !== "inline-base64") throw unsupportedInlineProfile();
2529
+ const bytes = content instanceof Uint8Array ? content : new Uint8Array(content);
2530
+ if (bytes.byteLength === 0) {
2531
+ throw new TopoloSdkError("invalid_upload_content", "Upload content must not be empty.");
2532
+ }
2533
+ if (bytes.byteLength > contract.maxBytes) {
2534
+ throw new TopoloSdkError("upload_too_large", `Upload content exceeds the ${contract.maxBytes}-byte action limit.`);
2535
+ }
2536
+ return mapUploadInput(contract, bytesToBase64(bytes), input3, options);
2537
+ }
2538
+ function prepareTopoloActionBase64UploadInput(contractValue, contentBase64, input3, options) {
2539
+ const contract = parseTopoloActionUploadContract(contractValue);
2540
+ if (contract.profile !== "inline-base64") throw unsupportedInlineProfile();
2541
+ if (!isCanonicalBase64(contentBase64)) {
2542
+ throw new TopoloSdkError("invalid_upload_content", "Upload content must be canonical base64.");
2543
+ }
2544
+ const padding = contentBase64.endsWith("==") ? 2 : contentBase64.endsWith("=") ? 1 : 0;
2545
+ const byteLength = contentBase64.length / 4 * 3 - padding;
2546
+ if (byteLength === 0) throw new TopoloSdkError("invalid_upload_content", "Upload content must not be empty.");
2547
+ if (byteLength > contract.maxBytes) {
2548
+ throw new TopoloSdkError("upload_too_large", `Upload content exceeds the ${contract.maxBytes}-byte action limit.`);
2549
+ }
2550
+ return mapUploadInput(contract, contentBase64, input3, options);
2551
+ }
2552
+ function prepareTopoloUrlSessionUploadInput(contractValue, byteLength, input3, options) {
2553
+ const contract = parseTopoloActionUploadContract(contractValue);
2554
+ if (contract.profile !== "url-session") {
2555
+ throw new TopoloSdkError("invalid_upload_contract", "This action does not publish a URL-session upload contract.");
2556
+ }
2557
+ validateUploadMetadata(contract, byteLength, options);
2558
+ return {
2559
+ contract,
2560
+ input: {
2561
+ ...input3,
2562
+ [contract.fileNameField]: options.fileName.trim(),
2563
+ [contract.contentTypeField]: options.contentType.trim().toLowerCase(),
2564
+ [contract.sizeField]: byteLength
2565
+ }
2566
+ };
2567
+ }
2568
+ function topoloUploadResponseValue(response, path8) {
2569
+ return path8.split(".").reduce((value, segment) => isRecord2(value) ? value[segment] : void 0, response);
2570
+ }
2571
+ function decodeTopoloActionBase64(contentBase64) {
2572
+ if (!isCanonicalBase64(contentBase64)) {
2573
+ throw new TopoloSdkError("invalid_upload_content", "Upload content must be canonical base64.");
2574
+ }
2575
+ const binary = atob(contentBase64);
2576
+ return Uint8Array.from(binary, (character) => character.charCodeAt(0));
2577
+ }
2578
+ function mapUploadInput(contract, contentBase64, input3, options) {
2579
+ validateUploadMetadata(contract, Math.max(1, decodedBase64Size(contentBase64)), options);
2580
+ const fileName = options.fileName.trim();
2581
+ const contentType = options.contentType.trim().toLowerCase();
2582
+ return {
2583
+ ...input3,
2584
+ [contract.contentField]: contentBase64,
2585
+ [contract.contentTypeField]: contentType,
2586
+ [contract.fileNameField]: fileName
2587
+ };
2588
+ }
2589
+ function validateUploadMetadata(contract, byteLength, options) {
2590
+ const fileName = options.fileName.trim();
2591
+ const contentType = options.contentType.trim().toLowerCase();
2592
+ if (!fileName) throw new TopoloSdkError("invalid_upload_file_name", "Upload fileName must not be empty.");
2593
+ if (!Number.isSafeInteger(byteLength) || byteLength <= 0) {
2594
+ throw new TopoloSdkError("invalid_upload_content", "Upload content must not be empty.");
2595
+ }
2596
+ if (byteLength > contract.maxBytes) {
2597
+ throw new TopoloSdkError("upload_too_large", `Upload content exceeds the ${contract.maxBytes}-byte action limit.`);
2598
+ }
2599
+ if (!matchesContentType(contentType, contract.acceptedContentTypes)) {
2600
+ throw new TopoloSdkError("unsupported_upload_content_type", `Content type "${contentType}" is not accepted by this action.`);
2601
+ }
2602
+ }
2603
+ function decodedBase64Size(contentBase64) {
2604
+ const padding = contentBase64.endsWith("==") ? 2 : contentBase64.endsWith("=") ? 1 : 0;
2605
+ return contentBase64.length / 4 * 3 - padding;
2606
+ }
2607
+ function parseCompleteContract(value) {
2608
+ if (!isRecord2(value)) throw new TopoloSdkError("invalid_upload_contract", "The action upload completion contract is invalid.");
2609
+ return {
2610
+ actionId: requiredPath(value.actionId, "complete.actionId"),
2611
+ inputField: requiredField(value.inputField, "complete.inputField"),
2612
+ responsePath: requiredPath(value.responsePath, "complete.responsePath")
2613
+ };
2614
+ }
2615
+ function requiredPath(value, name) {
2616
+ const path8 = typeof value === "string" ? value.trim() : "";
2617
+ if (!/^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*$/.test(path8)) {
2618
+ throw new TopoloSdkError("invalid_upload_contract", `The action upload contract has an invalid ${name}.`);
2619
+ }
2620
+ return path8;
2621
+ }
2622
+ function unsupportedInlineProfile() {
2623
+ return new TopoloSdkError("invalid_upload_contract", "This action does not publish an inline-base64 upload contract.");
2624
+ }
2625
+ function isCanonicalBase64(value) {
2626
+ 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);
2627
+ }
2628
+ function requiredField(value, name) {
2629
+ const field = typeof value === "string" ? value.trim() : "";
2630
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(field)) {
2631
+ throw new TopoloSdkError("invalid_upload_contract", `The action upload contract has an invalid ${name}.`);
2632
+ }
2633
+ return field;
2634
+ }
2635
+ function matchesContentType(contentType, accepted) {
2636
+ return accepted.some((candidate) => candidate === "*/*" || candidate === contentType || candidate.endsWith("/*") && contentType.startsWith(candidate.slice(0, -1)));
2637
+ }
2638
+ function bytesToBase64(bytes) {
2639
+ let binary = "";
2640
+ const chunkSize = 32768;
2641
+ for (let offset = 0; offset < bytes.length; offset += chunkSize) {
2642
+ binary += String.fromCharCode(...bytes.subarray(offset, offset + chunkSize));
2643
+ }
2644
+ return btoa(binary);
2645
+ }
2646
+ function isRecord2(value) {
2647
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
2648
+ }
2390
2649
  var WRITE_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
2391
2650
  var TOPOLO_ACTION_ID_HEADER = "X-Topolo-Action-Id";
2392
2651
  var TOPOLO_ACTION_NAME_HEADER = "X-Topolo-Action-Name";
@@ -2463,6 +2722,9 @@ var TopoloClient = class _TopoloClient {
2463
2722
  if (v !== void 0) url.searchParams.set(k, String(v));
2464
2723
  }
2465
2724
  }
2725
+ if (opts.body !== void 0 && opts.rawBody !== void 0) {
2726
+ throw new TopoloSdkError("invalid_request_body", "body and rawBody are mutually exclusive.");
2727
+ }
2466
2728
  const headers = new Headers();
2467
2729
  headers.set("Accept", "application/json");
2468
2730
  if (opts.body !== void 0) headers.set("Content-Type", "application/json");
@@ -2497,7 +2759,7 @@ var TopoloClient = class _TopoloClient {
2497
2759
  return await this.fetchImpl(urlStr, {
2498
2760
  method,
2499
2761
  headers: new Headers(headers),
2500
- body: opts.body !== void 0 ? JSON.stringify(opts.body) : null,
2762
+ body: opts.rawBody ?? (opts.body !== void 0 ? JSON.stringify(opts.body) : null),
2501
2763
  signal
2502
2764
  });
2503
2765
  } finally {
@@ -2545,14 +2807,32 @@ var TopoloClient = class _TopoloClient {
2545
2807
  error: message,
2546
2808
  status: res.status
2547
2809
  });
2548
- if (res.status === 401) throw new TopoloAuthError(describeError(parsed, "Unauthorized"));
2810
+ const errorContext = {
2811
+ status: res.status,
2812
+ body: parsed,
2813
+ service: resolvedService.serviceKey,
2814
+ path: opts.path,
2815
+ requestId: res.headers.get("X-Topolo-Request-Id") || requestId,
2816
+ retryAfter: res.headers.get("Retry-After") || void 0
2817
+ };
2818
+ if (res.status === 401) {
2819
+ throw new TopoloAuthError(describeError(parsed, "Unauthorized"), "auth_error", errorContext);
2820
+ }
2549
2821
  if (res.status === 403) {
2550
2822
  throw new TopoloPermissionError(
2551
2823
  describeError(parsed, "Permission denied"),
2552
- extractRequired(parsed)
2824
+ extractRequired(parsed),
2825
+ errorContext
2553
2826
  );
2554
2827
  }
2555
- throw new TopoloHttpError(resolvedService.serviceKey, opts.path, res.status, parsed);
2828
+ throw new TopoloHttpError(
2829
+ resolvedService.serviceKey,
2830
+ opts.path,
2831
+ res.status,
2832
+ parsed,
2833
+ message,
2834
+ errorContext
2835
+ );
2556
2836
  }
2557
2837
  this.emit({
2558
2838
  phase: "response",
@@ -2711,7 +2991,7 @@ var TopoloClient = class _TopoloClient {
2711
2991
  }
2712
2992
  const entries = [];
2713
2993
  for (const item of raw) {
2714
- if (!isRecord2(item)) continue;
2994
+ if (!isRecord3(item)) continue;
2715
2995
  const s = item;
2716
2996
  const appId = typeof s.app_id === "string" ? s.app_id : typeof s.id === "string" ? s.id : null;
2717
2997
  if (!appId) continue;
@@ -2786,8 +3066,8 @@ var TopoloClient = class _TopoloClient {
2786
3066
  "app_topolo_auth.service_api_key_resources.list",
2787
3067
  { appId: service.appId }
2788
3068
  );
2789
- const payload = isRecord2(response) && isRecord2(response.data) ? response.data : response;
2790
- if (!isRecord2(payload) || !Array.isArray(payload.resources)) {
3069
+ const payload = isRecord3(response) && isRecord3(response.data) ? response.data : response;
3070
+ if (!isRecord3(payload) || !Array.isArray(payload.resources)) {
2791
3071
  throw new TopoloSdkError("resource_catalog_unavailable", "TopoloAuth did not return bindable resources.");
2792
3072
  }
2793
3073
  return {
@@ -2893,6 +3173,71 @@ var TopoloClient = class _TopoloClient {
2893
3173
  const entry = await this.getAction(action);
2894
3174
  return this.executeResolvedAction(entry, input3, options);
2895
3175
  }
3176
+ async uploadAction(action, content, input3, options) {
3177
+ const entry = await this.getAction(action);
3178
+ const contract = parseTopoloActionUploadContract(entry.agentContract?.upload);
3179
+ if (contract.profile === "url-session") {
3180
+ const bytes = content instanceof Uint8Array ? content : new Uint8Array(content);
3181
+ return this.executeUrlSessionUpload(entry, contract, bytes, input3, options);
3182
+ }
3183
+ const uploadInput = prepareTopoloActionUploadInput(entry.agentContract?.upload, content, input3, options);
3184
+ return this.executeResolvedAction(entry, uploadInput, options);
3185
+ }
3186
+ async uploadActionBase64(action, contentBase64, input3, options) {
3187
+ const entry = await this.getAction(action);
3188
+ const contract = parseTopoloActionUploadContract(entry.agentContract?.upload);
3189
+ if (contract.profile === "url-session") {
3190
+ return this.executeUrlSessionUpload(entry, contract, decodeTopoloActionBase64(contentBase64), input3, options);
3191
+ }
3192
+ const uploadInput = prepareTopoloActionBase64UploadInput(entry.agentContract?.upload, contentBase64, input3, options);
3193
+ return this.executeResolvedAction(entry, uploadInput, options);
3194
+ }
3195
+ async executeUrlSessionUpload(entry, contract, bytes, input3, options) {
3196
+ const prepared = prepareTopoloUrlSessionUploadInput(contract, bytes.byteLength, input3, options);
3197
+ const initiation = await this.executeResolvedAction(entry, prepared.input, options);
3198
+ const uploadUrl = topoloUploadResponseValue(initiation, contract.uploadUrlPath);
3199
+ if (typeof uploadUrl !== "string" || !uploadUrl.trim()) {
3200
+ throw new TopoloSdkError("invalid_upload_response", `Upload action response is missing ${contract.uploadUrlPath}.`);
3201
+ }
3202
+ await this.putUrlSessionContent(entry, uploadUrl, contract.uploadMethod, bytes, options);
3203
+ if (!contract.complete) return initiation;
3204
+ const completionValue = topoloUploadResponseValue(initiation, contract.complete.responsePath);
3205
+ if (typeof completionValue !== "string" || !completionValue.trim()) {
3206
+ throw new TopoloSdkError("invalid_upload_response", `Upload action response is missing ${contract.complete.responsePath}.`);
3207
+ }
3208
+ const completion = await this.getAction(contract.complete.actionId);
3209
+ return this.executeResolvedAction(completion, {
3210
+ ...options.completionInput ?? {},
3211
+ [contract.complete.inputField]: completionValue
3212
+ }, options);
3213
+ }
3214
+ async putUrlSessionContent(entry, uploadUrl, method, bytes, options) {
3215
+ const service = entry.appSlug || entry.appId;
3216
+ const resolved = entry.apiBaseUrl ? { serviceKey: normalizeLookupKey(service), baseUrl: requireHttpOrigin(entry.apiBaseUrl) } : await this.resolveRequestService(service);
3217
+ const target = new URL(uploadUrl, resolved.baseUrl.endsWith("/") ? resolved.baseUrl : `${resolved.baseUrl}/`);
3218
+ const body = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
3219
+ if (target.origin === new URL(resolved.baseUrl).origin) {
3220
+ await this.request({
3221
+ service,
3222
+ baseUrl: resolved.baseUrl,
3223
+ path: target.toString(),
3224
+ method,
3225
+ rawBody: body,
3226
+ headers: { "Content-Type": options.contentType.trim().toLowerCase() },
3227
+ resource: options.resource,
3228
+ confirm: options.confirm
3229
+ });
3230
+ return;
3231
+ }
3232
+ const response = await this.fetchImpl(target, {
3233
+ method,
3234
+ headers: { "Content-Type": options.contentType.trim().toLowerCase() },
3235
+ body
3236
+ });
3237
+ if (!response.ok) {
3238
+ throw new TopoloHttpError(service, target.pathname, response.status, await response.text(), `Upload transport failed with HTTP ${response.status}.`);
3239
+ }
3240
+ }
2896
3241
  async validateActionInput(action, input3 = {}) {
2897
3242
  const entry = await this.getAction(action);
2898
3243
  return { action: entry, validation: validateTopoloActionInput(entry.inputSchema, input3) };
@@ -3051,7 +3396,7 @@ function resolveConfiguredAppId(slug, appIds) {
3051
3396
  return null;
3052
3397
  }
3053
3398
  function normalizeApiKeyResourceMetadata(input3) {
3054
- if (!isRecord2(input3)) return null;
3399
+ if (!isRecord3(input3)) return null;
3055
3400
  const resourceType = readString(input3.resourceType ?? input3.resource_type);
3056
3401
  const label = readString(input3.label);
3057
3402
  const emptyLabel = readString(input3.emptyLabel ?? input3.empty_label);
@@ -3085,7 +3430,7 @@ function normalizeApiKeyResourceMetadataList(input3) {
3085
3430
  return resources;
3086
3431
  }
3087
3432
  function normalizeBindableResource(input3) {
3088
- if (!isRecord2(input3)) return null;
3433
+ if (!isRecord3(input3)) return null;
3089
3434
  const resourceType = readString(input3.resourceType ?? input3.resource_type);
3090
3435
  const resourceId = readString(input3.resourceId ?? input3.resource_id);
3091
3436
  if (!resourceType || !resourceId) return null;
@@ -3096,13 +3441,13 @@ function normalizeBindableResource(input3) {
3096
3441
  slug: readOptionalString(input3.slug),
3097
3442
  status: readOptionalString(input3.status),
3098
3443
  organizationId: readOptionalString(input3.organizationId ?? input3.organization_id),
3099
- metadata: isRecord2(input3.metadata) ? input3.metadata : {}
3444
+ metadata: isRecord3(input3.metadata) ? input3.metadata : {}
3100
3445
  };
3101
3446
  }
3102
3447
  function normalizeResourceBindings(input3) {
3103
3448
  if (!Array.isArray(input3)) return [];
3104
3449
  return input3.flatMap((binding) => {
3105
- if (!isRecord2(binding)) return [];
3450
+ if (!isRecord3(binding)) return [];
3106
3451
  const resourceType = readString(binding.resourceType ?? binding.resource_type);
3107
3452
  const resourceId = readString(binding.resourceId ?? binding.resource_id);
3108
3453
  return resourceType && resourceId ? [{ resourceType, resourceId }] : [];
@@ -3115,7 +3460,7 @@ function readOptionalString(value) {
3115
3460
  const normalized = readString(value);
3116
3461
  return normalized || null;
3117
3462
  }
3118
- function isRecord2(value) {
3463
+ function isRecord3(value) {
3119
3464
  return typeof value === "object" && value !== null && !Array.isArray(value);
3120
3465
  }
3121
3466
  function resolveConfiguredServiceUrl(slug, serviceUrls) {
@@ -3187,14 +3532,14 @@ function catalogSearchQuery(options) {
3187
3532
  return query;
3188
3533
  }
3189
3534
  function normalizeCatalogPage(value) {
3190
- if (!isRecord2(value)) return { nextCursor: null, hasMore: false };
3535
+ if (!isRecord3(value)) return { nextCursor: null, hasMore: false };
3191
3536
  return {
3192
3537
  nextCursor: typeof value.nextCursor === "string" && value.nextCursor ? value.nextCursor : null,
3193
3538
  hasMore: value.hasMore === true
3194
3539
  };
3195
3540
  }
3196
3541
  function normalizeServiceCatalogEntry(item) {
3197
- if (!isRecord2(item)) return null;
3542
+ if (!isRecord3(item)) return null;
3198
3543
  const appId = typeof item.app_id === "string" ? item.app_id : typeof item.id === "string" ? item.id : null;
3199
3544
  if (!appId) return null;
3200
3545
  const permissions = Array.isArray(item.granted_permissions) ? item.granted_permissions.filter(
@@ -3504,7 +3849,7 @@ function createTopolo(options) {
3504
3849
  };
3505
3850
  }
3506
3851
 
3507
- // node_modules/@topolo/sdk/dist/node/credential-store.js
3852
+ // ../topolo-sdk/dist/node/credential-store.js
3508
3853
  import { mkdir, readFile, writeFile, chmod } from "fs/promises";
3509
3854
  import { existsSync } from "fs";
3510
3855
  import { homedir, platform } from "os";
@@ -3518,23 +3863,50 @@ var TopoloSdkError2 = class extends Error {
3518
3863
  }
3519
3864
  };
3520
3865
  var TopoloAuthError2 = class extends TopoloSdkError2 {
3521
- constructor(message, code = "auth_error") {
3866
+ status;
3867
+ body;
3868
+ service;
3869
+ path;
3870
+ requestId;
3871
+ retryAfter;
3872
+ constructor(message, code = "auth_error", options = {}) {
3522
3873
  super(code, message);
3523
3874
  this.name = "TopoloAuthError";
3875
+ this.status = options.status ?? 401;
3876
+ this.body = options.body;
3877
+ this.service = options.service;
3878
+ this.path = options.path;
3879
+ this.requestId = options.requestId;
3880
+ this.retryAfter = options.retryAfter;
3524
3881
  }
3525
3882
  };
3883
+ function extractHttpErrorCode2(body) {
3884
+ if (!body || typeof body !== "object" || Array.isArray(body)) return "http_error";
3885
+ const record4 = body;
3886
+ if (typeof record4.code === "string" && record4.code.trim()) return record4.code.trim();
3887
+ const nested = record4.error;
3888
+ if (nested && typeof nested === "object" && !Array.isArray(nested)) {
3889
+ const code = nested.code;
3890
+ if (typeof code === "string" && code.trim()) return code.trim();
3891
+ }
3892
+ return "http_error";
3893
+ }
3526
3894
  var TopoloHttpError2 = class extends TopoloSdkError2 {
3527
3895
  status;
3528
3896
  body;
3529
3897
  service;
3530
3898
  path;
3531
- constructor(service, path8, status, body, message) {
3532
- super("http_error", message ?? `HTTP ${status} from ${service}${path8}`);
3899
+ requestId;
3900
+ retryAfter;
3901
+ constructor(service, path8, status, body, message, options = {}) {
3902
+ super(extractHttpErrorCode2(body), message ?? `HTTP ${status} from ${service}${path8}`);
3533
3903
  this.name = "TopoloHttpError";
3534
3904
  this.status = status;
3535
3905
  this.body = body;
3536
3906
  this.service = service;
3537
3907
  this.path = path8;
3908
+ this.requestId = options.requestId;
3909
+ this.retryAfter = options.retryAfter;
3538
3910
  }
3539
3911
  };
3540
3912
  var APP_API_URLS2 = {
@@ -4011,6 +4383,10 @@ function fail(err, mode) {
4011
4383
  process.stderr.write(`Error: ${payload.message}
4012
4384
  `);
4013
4385
  if (payload.code) process.stderr.write(`Code: ${payload.code}
4386
+ `);
4387
+ if (payload.status) process.stderr.write(`HTTP: ${payload.status}
4388
+ `);
4389
+ if (payload.requestId) process.stderr.write(`Request: ${payload.requestId}
4014
4390
  `);
4015
4391
  if (payload.hint) process.stderr.write(`Hint: ${payload.hint}
4016
4392
  `);
@@ -4029,6 +4405,12 @@ function normalizeError(err) {
4029
4405
  return {
4030
4406
  message: err.message,
4031
4407
  code,
4408
+ status: err.status,
4409
+ service: err.service,
4410
+ path: err.path,
4411
+ requestId: err.requestId,
4412
+ retryAfter: err.retryAfter,
4413
+ details: err.body,
4032
4414
  hint: code ? hintMap[code] : void 0,
4033
4415
  exitCode: code === "auth_error" || code === "confirmation_required" ? 2 : code === "permission_denied" ? 3 : code === "immutable_drift" ? 4 : 1
4034
4416
  };
@@ -4397,6 +4779,7 @@ function formatPretty(info) {
4397
4779
 
4398
4780
  // src/commands/actions.ts
4399
4781
  import { readFileSync as readFileSync2 } from "fs";
4782
+ import { basename, extname } from "path";
4400
4783
 
4401
4784
  // src/commands/api.ts
4402
4785
  var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
@@ -4551,6 +4934,30 @@ function registerActionsCommand(program2) {
4551
4934
  fail(err, mode);
4552
4935
  }
4553
4936
  });
4937
+ 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) => {
4938
+ const mode = resolveOutputMode(opts.json ?? command.parent?.opts().json);
4939
+ try {
4940
+ const topolo = await requiredClient();
4941
+ const contentType = opts.contentType?.trim().toLowerCase() || inferContentType(opts.file);
4942
+ if (!contentType) throw new Error("--content-type is required for this file extension");
4943
+ const resource = parseResourceOption(opts.resourceType, opts.resourceId);
4944
+ const result = await topolo.client.uploadAction(
4945
+ action,
4946
+ readFileSync2(opts.file),
4947
+ parseJsonObject(opts.data || "{}"),
4948
+ {
4949
+ fileName: basename(opts.file),
4950
+ contentType,
4951
+ completionInput: parseJsonObject(opts.completeData || "{}"),
4952
+ confirm: opts.confirm === true,
4953
+ ...resource ? { resource } : {}
4954
+ }
4955
+ );
4956
+ emit(result, mode, () => JSON.stringify(result, null, 2));
4957
+ } catch (err) {
4958
+ fail(err, mode);
4959
+ }
4960
+ });
4554
4961
  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) => {
4555
4962
  const mode = resolveOutputMode(opts.json ?? command.parent?.opts().json);
4556
4963
  try {
@@ -4631,6 +5038,21 @@ function parseJsonObject(value) {
4631
5038
  }
4632
5039
  return parsed;
4633
5040
  }
5041
+ function inferContentType(path8) {
5042
+ return {
5043
+ ".avif": "image/avif",
5044
+ ".gif": "image/gif",
5045
+ ".heic": "image/heic",
5046
+ ".jpeg": "image/jpeg",
5047
+ ".jpg": "image/jpeg",
5048
+ ".mov": "video/quicktime",
5049
+ ".mp4": "video/mp4",
5050
+ ".pdf": "application/pdf",
5051
+ ".png": "image/png",
5052
+ ".svg": "image/svg+xml",
5053
+ ".webp": "image/webp"
5054
+ }[extname(path8).toLowerCase()] ?? null;
5055
+ }
4634
5056
  function formatActionRows(rows, nextCursor = null) {
4635
5057
  if (rows.length === 0) return "No actions available for this credential.";
4636
5058
  const serviceWidth = Math.max(7, ...rows.map((row) => (row.appSlug || row.appId).length));
@@ -11692,16 +12114,16 @@ function scanStagingProductionUrls(rootInput, allowPatterns) {
11692
12114
  const allowlist = allowPatterns.map((pattern) => new RegExp(pattern));
11693
12115
  const violations = [];
11694
12116
  function shouldSkipFile(filePath) {
11695
- const basename = path7.basename(filePath);
12117
+ const basename2 = path7.basename(filePath);
11696
12118
  const relativePath2 = path7.relative(root, filePath);
11697
- if (basename === "package-lock.json" || basename === "pnpm-lock.yaml" || basename === "yarn.lock") return true;
11698
- if (basename === "topolo.cloudcontrol.json") return true;
11699
- if (basename === "wrangler.toml" || basename === "wrangler.jsonc") return true;
11700
- if (basename === "consumer-graph.json") return true;
11701
- if (/^cloudcontrol-deployment-report-.+\.json$/.test(basename)) return true;
11702
- if (basename.endsWith(".generated.ts")) return true;
11703
- if (basename.endsWith(".test.ts") || basename.endsWith(".test.tsx") || basename.endsWith(".spec.ts")) return true;
11704
- if (basename === ".env.production") return true;
12119
+ if (basename2 === "package-lock.json" || basename2 === "pnpm-lock.yaml" || basename2 === "yarn.lock") return true;
12120
+ if (basename2 === "topolo.cloudcontrol.json") return true;
12121
+ if (basename2 === "wrangler.toml" || basename2 === "wrangler.jsonc") return true;
12122
+ if (basename2 === "consumer-graph.json") return true;
12123
+ if (/^cloudcontrol-deployment-report-.+\.json$/.test(basename2)) return true;
12124
+ if (basename2.endsWith(".generated.ts")) return true;
12125
+ if (basename2.endsWith(".test.ts") || basename2.endsWith(".test.tsx") || basename2.endsWith(".spec.ts")) return true;
12126
+ if (basename2 === ".env.production") return true;
11705
12127
  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;
11706
12128
  if (relativePath2.includes(`${path7.sep}test${path7.sep}`) || relativePath2.includes(`${path7.sep}tests${path7.sep}`) || relativePath2.includes(`${path7.sep}e2e${path7.sep}`)) return true;
11707
12129
  if (relativePath2.startsWith(`scripts${path7.sep}`) || relativePath2.includes(`${path7.sep}scripts${path7.sep}`)) return true;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@topolo/cli",
3
- "version": "0.6.2",
4
- "topoloPublishedVersion": "0.6.2",
3
+ "version": "0.6.5",
4
+ "topoloPublishedVersion": "0.6.5",
5
5
  "description": "Command-line interface for Topolo developers, operators, and agents, with internal first-party scaffolding available under the topolo platform namespace.",
6
6
  "type": "module",
7
7
  "bin": {
@@ -12,7 +12,7 @@
12
12
  "skills"
13
13
  ],
14
14
  "scripts": {
15
- "build:sdk": "pnpm --dir ../topolo-sdk build",
15
+ "build:sdk": "python3 ../../tooling/with-resource-lock.py ../topolo-sdk -- pnpm --dir ../topolo-sdk build",
16
16
  "prebuild": "pnpm run build:sdk",
17
17
  "build": "tsup && node scripts/vendor-generator.mjs",
18
18
  "dev": "tsup --watch",
@@ -29,7 +29,7 @@
29
29
  "commander": "^12.1.0"
30
30
  },
31
31
  "devDependencies": {
32
- "@topolo/sdk": "latest",
32
+ "@topolo/sdk": "file:../topolo-sdk",
33
33
  "@types/node": "^20.12.0",
34
34
  "@vitest/coverage-v8": "3.2.6",
35
35
  "tsup": "^8.0.0",