@postplus/cli 0.1.44 → 0.1.45

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.
@@ -12,8 +12,7 @@ export const HOSTED_EXECUTION_MANIFESTS = {
12
12
  "domain": "media",
13
13
  "capability": "media-generation",
14
14
  "endpointKeys": [
15
- "transcription",
16
- "transcription-turbo"
15
+ "transcription"
17
16
  ],
18
17
  "endpoints": [
19
18
  {
@@ -96,87 +95,6 @@ export const HOSTED_EXECUTION_MANIFESTS = {
96
95
  "billingDimensions": [
97
96
  "mediaSeconds"
98
97
  ]
99
- },
100
- {
101
- "endpointKey": "transcription-turbo",
102
- "provider": "wavespeed",
103
- "providerModelPath": "wavespeed-ai/openai-whisper-turbo",
104
- "fields": [
105
- {
106
- "name": "audio",
107
- "class": "intent",
108
- "flag": "--audio",
109
- "type": "media-url",
110
- "required": true
111
- },
112
- {
113
- "name": "duration_seconds",
114
- "class": "intent",
115
- "flag": "--duration-seconds",
116
- "type": "number",
117
- "required": true
118
- },
119
- {
120
- "name": "task",
121
- "class": "default",
122
- "flag": "--task",
123
- "type": "string",
124
- "enumValues": [
125
- "transcribe",
126
- "translate"
127
- ],
128
- "default": "transcribe",
129
- "required": false
130
- },
131
- {
132
- "name": "language",
133
- "class": "default",
134
- "flag": "--language",
135
- "type": "string",
136
- "default": "auto",
137
- "required": false
138
- },
139
- {
140
- "name": "enable_timestamps",
141
- "class": "default",
142
- "flag": "--enable-timestamps",
143
- "type": "boolean",
144
- "default": false,
145
- "required": false
146
- },
147
- {
148
- "name": "prompt",
149
- "class": "intent",
150
- "flag": "--prompt",
151
- "type": "string",
152
- "required": false
153
- },
154
- {
155
- "name": "mediaSeconds",
156
- "class": "runner-managed",
157
- "flag": null,
158
- "type": "number",
159
- "required": false,
160
- "derivedFrom": "duration_seconds"
161
- },
162
- {
163
- "name": "operationId",
164
- "class": "runner-managed",
165
- "flag": null,
166
- "type": "string",
167
- "required": false
168
- },
169
- {
170
- "name": "quoteConfirmationToken",
171
- "class": "runner-managed",
172
- "flag": null,
173
- "type": "string",
174
- "required": false
175
- }
176
- ],
177
- "billingDimensions": [
178
- "mediaSeconds"
179
- ]
180
98
  }
181
99
  ]
182
100
  }
@@ -284,6 +202,7 @@ export const HOSTED_EXECUTION_MANIFESTS = {
284
202
  "2k",
285
203
  "4k"
286
204
  ],
205
+ "canonicalize": "image-resolution-tier",
287
206
  "default": "1k",
288
207
  "required": false
289
208
  },
@@ -297,6 +216,7 @@ export const HOSTED_EXECUTION_MANIFESTS = {
297
216
  "medium",
298
217
  "high"
299
218
  ],
219
+ "canonicalize": "lowercase",
300
220
  "default": "medium",
301
221
  "required": false
302
222
  },
@@ -358,6 +278,7 @@ export const HOSTED_EXECUTION_MANIFESTS = {
358
278
  "2k",
359
279
  "4k"
360
280
  ],
281
+ "canonicalize": "image-resolution-tier",
361
282
  "default": "1k",
362
283
  "required": false
363
284
  },
@@ -371,6 +292,7 @@ export const HOSTED_EXECUTION_MANIFESTS = {
371
292
  "medium",
372
293
  "high"
373
294
  ],
295
+ "canonicalize": "lowercase",
374
296
  "default": "medium",
375
297
  "required": false
376
298
  },
@@ -425,6 +347,7 @@ export const HOSTED_EXECUTION_MANIFESTS = {
425
347
  "2k",
426
348
  "4k"
427
349
  ],
350
+ "canonicalize": "image-resolution-tier",
428
351
  "default": "1k",
429
352
  "required": false
430
353
  },
@@ -445,6 +368,7 @@ export const HOSTED_EXECUTION_MANIFESTS = {
445
368
  "png",
446
369
  "jpeg"
447
370
  ],
371
+ "canonicalize": "lowercase",
448
372
  "default": "png",
449
373
  "required": false
450
374
  },
@@ -506,6 +430,7 @@ export const HOSTED_EXECUTION_MANIFESTS = {
506
430
  "2k",
507
431
  "4k"
508
432
  ],
433
+ "canonicalize": "image-resolution-tier",
509
434
  "default": "1k",
510
435
  "required": false
511
436
  },
@@ -526,6 +451,7 @@ export const HOSTED_EXECUTION_MANIFESTS = {
526
451
  "png",
527
452
  "jpeg"
528
453
  ],
454
+ "canonicalize": "lowercase",
529
455
  "default": "png",
530
456
  "required": false
531
457
  },
@@ -576,6 +502,7 @@ export const HOSTED_EXECUTION_MANIFESTS = {
576
502
  "enumValues": [
577
503
  "1k"
578
504
  ],
505
+ "canonicalize": "image-resolution-tier",
579
506
  "default": "1k",
580
507
  "required": false
581
508
  },
@@ -588,6 +515,7 @@ export const HOSTED_EXECUTION_MANIFESTS = {
588
515
  "png",
589
516
  "jpeg"
590
517
  ],
518
+ "canonicalize": "lowercase",
591
519
  "default": "png",
592
520
  "required": false
593
521
  },
@@ -638,6 +566,7 @@ export const HOSTED_EXECUTION_MANIFESTS = {
638
566
  "enumValues": [
639
567
  "2k"
640
568
  ],
569
+ "canonicalize": "image-resolution-tier",
641
570
  "default": "2k",
642
571
  "required": false
643
572
  },
@@ -650,6 +579,7 @@ export const HOSTED_EXECUTION_MANIFESTS = {
650
579
  "png",
651
580
  "jpeg"
652
581
  ],
582
+ "canonicalize": "lowercase",
653
583
  "default": "png",
654
584
  "required": false
655
585
  },
@@ -700,6 +630,7 @@ export const HOSTED_EXECUTION_MANIFESTS = {
700
630
  "enumValues": [
701
631
  "4k"
702
632
  ],
633
+ "canonicalize": "image-resolution-tier",
703
634
  "default": "4k",
704
635
  "required": false
705
636
  },
@@ -712,6 +643,7 @@ export const HOSTED_EXECUTION_MANIFESTS = {
712
643
  "png",
713
644
  "jpeg"
714
645
  ],
646
+ "canonicalize": "lowercase",
715
647
  "default": "png",
716
648
  "required": false
717
649
  },
@@ -770,6 +702,7 @@ export const HOSTED_EXECUTION_MANIFESTS = {
770
702
  "enumValues": [
771
703
  "1k"
772
704
  ],
705
+ "canonicalize": "image-resolution-tier",
773
706
  "default": "1k",
774
707
  "required": false
775
708
  },
@@ -782,6 +715,7 @@ export const HOSTED_EXECUTION_MANIFESTS = {
782
715
  "png",
783
716
  "jpeg"
784
717
  ],
718
+ "canonicalize": "lowercase",
785
719
  "default": "png",
786
720
  "required": false
787
721
  },
@@ -840,6 +774,7 @@ export const HOSTED_EXECUTION_MANIFESTS = {
840
774
  "enumValues": [
841
775
  "2k"
842
776
  ],
777
+ "canonicalize": "image-resolution-tier",
843
778
  "default": "2k",
844
779
  "required": false
845
780
  },
@@ -852,6 +787,7 @@ export const HOSTED_EXECUTION_MANIFESTS = {
852
787
  "png",
853
788
  "jpeg"
854
789
  ],
790
+ "canonicalize": "lowercase",
855
791
  "default": "png",
856
792
  "required": false
857
793
  },
@@ -910,6 +846,7 @@ export const HOSTED_EXECUTION_MANIFESTS = {
910
846
  "enumValues": [
911
847
  "4k"
912
848
  ],
849
+ "canonicalize": "image-resolution-tier",
913
850
  "default": "4k",
914
851
  "required": false
915
852
  },
@@ -922,6 +859,7 @@ export const HOSTED_EXECUTION_MANIFESTS = {
922
859
  "png",
923
860
  "jpeg"
924
861
  ],
862
+ "canonicalize": "lowercase",
925
863
  "default": "png",
926
864
  "required": false
927
865
  },
@@ -973,6 +911,7 @@ export const HOSTED_EXECUTION_MANIFESTS = {
973
911
  "jpeg",
974
912
  "png"
975
913
  ],
914
+ "canonicalize": "lowercase",
976
915
  "default": "png",
977
916
  "required": false
978
917
  },
@@ -1024,6 +963,7 @@ export const HOSTED_EXECUTION_MANIFESTS = {
1024
963
  "jpeg",
1025
964
  "png"
1026
965
  ],
966
+ "canonicalize": "lowercase",
1027
967
  "default": "png",
1028
968
  "required": false
1029
969
  },
@@ -1092,6 +1032,7 @@ export const HOSTED_EXECUTION_MANIFESTS = {
1092
1032
  "jpeg",
1093
1033
  "png"
1094
1034
  ],
1035
+ "canonicalize": "lowercase",
1095
1036
  "default": "png",
1096
1037
  "required": false
1097
1038
  },
@@ -1151,6 +1092,7 @@ export const HOSTED_EXECUTION_MANIFESTS = {
1151
1092
  "jpeg",
1152
1093
  "png"
1153
1094
  ],
1095
+ "canonicalize": "lowercase",
1154
1096
  "default": "png",
1155
1097
  "required": false
1156
1098
  },
@@ -1239,9 +1181,7 @@ export const HOSTED_EXECUTION_MANIFESTS = {
1239
1181
  "capability": "media-generation",
1240
1182
  "endpointKeys": [
1241
1183
  "video-seedance-2-image",
1242
- "video-seedance-2-image-turbo",
1243
- "video-seedance-2-text",
1244
- "video-seedance-2-text-turbo"
1184
+ "video-seedance-2-text"
1245
1185
  ],
1246
1186
  "endpoints": [
1247
1187
  {
@@ -1273,76 +1213,7 @@ export const HOSTED_EXECUTION_MANIFESTS = {
1273
1213
  "720p",
1274
1214
  "1080p"
1275
1215
  ],
1276
- "default": "720p",
1277
- "required": false
1278
- },
1279
- {
1280
- "name": "duration",
1281
- "class": "default",
1282
- "flag": null,
1283
- "type": "number",
1284
- "default": 5,
1285
- "required": false,
1286
- "min": 4,
1287
- "max": 15
1288
- },
1289
- {
1290
- "name": "operationId",
1291
- "class": "runner-managed",
1292
- "flag": null,
1293
- "type": "string",
1294
- "required": false
1295
- },
1296
- {
1297
- "name": "quoteConfirmationToken",
1298
- "class": "runner-managed",
1299
- "flag": null,
1300
- "type": "string",
1301
- "required": false
1302
- },
1303
- {
1304
- "name": "requestDimensions",
1305
- "class": "runner-managed",
1306
- "flag": null,
1307
- "type": "string",
1308
- "required": false
1309
- }
1310
- ],
1311
- "billingDimensions": [
1312
- "duration",
1313
- "resolution",
1314
- "referenceVideoCount",
1315
- "referenceVideoMode"
1316
- ]
1317
- },
1318
- {
1319
- "endpointKey": "video-seedance-2-image-turbo",
1320
- "provider": "wavespeed",
1321
- "providerModelPath": "bytedance/seedance-2.0/image-to-video-turbo",
1322
- "fields": [
1323
- {
1324
- "name": "prompt",
1325
- "class": "intent",
1326
- "flag": null,
1327
- "type": "string",
1328
- "required": true
1329
- },
1330
- {
1331
- "name": "image",
1332
- "class": "intent",
1333
- "flag": null,
1334
- "type": "media-url",
1335
- "required": true
1336
- },
1337
- {
1338
- "name": "resolution",
1339
- "class": "default",
1340
- "flag": null,
1341
- "type": "string",
1342
- "enumValues": [
1343
- "720p",
1344
- "1080p"
1345
- ],
1216
+ "canonicalize": "lowercase",
1346
1217
  "default": "720p",
1347
1218
  "required": false
1348
1219
  },
@@ -1407,100 +1278,7 @@ export const HOSTED_EXECUTION_MANIFESTS = {
1407
1278
  "720p",
1408
1279
  "1080p"
1409
1280
  ],
1410
- "default": "720p",
1411
- "required": false
1412
- },
1413
- {
1414
- "name": "aspect_ratio",
1415
- "class": "intent",
1416
- "flag": null,
1417
- "type": "string",
1418
- "enumValues": [
1419
- "21:9",
1420
- "16:9",
1421
- "4:3",
1422
- "1:1",
1423
- "3:4",
1424
- "9:16"
1425
- ],
1426
- "required": false
1427
- },
1428
- {
1429
- "name": "duration",
1430
- "class": "default",
1431
- "flag": null,
1432
- "type": "number",
1433
- "default": 5,
1434
- "required": false,
1435
- "min": 4,
1436
- "max": 15
1437
- },
1438
- {
1439
- "name": "reference_images",
1440
- "class": "intent",
1441
- "flag": null,
1442
- "type": "media-url",
1443
- "repeatable": true,
1444
- "required": false
1445
- },
1446
- {
1447
- "name": "reference_videos",
1448
- "class": "intent",
1449
- "flag": null,
1450
- "type": "media-url",
1451
- "repeatable": true,
1452
- "required": false
1453
- },
1454
- {
1455
- "name": "operationId",
1456
- "class": "runner-managed",
1457
- "flag": null,
1458
- "type": "string",
1459
- "required": false
1460
- },
1461
- {
1462
- "name": "quoteConfirmationToken",
1463
- "class": "runner-managed",
1464
- "flag": null,
1465
- "type": "string",
1466
- "required": false
1467
- },
1468
- {
1469
- "name": "requestDimensions",
1470
- "class": "runner-managed",
1471
- "flag": null,
1472
- "type": "string",
1473
- "required": false
1474
- }
1475
- ],
1476
- "billingDimensions": [
1477
- "duration",
1478
- "resolution",
1479
- "referenceVideoCount",
1480
- "referenceVideoMode"
1481
- ]
1482
- },
1483
- {
1484
- "endpointKey": "video-seedance-2-text-turbo",
1485
- "provider": "wavespeed",
1486
- "providerModelPath": "bytedance/seedance-2.0/text-to-video-turbo",
1487
- "fields": [
1488
- {
1489
- "name": "prompt",
1490
- "class": "intent",
1491
- "flag": null,
1492
- "type": "string",
1493
- "required": true
1494
- },
1495
- {
1496
- "name": "resolution",
1497
- "class": "default",
1498
- "flag": null,
1499
- "type": "string",
1500
- "enumValues": [
1501
- "720p",
1502
- "1080p"
1503
- ],
1281
+ "canonicalize": "lowercase",
1504
1282
  "default": "720p",
1505
1283
  "required": false
1506
1284
  },
@@ -1721,8 +1499,6 @@ export const HOSTED_EXECUTION_MANIFESTS = {
1721
1499
  "video-kling-v3-0-pro-image",
1722
1500
  "video-kling-v3-0-std-text",
1723
1501
  "video-kling-v3-0-std-image",
1724
- "video-wanx2-1-t2v-turbo",
1725
- "video-wanx2-1-i2v-turbo",
1726
1502
  "video-infinitetalk",
1727
1503
  "video-kling-v2-6-pro-motion-control"
1728
1504
  ],
@@ -1993,148 +1769,6 @@ export const HOSTED_EXECUTION_MANIFESTS = {
1993
1769
  "audioMode"
1994
1770
  ]
1995
1771
  },
1996
- {
1997
- "endpointKey": "video-wanx2-1-t2v-turbo",
1998
- "provider": "dashscope",
1999
- "providerModelPath": "wanx2.1-t2v-turbo",
2000
- "fields": [
2001
- {
2002
- "name": "prompt",
2003
- "class": "intent",
2004
- "flag": null,
2005
- "type": "string",
2006
- "required": true
2007
- },
2008
- {
2009
- "name": "aspect_ratio",
2010
- "class": "intent",
2011
- "flag": null,
2012
- "type": "string",
2013
- "enumValues": [
2014
- "9:16",
2015
- "16:9"
2016
- ],
2017
- "required": false
2018
- },
2019
- {
2020
- "name": "resolution",
2021
- "class": "default",
2022
- "flag": null,
2023
- "type": "string",
2024
- "enumValues": [
2025
- "480p",
2026
- "720p"
2027
- ],
2028
- "default": "720p",
2029
- "required": false
2030
- },
2031
- {
2032
- "name": "duration",
2033
- "class": "default",
2034
- "flag": null,
2035
- "type": "number",
2036
- "enumValues": [
2037
- "5"
2038
- ],
2039
- "default": 5,
2040
- "required": false
2041
- },
2042
- {
2043
- "name": "operationId",
2044
- "class": "runner-managed",
2045
- "flag": null,
2046
- "type": "string",
2047
- "required": false
2048
- },
2049
- {
2050
- "name": "quoteConfirmationToken",
2051
- "class": "runner-managed",
2052
- "flag": null,
2053
- "type": "string",
2054
- "required": false
2055
- },
2056
- {
2057
- "name": "requestDimensions",
2058
- "class": "runner-managed",
2059
- "flag": null,
2060
- "type": "string",
2061
- "required": false
2062
- }
2063
- ],
2064
- "billingDimensions": [
2065
- "resolution",
2066
- "duration"
2067
- ]
2068
- },
2069
- {
2070
- "endpointKey": "video-wanx2-1-i2v-turbo",
2071
- "provider": "dashscope",
2072
- "providerModelPath": "wanx2.1-i2v-turbo",
2073
- "fields": [
2074
- {
2075
- "name": "prompt",
2076
- "class": "intent",
2077
- "flag": null,
2078
- "type": "string",
2079
- "required": true
2080
- },
2081
- {
2082
- "name": "image",
2083
- "class": "intent",
2084
- "flag": null,
2085
- "type": "media-url",
2086
- "required": true
2087
- },
2088
- {
2089
- "name": "resolution",
2090
- "class": "default",
2091
- "flag": null,
2092
- "type": "string",
2093
- "enumValues": [
2094
- "480p",
2095
- "720p"
2096
- ],
2097
- "default": "720p",
2098
- "required": false
2099
- },
2100
- {
2101
- "name": "duration",
2102
- "class": "default",
2103
- "flag": null,
2104
- "type": "number",
2105
- "enumValues": [
2106
- "5"
2107
- ],
2108
- "default": 5,
2109
- "required": false
2110
- },
2111
- {
2112
- "name": "operationId",
2113
- "class": "runner-managed",
2114
- "flag": null,
2115
- "type": "string",
2116
- "required": false
2117
- },
2118
- {
2119
- "name": "quoteConfirmationToken",
2120
- "class": "runner-managed",
2121
- "flag": null,
2122
- "type": "string",
2123
- "required": false
2124
- },
2125
- {
2126
- "name": "requestDimensions",
2127
- "class": "runner-managed",
2128
- "flag": null,
2129
- "type": "string",
2130
- "required": false
2131
- }
2132
- ],
2133
- "billingDimensions": [
2134
- "resolution",
2135
- "duration"
2136
- ]
2137
- },
2138
1772
  {
2139
1773
  "endpointKey": "video-infinitetalk",
2140
1774
  "provider": "wavespeed",
@@ -5,6 +5,7 @@ import path from 'node:path';
5
5
  import { resolveFreshRemoteAuth } from './auth-session.js';
6
6
  import { sendAuthedCloudRequest } from './authed-cloud-request.js';
7
7
  import { formatPostPlusCompatibilityError } from './client-compatibility.js';
8
+ import { assertModelledFieldValuesInRange } from './hosted-field-validation.js';
8
9
  import { buildVerbTargetIndex, } from './hosted-manifest-index.js';
9
10
  import { buildHostedRequestSchemaReport, buildMediaGenerationRequestDimensions, } from './hosted-request-schemas.js';
10
11
  import { readLargeCreditQuoteConfirmationChallenge, } from './quote-confirmation.js';
@@ -176,6 +177,11 @@ async function runMediaVerbFlags(args) {
176
177
  flags,
177
178
  verb,
178
179
  });
180
+ // Schema-driven early validation reads the manifest enum/range + canonicalize hint
181
+ // for every modelled field (a single source shared with the Web boundary, which
182
+ // stays authoritative). It runs on the built input so a mixed-case "4K"/"High"
183
+ // passes while an out-of-enum value fast-fails locally before the hosted call.
184
+ assertModelledFieldValuesInRange(endpointKey, fields, input);
179
185
  return submitMediaGenerationRequest({
180
186
  capability: resolved.capability,
181
187
  endpointKey,
@@ -224,6 +230,12 @@ async function runMediaVerbRequestJson(args) {
224
230
  throw new Error(`media ${verb} ${endpointKey} input must not include runner-managed field "${field.name}"; the CLI mints or derives it.`);
225
231
  }
226
232
  }
233
+ // Schema-driven early validation reads the manifest enum/range + canonicalize hint
234
+ // for every modelled field (a single source shared with the Web boundary, which
235
+ // stays authoritative). It runs on the agent-authored body so an out-of-enum
236
+ // resolution ("999p") fast-fails locally before the hosted call while a mixed-case
237
+ // "720P" passes.
238
+ assertModelledFieldValuesInRange(endpointKey, endpoint.fields, input);
227
239
  return submitMediaGenerationRequest({
228
240
  capability: resolved.capability,
229
241
  endpointKey,
@@ -567,9 +579,11 @@ function buildMediaVerbInput(input) {
567
579
  }
568
580
  continue;
569
581
  }
570
- if (field.enumValues && !field.enumValues.includes(raw)) {
571
- throw new Error(`--${key} must be one of ${field.enumValues.join(', ')}.`);
572
- }
582
+ // Enum / numeric-range membership (with canonicalize-faithful casing) is gated
583
+ // once by assertModelledFieldValuesInRange after the input is built — not here —
584
+ // so a mixed-case "4K" is not wrongly rejected by a raw includes() check. This
585
+ // path only parses the flag string into its typed value; the number floor below
586
+ // keeps a non-range number field (e.g. transcription duration_seconds) positive.
573
587
  if (field.type === 'number') {
574
588
  const parsed = Number(raw);
575
589
  if (!Number.isFinite(parsed) || parsed <= 0) {
@@ -0,0 +1,93 @@
1
+ // Schema-driven early field validation (issue #475). Mirrors the Web boundary's
2
+ // assertModelledFieldValuesInRange: every enum / numeric-range field an endpoint
3
+ // advertises in the generated execution manifest — the same EnvelopeFieldSpec the
4
+ // Web validator reads — is checked here BEFORE the request is posted, so the agent
5
+ // gets an immediate field-level error (e.g. seedance resolution "999p") instead of
6
+ // waiting for the round-trip. The Web boundary stays the AUTHORITATIVE gate; this is
7
+ // only pre-submit feedback.
8
+ //
9
+ // Casing-faithfulness is the reason this cannot be a CLI-side island: the per-field
10
+ // canonicalization rule is read from the manifest `canonicalize` hint, the SAME
11
+ // single source the Web validator reads, so "720P" / "4K" pass and "english" /
12
+ // "999p" fail exactly as they do on the boundary. The two canonicalize functions
13
+ // below are stable 3-line algorithms; WHICH field uses WHICH is decided by the
14
+ // schema hint, never re-guessed here.
15
+ // k-tier normalization for image resolution ("4K" -> "4k"). Mirrors the Web
16
+ // canonicalizeImageResolution exactly.
17
+ function canonicalizeImageResolution(value) {
18
+ const trimmed = value.trim();
19
+ const tier = trimmed.match(/^(\d+(?:\.\d+)?)\s*k$/iu);
20
+ return tier ? `${tier[1]}k` : trimmed;
21
+ }
22
+ function canonicalizeLowercaseToken(value) {
23
+ return value.trim().toLowerCase();
24
+ }
25
+ function canonicalizeModelledFieldValue(field, value) {
26
+ switch (field.canonicalize) {
27
+ case 'image-resolution-tier':
28
+ return canonicalizeImageResolution(value);
29
+ case 'lowercase':
30
+ return canonicalizeLowercaseToken(value);
31
+ default:
32
+ return value;
33
+ }
34
+ }
35
+ function isIntegerInRange(min, max, value) {
36
+ return Number.isInteger(value) && value >= min && value <= max;
37
+ }
38
+ function formatReceivedValue(raw) {
39
+ return typeof raw === 'string' ? `"${raw}"` : String(raw);
40
+ }
41
+ function assertModelledNumberFieldValue(endpointKey, field, raw) {
42
+ const enumValues = field.enumValues && field.enumValues.length > 0 ? field.enumValues : null;
43
+ const constraint = enumValues
44
+ ? `must be one of ${enumValues.join(', ')}`
45
+ : `must be an integer from ${field.min} to ${field.max}`;
46
+ if (typeof raw !== 'number' || !Number.isFinite(raw)) {
47
+ throw new Error(`${endpointKey} ${field.name} ${constraint}; received ${formatReceivedValue(raw)}.`);
48
+ }
49
+ if (enumValues) {
50
+ if (!enumValues.includes(String(raw))) {
51
+ throw new Error(`${endpointKey} ${field.name} ${constraint}; received ${raw}.`);
52
+ }
53
+ return;
54
+ }
55
+ if (field.min === undefined || field.max === undefined) {
56
+ return;
57
+ }
58
+ if (!isIntegerInRange(field.min, field.max, raw)) {
59
+ throw new Error(`${endpointKey} ${field.name} ${constraint}; received ${raw}.`);
60
+ }
61
+ }
62
+ // Validates every advertised enum / numeric-range field present in the input against
63
+ // the manifest contract. Skips runner-managed fields (no caller input), fields with
64
+ // neither an enum nor a range, and fields the input omits — exactly mirroring the Web
65
+ // boundary so a value the CLI accepts the boundary also accepts, and vice versa.
66
+ export function assertModelledFieldValuesInRange(endpointKey, fields, input) {
67
+ for (const field of fields) {
68
+ if (field.class === 'runner-managed') {
69
+ continue;
70
+ }
71
+ const enumValues = field.enumValues && field.enumValues.length > 0 ? field.enumValues : null;
72
+ const hasRange = field.min !== undefined && field.max !== undefined;
73
+ if (!enumValues && !hasRange) {
74
+ continue;
75
+ }
76
+ if (!Object.hasOwn(input, field.name)) {
77
+ continue;
78
+ }
79
+ if (field.type === 'number') {
80
+ assertModelledNumberFieldValue(endpointKey, field, input[field.name]);
81
+ continue;
82
+ }
83
+ const raw = input[field.name];
84
+ if (typeof raw !== 'string' || !raw.trim()) {
85
+ continue;
86
+ }
87
+ const value = raw.trim();
88
+ if (enumValues &&
89
+ !enumValues.includes(canonicalizeModelledFieldValue(field, value))) {
90
+ throw new Error(`${endpointKey} ${field.name} must be one of ${enumValues.join(', ')}; received "${value}".`);
91
+ }
92
+ }
93
+ }
@@ -29,6 +29,9 @@ function toFieldContract(field) {
29
29
  if (field.max !== undefined) {
30
30
  contract.max = field.max;
31
31
  }
32
+ if (field.canonicalize) {
33
+ contract.canonicalize = field.canonicalize;
34
+ }
32
35
  if (field.default !== undefined) {
33
36
  contract.default = field.default;
34
37
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@postplus/cli",
3
- "version": "0.1.44",
3
+ "version": "0.1.45",
4
4
  "packageManager": "pnpm@10.30.3+sha512.c961d1e0a2d8e354ecaa5166b822516668b7f44cb5bd95122d590dd81922f606f5473b6d23ec4a5be05e7fcd18e8488d47d978bbe981872f1145d06e9a740017",
5
5
  "type": "module",
6
6
  "description": "PostPlus CLI for PostPlus Cloud auth, status, and diagnostics.",
@@ -18,6 +18,7 @@
18
18
  "build/doctor.js",
19
19
  "build/hosted-domain-commands.js",
20
20
  "build/generated/hosted-execution-manifest.generated.js",
21
+ "build/hosted-field-validation.js",
21
22
  "build/hosted-manifest-index.js",
22
23
  "build/hosted-release.js",
23
24
  "build/hosted-request-schemas.js",