@postplus/cli 0.1.45 → 0.1.47
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/build/authed-cloud-request.d.ts +39 -0
- package/build/authed-cloud-request.js +3 -0
- package/build/client-compatibility.js +10 -4
- package/build/generated/hosted-execution-manifest.generated.js +298 -1
- package/build/generated/hosted-field-validation-core.generated.js +84 -0
- package/build/hosted-domain-commands.d.ts +14 -0
- package/build/hosted-domain-commands.js +129 -69
- package/build/hosted-field-validation.js +11 -91
- package/build/hosted-lib.d.ts +34 -0
- package/build/hosted-lib.js +45 -0
- package/build/hosted-manifest-index.d.ts +69 -0
- package/build/hosted-request-schemas.js +0 -108
- package/build/index.js +6 -4
- package/build/update-check.js +1 -2
- package/package.json +14 -1
|
@@ -3,11 +3,11 @@ import { createReadStream } from 'node:fs';
|
|
|
3
3
|
import { mkdir, readFile, stat, writeFile } from 'node:fs/promises';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { resolveFreshRemoteAuth } from './auth-session.js';
|
|
6
|
-
import { sendAuthedCloudRequest } from './authed-cloud-request.js';
|
|
6
|
+
import { sendAuthedCloudRequest, } from './authed-cloud-request.js';
|
|
7
7
|
import { formatPostPlusCompatibilityError } from './client-compatibility.js';
|
|
8
8
|
import { assertModelledFieldValuesInRange } from './hosted-field-validation.js';
|
|
9
9
|
import { buildVerbTargetIndex, } from './hosted-manifest-index.js';
|
|
10
|
-
import { buildHostedRequestSchemaReport
|
|
10
|
+
import { buildHostedRequestSchemaReport } from './hosted-request-schemas.js';
|
|
11
11
|
import { readLargeCreditQuoteConfirmationChallenge, } from './quote-confirmation.js';
|
|
12
12
|
// Manifest-driven verb grammar indexes (SSOT projected from apps/web +
|
|
13
13
|
// public-skill-metadata via the generated manifest). The verb/flag grammar,
|
|
@@ -27,6 +27,20 @@ function buildPublishVerbIndex() {
|
|
|
27
27
|
}
|
|
28
28
|
return index;
|
|
29
29
|
}
|
|
30
|
+
// Reads the request-json body for a surface: from the injected object (lib path)
|
|
31
|
+
// or by reading `--request <file>` (bin path). This is the SINGLE place the two
|
|
32
|
+
// paths diverge on input source; the resolved body then flows through the SAME
|
|
33
|
+
// validation + envelope build, so the URL/body/headers stay byte-identical.
|
|
34
|
+
async function resolveRequestBody(context, flags) {
|
|
35
|
+
if (context) {
|
|
36
|
+
if (context.requestJson === undefined) {
|
|
37
|
+
throw new Error('This hosted command requires a requestJson body.');
|
|
38
|
+
}
|
|
39
|
+
return { body: context.requestJson, errorInputLabel: 'requestJson' };
|
|
40
|
+
}
|
|
41
|
+
const requestPath = requireFlag(flags, 'request');
|
|
42
|
+
return { body: await readJsonFile(requestPath), errorInputLabel: requestPath };
|
|
43
|
+
}
|
|
30
44
|
class HostedQuoteConfirmationRequiredError extends Error {
|
|
31
45
|
challenge;
|
|
32
46
|
constructor(message, challenge) {
|
|
@@ -43,41 +57,45 @@ class HostedProductRequestError extends Error {
|
|
|
43
57
|
this.name = 'HostedProductRequestError';
|
|
44
58
|
}
|
|
45
59
|
}
|
|
46
|
-
export async function runHostedDomainCommand(domain, args
|
|
60
|
+
export async function runHostedDomainCommand(domain, args,
|
|
61
|
+
// Present only on the in-process hosted-lib path; the bin path never passes it.
|
|
62
|
+
// See HostedRequestContext: it carries the injected auth/releaseId/requestJson
|
|
63
|
+
// and switches every leaf onto the no-disk, no-file, return-payload behavior.
|
|
64
|
+
context) {
|
|
47
65
|
const [subcommand, ...rest] = args;
|
|
48
66
|
if (domain === 'research') {
|
|
49
67
|
if (subcommand === 'schema') {
|
|
50
|
-
return runHostedSchema(domain, rest);
|
|
68
|
+
return runHostedSchema(domain, rest, context);
|
|
51
69
|
}
|
|
52
70
|
if (subcommand === 'collect') {
|
|
53
|
-
return runResearchCollect(rest);
|
|
71
|
+
return runResearchCollect(rest, context);
|
|
54
72
|
}
|
|
55
73
|
if (subcommand === 'scrape') {
|
|
56
|
-
return runResearchScrape(rest);
|
|
74
|
+
return runResearchScrape(rest, context);
|
|
57
75
|
}
|
|
58
76
|
printResearchHelp();
|
|
59
77
|
return subcommand === undefined || isHelp(subcommand) ? 0 : 1;
|
|
60
78
|
}
|
|
61
79
|
if (subcommand === 'schema') {
|
|
62
|
-
return runHostedSchema(domain, rest);
|
|
80
|
+
return runHostedSchema(domain, rest, context);
|
|
63
81
|
}
|
|
64
82
|
// Poll a pending async media-generation run by handle. This is a hand-coded
|
|
65
83
|
// branch (not a manifest verb) because a status poll has no endpointKey/field
|
|
66
84
|
// contract to project — exactly like the `research collect --run-handle`
|
|
67
85
|
// polling branch. It must be checked before the manifest verb dispatch.
|
|
68
86
|
if (domain === 'media' && subcommand === 'poll') {
|
|
69
|
-
return runMediaPoll(rest);
|
|
87
|
+
return runMediaPoll(rest, context);
|
|
70
88
|
}
|
|
71
89
|
if (domain === 'media' &&
|
|
72
90
|
subcommand &&
|
|
73
91
|
MEDIA_VERB_ENDPOINTS.has(subcommand)) {
|
|
74
|
-
return runMediaVerb(subcommand, rest);
|
|
92
|
+
return runMediaVerb(subcommand, rest, context);
|
|
75
93
|
}
|
|
76
94
|
// publish: the OPERATION is the subcommand (no separate target positional).
|
|
77
95
|
if (domain === 'publish' &&
|
|
78
96
|
subcommand &&
|
|
79
97
|
PUBLISH_VERB_OPERATIONS.has(subcommand)) {
|
|
80
|
-
return runPublishOperation(subcommand, rest);
|
|
98
|
+
return runPublishOperation(subcommand, rest, context);
|
|
81
99
|
}
|
|
82
100
|
printDomainVerbHelp(domain);
|
|
83
101
|
return subcommand === undefined || isHelp(subcommand) ? 0 : 1;
|
|
@@ -87,7 +105,7 @@ export async function runHostedDomainCommand(domain, args) {
|
|
|
87
105
|
// scalar intent/default fields to flags, a request-json surface reads the nested
|
|
88
106
|
// envelope from `--request <file>`. Either way runner-managed fields (billing
|
|
89
107
|
// dimensions, ids, tokens) are derived/minted by the runner, never agent-supplied.
|
|
90
|
-
async function runMediaVerb(verb, args) {
|
|
108
|
+
async function runMediaVerb(verb, args, context) {
|
|
91
109
|
const targets = MEDIA_VERB_ENDPOINTS.get(verb);
|
|
92
110
|
if (!targets) {
|
|
93
111
|
throw new Error(`Unknown media verb ${verb}.`);
|
|
@@ -112,6 +130,7 @@ async function runMediaVerb(verb, args) {
|
|
|
112
130
|
modelKey: targetKey,
|
|
113
131
|
resolved,
|
|
114
132
|
verb,
|
|
133
|
+
context,
|
|
115
134
|
});
|
|
116
135
|
}
|
|
117
136
|
if (resolved.surface === 'request-json') {
|
|
@@ -120,6 +139,7 @@ async function runMediaVerb(verb, args) {
|
|
|
120
139
|
endpointKey: targetKey,
|
|
121
140
|
resolved,
|
|
122
141
|
verb,
|
|
142
|
+
context,
|
|
123
143
|
});
|
|
124
144
|
}
|
|
125
145
|
return runMediaVerbFlags({
|
|
@@ -127,12 +147,13 @@ async function runMediaVerb(verb, args) {
|
|
|
127
147
|
endpointKey: targetKey,
|
|
128
148
|
resolved,
|
|
129
149
|
verb,
|
|
150
|
+
context,
|
|
130
151
|
});
|
|
131
152
|
}
|
|
132
153
|
// Flags surface (e.g. audio-transcription): scalar intent/default fields map to
|
|
133
154
|
// flags; runner-managed fields have no flag so the agent cannot pass them.
|
|
134
155
|
async function runMediaVerbFlags(args) {
|
|
135
|
-
const { endpointKey, resolved, verb } = args;
|
|
156
|
+
const { endpointKey, resolved, verb, context } = args;
|
|
136
157
|
const endpoint = requireResolvedEndpoint(resolved, verb, endpointKey);
|
|
137
158
|
const fields = endpoint.fields;
|
|
138
159
|
const flagToField = new Map();
|
|
@@ -193,6 +214,7 @@ async function runMediaVerbFlags(args) {
|
|
|
193
214
|
outputPath,
|
|
194
215
|
quoteConfirmationToken: flags.values.get('quote-confirmation-token'),
|
|
195
216
|
skillName: flags.values.get('skill') ?? resolved.skill,
|
|
217
|
+
context,
|
|
196
218
|
});
|
|
197
219
|
}
|
|
198
220
|
// Request-json surface (e.g. seedance-submitter): the nested envelope is supplied
|
|
@@ -200,7 +222,7 @@ async function runMediaVerbFlags(args) {
|
|
|
200
222
|
// so the body carries only the media-generation input. Runner-managed fields have
|
|
201
223
|
// no flag and must not appear in the body — the CLI fast-fails if they do.
|
|
202
224
|
async function runMediaVerbRequestJson(args) {
|
|
203
|
-
const { endpointKey, resolved, verb } = args;
|
|
225
|
+
const { endpointKey, resolved, verb, context } = args;
|
|
204
226
|
const endpoint = requireResolvedEndpoint(resolved, verb, endpointKey);
|
|
205
227
|
const flags = parseFlags(args.args, new Set(['json']));
|
|
206
228
|
const allowedKeys = new Set([
|
|
@@ -216,9 +238,8 @@ async function runMediaVerbRequestJson(args) {
|
|
|
216
238
|
throw new Error(`Unknown option for media ${verb}: --${key}.`);
|
|
217
239
|
}
|
|
218
240
|
}
|
|
219
|
-
const requestPath = requireFlag(flags, 'request');
|
|
220
241
|
const outputPath = flags.values.get('output') ?? null;
|
|
221
|
-
const raw = await
|
|
242
|
+
const { body: raw, errorInputLabel } = await resolveRequestBody(context, flags);
|
|
222
243
|
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
223
244
|
throw new Error(`media ${verb} ${endpointKey} --request must be a JSON object of media-generation input.`);
|
|
224
245
|
}
|
|
@@ -239,7 +260,7 @@ async function runMediaVerbRequestJson(args) {
|
|
|
239
260
|
return submitMediaGenerationRequest({
|
|
240
261
|
capability: resolved.capability,
|
|
241
262
|
endpointKey,
|
|
242
|
-
errorInputLabel
|
|
263
|
+
errorInputLabel,
|
|
243
264
|
input,
|
|
244
265
|
json: flags.booleans.has('json'),
|
|
245
266
|
operationId: flags.values.get('hosted-operation-id') ??
|
|
@@ -247,6 +268,7 @@ async function runMediaVerbRequestJson(args) {
|
|
|
247
268
|
outputPath,
|
|
248
269
|
quoteConfirmationToken: flags.values.get('quote-confirmation-token'),
|
|
249
270
|
skillName: flags.values.get('skill') ?? resolved.skill,
|
|
271
|
+
context,
|
|
250
272
|
});
|
|
251
273
|
}
|
|
252
274
|
function requireResolvedEndpoint(resolved, verb, endpointKey) {
|
|
@@ -264,7 +286,7 @@ function requireResolvedEndpoint(resolved, verb, endpointKey) {
|
|
|
264
286
|
// `estimatedUsage.videoSeconds` so the Web boundary can route eligible short
|
|
265
287
|
// videos through its preflight/routing path (omit it to use the default route).
|
|
266
288
|
async function runVideoAnalysisVerb(args) {
|
|
267
|
-
const { modelKey, resolved, verb } = args;
|
|
289
|
+
const { modelKey, resolved, verb, context } = args;
|
|
268
290
|
const flags = parseFlags(args.args, new Set(['json']));
|
|
269
291
|
const allowedKeys = new Set([
|
|
270
292
|
'hosted-operation-id',
|
|
@@ -280,9 +302,8 @@ async function runVideoAnalysisVerb(args) {
|
|
|
280
302
|
throw new Error(`Unknown option for media ${verb}: --${key}.`);
|
|
281
303
|
}
|
|
282
304
|
}
|
|
283
|
-
const requestPath = requireFlag(flags, 'request');
|
|
284
305
|
const outputPath = flags.values.get('output') ?? null;
|
|
285
|
-
const raw = await
|
|
306
|
+
const { body: raw, errorInputLabel } = await resolveRequestBody(context, flags);
|
|
286
307
|
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
287
308
|
throw new Error(`media ${verb} ${modelKey} --request must be a JSON object of Gemini request payload.`);
|
|
288
309
|
}
|
|
@@ -311,16 +332,17 @@ async function runVideoAnalysisVerb(args) {
|
|
|
311
332
|
`postplus-cli:media:video-analysis:analyze:${randomUUID()}`,
|
|
312
333
|
quoteConfirmationToken: flags.values.get('quote-confirmation-token') ?? undefined,
|
|
313
334
|
};
|
|
314
|
-
return
|
|
335
|
+
return dispatchHostedCommand({
|
|
315
336
|
request: () => postHostedJson({
|
|
316
337
|
body,
|
|
317
338
|
pathName: '/api/postplus-cli/hosted/capability',
|
|
318
339
|
skillName: flags.values.get('skill') ?? resolved.skill,
|
|
340
|
+
context,
|
|
319
341
|
}),
|
|
320
|
-
errorInputLabel
|
|
342
|
+
errorInputLabel,
|
|
321
343
|
json: flags.booleans.has('json'),
|
|
322
344
|
outputPath,
|
|
323
|
-
});
|
|
345
|
+
}, context);
|
|
324
346
|
}
|
|
325
347
|
// `media-file upload`: the generic local-file -> hosted media verb. Released
|
|
326
348
|
// skills ship no scripts, so a skill that must place a local file behind hosted
|
|
@@ -346,15 +368,15 @@ function inferUploadMimeType(filePath) {
|
|
|
346
368
|
return (MEDIA_FILE_MIME_BY_EXTENSION[path.extname(filePath).toLowerCase()] ??
|
|
347
369
|
'application/octet-stream');
|
|
348
370
|
}
|
|
349
|
-
export async function runMediaFileCommand(args) {
|
|
371
|
+
export async function runMediaFileCommand(args, context) {
|
|
350
372
|
const [subcommand, ...rest] = args;
|
|
351
373
|
if (subcommand === 'upload') {
|
|
352
|
-
return runMediaFileUpload(rest);
|
|
374
|
+
return runMediaFileUpload(rest, context);
|
|
353
375
|
}
|
|
354
376
|
printMediaFileHelp();
|
|
355
377
|
return subcommand === undefined || isHelp(subcommand) ? 0 : 1;
|
|
356
378
|
}
|
|
357
|
-
async function runMediaFileUpload(args) {
|
|
379
|
+
async function runMediaFileUpload(args, context) {
|
|
358
380
|
const flags = parseFlags(args, new Set(['json']));
|
|
359
381
|
const allowedKeys = new Set([
|
|
360
382
|
'hosted-operation-id',
|
|
@@ -391,12 +413,13 @@ async function runMediaFileUpload(args) {
|
|
|
391
413
|
`postplus-cli:media-file:create-upload-url:${randomUUID()}`,
|
|
392
414
|
quoteConfirmationToken: flags.values.get('quote-confirmation-token') ?? undefined,
|
|
393
415
|
};
|
|
394
|
-
return
|
|
416
|
+
return dispatchHostedCommand({
|
|
395
417
|
request: async () => {
|
|
396
418
|
const payload = await postHostedJson({
|
|
397
419
|
body,
|
|
398
420
|
pathName: '/api/postplus-cli/hosted/capability',
|
|
399
421
|
skillName: flags.values.get('skill') ?? null,
|
|
422
|
+
context,
|
|
400
423
|
});
|
|
401
424
|
const output = readHostedUploadOutput(payload);
|
|
402
425
|
const signedUpload = readSignedUpload(output);
|
|
@@ -418,12 +441,13 @@ async function runMediaFileUpload(args) {
|
|
|
418
441
|
},
|
|
419
442
|
pathName: '/api/postplus-cli/hosted/capability',
|
|
420
443
|
skillName: flags.values.get('skill') ?? null,
|
|
444
|
+
context,
|
|
421
445
|
});
|
|
422
446
|
},
|
|
423
447
|
errorInputLabel: inputFile,
|
|
424
448
|
json: flags.booleans.has('json'),
|
|
425
449
|
outputPath,
|
|
426
|
-
});
|
|
450
|
+
}, context);
|
|
427
451
|
}
|
|
428
452
|
function readHostedUploadOutput(payload) {
|
|
429
453
|
if (payload && typeof payload === 'object' && !Array.isArray(payload)) {
|
|
@@ -492,6 +516,9 @@ Usage:
|
|
|
492
516
|
// Shared submit path for both surfaces: wrap the media input, derive billing
|
|
493
517
|
// dimensions from endpointKey + input, and POST to the Web boundary.
|
|
494
518
|
function submitMediaGenerationRequest(params) {
|
|
519
|
+
// Billing dimensions are derived solely at the Web boundary from
|
|
520
|
+
// (endpointKey, input); the CLI sends only the payload. The Web request schema
|
|
521
|
+
// rejects any caller-supplied `requestDimensions` (single source of truth).
|
|
495
522
|
const body = {
|
|
496
523
|
capability: params.capability,
|
|
497
524
|
endpointKey: params.endpointKey,
|
|
@@ -499,18 +526,18 @@ function submitMediaGenerationRequest(params) {
|
|
|
499
526
|
operation: 'request',
|
|
500
527
|
operationId: params.operationId,
|
|
501
528
|
quoteConfirmationToken: params.quoteConfirmationToken ?? undefined,
|
|
502
|
-
requestDimensions: buildMediaGenerationRequestDimensions(params.endpointKey, params.input),
|
|
503
529
|
};
|
|
504
|
-
return
|
|
530
|
+
return dispatchHostedCommand({
|
|
505
531
|
request: () => postHostedJson({
|
|
506
532
|
body,
|
|
507
533
|
pathName: '/api/postplus-cli/hosted/capability',
|
|
508
534
|
skillName: params.skillName,
|
|
535
|
+
context: params.context,
|
|
509
536
|
}),
|
|
510
537
|
errorInputLabel: params.errorInputLabel,
|
|
511
538
|
json: params.json,
|
|
512
539
|
outputPath: params.outputPath,
|
|
513
|
-
});
|
|
540
|
+
}, params.context);
|
|
514
541
|
}
|
|
515
542
|
// Poll a pending media-generation run: `postplus media poll --handle <run-id>`.
|
|
516
543
|
// A media `create`/`transcribe`/`analyze` submit returns an async run handle
|
|
@@ -522,11 +549,11 @@ function submitMediaGenerationRequest(params) {
|
|
|
522
549
|
// The body carries only the status quadruple; submit-only fields (input,
|
|
523
550
|
// requestDimensions, quoteConfirmationToken) are never sent. Mirrors the
|
|
524
551
|
// `research collect --run-handle` polling branch.
|
|
525
|
-
async function runMediaPoll(args) {
|
|
552
|
+
async function runMediaPoll(args, context) {
|
|
526
553
|
const flags = parseFlags(args, new Set(['json']));
|
|
527
554
|
const handle = requireFlag(flags, 'handle');
|
|
528
555
|
const outputPath = flags.values.get('output') ?? null;
|
|
529
|
-
return
|
|
556
|
+
return dispatchHostedCommand({
|
|
530
557
|
request: () => postHostedJson({
|
|
531
558
|
body: {
|
|
532
559
|
capability: 'media-generation',
|
|
@@ -536,11 +563,12 @@ async function runMediaPoll(args) {
|
|
|
536
563
|
},
|
|
537
564
|
pathName: '/api/postplus-cli/hosted/capability',
|
|
538
565
|
skillName: null,
|
|
566
|
+
context,
|
|
539
567
|
}),
|
|
540
568
|
errorInputLabel: 'media-poll-handle',
|
|
541
569
|
json: flags.booleans.has('json'),
|
|
542
570
|
outputPath,
|
|
543
|
-
});
|
|
571
|
+
}, context);
|
|
544
572
|
}
|
|
545
573
|
function buildMediaVerbInput(input) {
|
|
546
574
|
const record = {};
|
|
@@ -604,23 +632,24 @@ function buildMediaVerbInput(input) {
|
|
|
604
632
|
// schemaVersion envelope), and posts to /hosted/collection. The resolved entry
|
|
605
633
|
// gives the default skillName (overridable by `--skill`); the actorId stays
|
|
606
634
|
// internal and is never placed on the public body.
|
|
607
|
-
async function runResearchCollect(args) {
|
|
635
|
+
async function runResearchCollect(args, context) {
|
|
608
636
|
const [first, ...rest] = args;
|
|
609
637
|
// Polling path: `research collect --run-handle <h>`. No positional collectionKey.
|
|
610
638
|
if (!first || first.startsWith('--')) {
|
|
611
639
|
const flags = parseFlags(args, new Set(['json']));
|
|
612
640
|
const runHandle = requireFlag(flags, 'run-handle');
|
|
613
641
|
const outputPath = flags.values.get('output') ?? null;
|
|
614
|
-
return
|
|
642
|
+
return dispatchHostedCommand({
|
|
615
643
|
request: () => postHostedJson({
|
|
616
644
|
body: { runHandle, runHandleType: 'hosted-collection' },
|
|
617
645
|
pathName: '/api/postplus-cli/hosted/collection',
|
|
618
646
|
skillName: null,
|
|
647
|
+
context,
|
|
619
648
|
}),
|
|
620
649
|
errorInputLabel: 'research-collect-run-handle',
|
|
621
650
|
json: flags.booleans.has('json'),
|
|
622
651
|
outputPath,
|
|
623
|
-
});
|
|
652
|
+
}, context);
|
|
624
653
|
}
|
|
625
654
|
const verb = 'collect';
|
|
626
655
|
const collectionKey = first;
|
|
@@ -650,9 +679,8 @@ async function runResearchCollect(args) {
|
|
|
650
679
|
throw new Error(`Unknown option for research ${verb}: --${key}.`);
|
|
651
680
|
}
|
|
652
681
|
}
|
|
653
|
-
const requestPath = requireFlag(flags, 'request');
|
|
654
682
|
const outputPath = flags.values.get('output') ?? null;
|
|
655
|
-
const raw = await
|
|
683
|
+
const { body: raw, errorInputLabel } = await resolveRequestBody(context, flags);
|
|
656
684
|
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
657
685
|
throw new Error(`research ${verb} ${collectionKey} --request must be a JSON object of collection input.`);
|
|
658
686
|
}
|
|
@@ -671,7 +699,7 @@ async function runResearchCollect(args) {
|
|
|
671
699
|
}
|
|
672
700
|
maxTotalChargeUsd = parsed;
|
|
673
701
|
}
|
|
674
|
-
return
|
|
702
|
+
return dispatchHostedCommand({
|
|
675
703
|
request: () => postHostedJson({
|
|
676
704
|
body: {
|
|
677
705
|
collectionKey,
|
|
@@ -683,11 +711,12 @@ async function runResearchCollect(args) {
|
|
|
683
711
|
},
|
|
684
712
|
pathName: '/api/postplus-cli/hosted/collection',
|
|
685
713
|
skillName,
|
|
714
|
+
context,
|
|
686
715
|
}),
|
|
687
|
-
errorInputLabel
|
|
716
|
+
errorInputLabel,
|
|
688
717
|
json: flags.booleans.has('json'),
|
|
689
718
|
outputPath,
|
|
690
|
-
});
|
|
719
|
+
}, context);
|
|
691
720
|
}
|
|
692
721
|
// Manifest-driven public-content-collection verb (request-json surface). Resolves
|
|
693
722
|
// the positional `<sourceKey>` against the research verb index for verb `scrape`,
|
|
@@ -696,7 +725,7 @@ async function runResearchCollect(args) {
|
|
|
696
725
|
// capability `public-content-collection` / operation `scrape`. The resolved entry
|
|
697
726
|
// gives the default skillName (overridable by `--skill`); the datasetId stays
|
|
698
727
|
// internal and is never placed on the public body.
|
|
699
|
-
async function runResearchScrape(args) {
|
|
728
|
+
async function runResearchScrape(args, context) {
|
|
700
729
|
const [first, ...rest] = args;
|
|
701
730
|
const verb = 'scrape';
|
|
702
731
|
const targets = RESEARCH_VERB_TARGETS.get(verb);
|
|
@@ -704,16 +733,17 @@ async function runResearchScrape(args) {
|
|
|
704
733
|
const flags = parseFlags(args, new Set(['json']));
|
|
705
734
|
const runHandle = requireFlag(flags, 'run-handle');
|
|
706
735
|
const outputPath = flags.values.get('output') ?? null;
|
|
707
|
-
return
|
|
736
|
+
return dispatchHostedCommand({
|
|
708
737
|
request: () => postHostedJson({
|
|
709
738
|
body: { runHandle, runHandleType: 'public-content-collection' },
|
|
710
739
|
pathName: '/api/postplus-cli/hosted/collection',
|
|
711
740
|
skillName: null,
|
|
741
|
+
context,
|
|
712
742
|
}),
|
|
713
743
|
errorInputLabel: 'research-scrape-run-handle',
|
|
714
744
|
json: flags.booleans.has('json'),
|
|
715
745
|
outputPath,
|
|
716
|
-
});
|
|
746
|
+
}, context);
|
|
717
747
|
}
|
|
718
748
|
const sourceKey = first;
|
|
719
749
|
const resolved = targets?.get(sourceKey);
|
|
@@ -741,9 +771,8 @@ async function runResearchScrape(args) {
|
|
|
741
771
|
throw new Error(`Unknown option for research ${verb}: --${key}.`);
|
|
742
772
|
}
|
|
743
773
|
}
|
|
744
|
-
const requestPath = requireFlag(flags, 'request');
|
|
745
774
|
const outputPath = flags.values.get('output') ?? null;
|
|
746
|
-
const raw = await
|
|
775
|
+
const { body: raw, errorInputLabel } = await resolveRequestBody(context, flags);
|
|
747
776
|
if (!Array.isArray(raw) || raw.length === 0) {
|
|
748
777
|
throw new Error(`research ${verb} ${sourceKey} --request must be a non-empty JSON array of scrape input records.`);
|
|
749
778
|
}
|
|
@@ -764,7 +793,7 @@ async function runResearchScrape(args) {
|
|
|
764
793
|
}
|
|
765
794
|
// The Web /hosted/capability scrape contract is a strict object: skillName is
|
|
766
795
|
// carried as the compatibility header (postHostedJson), never on the public body.
|
|
767
|
-
return
|
|
796
|
+
return dispatchHostedCommand({
|
|
768
797
|
request: () => postHostedJson({
|
|
769
798
|
body: {
|
|
770
799
|
capability: 'public-content-collection',
|
|
@@ -777,11 +806,12 @@ async function runResearchScrape(args) {
|
|
|
777
806
|
},
|
|
778
807
|
pathName: '/api/postplus-cli/hosted/capability',
|
|
779
808
|
skillName,
|
|
809
|
+
context,
|
|
780
810
|
}),
|
|
781
|
-
errorInputLabel
|
|
811
|
+
errorInputLabel,
|
|
782
812
|
json: flags.booleans.has('json'),
|
|
783
813
|
outputPath,
|
|
784
|
-
});
|
|
814
|
+
}, context);
|
|
785
815
|
}
|
|
786
816
|
// Manifest-driven publish operation (request-json surface). The OPERATION is the
|
|
787
817
|
// subcommand and the target: `postplus publish <operation> --request <file>`. The
|
|
@@ -790,7 +820,7 @@ async function runResearchScrape(args) {
|
|
|
790
820
|
// Side-effecting operations surface the Web quote-confirmation challenge; the
|
|
791
821
|
// shared runHostedCommand handles the challenge -> retry-with-token path. There is
|
|
792
822
|
// no requestDimensions/approval/execute — those were private-runtime concepts.
|
|
793
|
-
async function runPublishOperation(operation, args) {
|
|
823
|
+
async function runPublishOperation(operation, args, context) {
|
|
794
824
|
const resolved = PUBLISH_VERB_OPERATIONS.get(operation);
|
|
795
825
|
if (!resolved) {
|
|
796
826
|
throw new Error(`Unknown publish operation ${operation}. Valid: ${[...PUBLISH_VERB_OPERATIONS.keys()].join(', ')}.`);
|
|
@@ -814,9 +844,8 @@ async function runPublishOperation(operation, args) {
|
|
|
814
844
|
throw new Error(`Unknown option for publish ${operation}: --${key}.`);
|
|
815
845
|
}
|
|
816
846
|
}
|
|
817
|
-
const requestPath = requireFlag(flags, 'request');
|
|
818
847
|
const outputPath = flags.values.get('output') ?? null;
|
|
819
|
-
const raw = await
|
|
848
|
+
const { body: raw, errorInputLabel } = await resolveRequestBody(context, flags);
|
|
820
849
|
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
821
850
|
throw new Error(`publish ${operation} --request must be a JSON object of publishing input.`);
|
|
822
851
|
}
|
|
@@ -825,7 +854,7 @@ async function runPublishOperation(operation, args) {
|
|
|
825
854
|
const operationId = flags.values.get('hosted-operation-id') ??
|
|
826
855
|
`postplus-cli:publish:social-publishing:request:${randomUUID()}`;
|
|
827
856
|
const quoteConfirmationToken = flags.values.get('quote-confirmation-token');
|
|
828
|
-
return
|
|
857
|
+
return dispatchHostedCommand({
|
|
829
858
|
request: () => postHostedJson({
|
|
830
859
|
body: {
|
|
831
860
|
capability: 'social-publishing',
|
|
@@ -836,13 +865,14 @@ async function runPublishOperation(operation, args) {
|
|
|
836
865
|
},
|
|
837
866
|
pathName: '/api/postplus-cli/hosted/capability',
|
|
838
867
|
skillName,
|
|
868
|
+
context,
|
|
839
869
|
}),
|
|
840
|
-
errorInputLabel
|
|
870
|
+
errorInputLabel,
|
|
841
871
|
json: flags.booleans.has('json'),
|
|
842
872
|
outputPath,
|
|
843
|
-
});
|
|
873
|
+
}, context);
|
|
844
874
|
}
|
|
845
|
-
async function runHostedSchema(domain, args) {
|
|
875
|
+
async function runHostedSchema(domain, args, context) {
|
|
846
876
|
const flags = parseFlags(args, new Set(['json']));
|
|
847
877
|
const allowedFlags = domain === 'media'
|
|
848
878
|
? new Set(['endpoint'])
|
|
@@ -854,24 +884,40 @@ async function runHostedSchema(domain, args) {
|
|
|
854
884
|
throw new Error(`Unknown option for ${domain} schema: --${key}.`);
|
|
855
885
|
}
|
|
856
886
|
}
|
|
857
|
-
|
|
887
|
+
const report = buildHostedRequestSchemaReport({
|
|
858
888
|
collectionKey: flags.values.get('collection-key') ?? null,
|
|
859
889
|
domain,
|
|
860
890
|
endpointKey: flags.values.get('endpoint') ?? null,
|
|
861
|
-
})
|
|
891
|
+
});
|
|
892
|
+
// In-process / context path: RETURN the structured catalog so the model
|
|
893
|
+
// receives it as the call result. The bin path (no context) keeps writeJson +
|
|
894
|
+
// return 0 for human CLI stdout output. Mirrors the spend-verb dispatch.
|
|
895
|
+
if (context) {
|
|
896
|
+
return report;
|
|
897
|
+
}
|
|
898
|
+
writeJson(report);
|
|
862
899
|
return 0;
|
|
863
900
|
}
|
|
864
901
|
async function postHostedJson(input) {
|
|
865
|
-
const
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
902
|
+
const response = input.context
|
|
903
|
+
? await sendAuthedCloudRequest({
|
|
904
|
+
auth: input.context.auth,
|
|
905
|
+
body: input.body,
|
|
906
|
+
method: 'POST',
|
|
907
|
+
pathName: input.pathName,
|
|
908
|
+
skillName: input.skillName,
|
|
909
|
+
skillsReleaseId: input.context.skillsReleaseId ?? null,
|
|
910
|
+
timeoutMs: 120000,
|
|
911
|
+
})
|
|
912
|
+
: await sendAuthedCloudRequest({
|
|
913
|
+
auth: await resolveFreshRemoteAuth(),
|
|
914
|
+
body: input.body,
|
|
915
|
+
method: 'POST',
|
|
916
|
+
pathName: input.pathName,
|
|
917
|
+
retryOn401: () => resolveFreshRemoteAuth({ forceRefresh: true }),
|
|
918
|
+
skillName: input.skillName,
|
|
919
|
+
timeoutMs: 120000,
|
|
920
|
+
});
|
|
875
921
|
const payload = await readJsonResponse(response);
|
|
876
922
|
if (!response.ok) {
|
|
877
923
|
const productError = readHostedProductError(payload);
|
|
@@ -887,7 +933,7 @@ async function postHostedJson(input) {
|
|
|
887
933
|
}
|
|
888
934
|
return payload;
|
|
889
935
|
}
|
|
890
|
-
// Single exit path for
|
|
936
|
+
// Single exit path for the BIN hosted command: success writes the result and
|
|
891
937
|
// returns 0; a quote challenge writes the challenge file and rethrows actionable
|
|
892
938
|
// guidance; a structured product error writes the full error envelope to the
|
|
893
939
|
// result JSON and surfaces code/layer/operationId on the terminal, exiting 1.
|
|
@@ -919,6 +965,20 @@ async function runHostedCommand(input) {
|
|
|
919
965
|
await writeResult(payload, input.outputPath, input.json);
|
|
920
966
|
return 0;
|
|
921
967
|
}
|
|
968
|
+
// Single exit path for both BIN and LIB hosted commands. Each dispatch function
|
|
969
|
+
// builds the SAME `request` closure (resolve verb -> build envelope -> POST) and
|
|
970
|
+
// hands it here. The bin path (no `context`) keeps stdout/file/exit-code behavior
|
|
971
|
+
// via runHostedCommand. The lib path (with `context`) returns the parsed payload
|
|
972
|
+
// and rethrows the structured HostedProductRequestError / quote-confirmation error
|
|
973
|
+
// VERBATIM — no stdout, no file writes, no exit code — so the in-process caller
|
|
974
|
+
// surfaces the structured JSON and fails honestly. Because the closure is shared,
|
|
975
|
+
// the wire request (URL + body + headers) is byte-identical across both paths.
|
|
976
|
+
async function dispatchHostedCommand(input, context) {
|
|
977
|
+
if (!context) {
|
|
978
|
+
return runHostedCommand(input);
|
|
979
|
+
}
|
|
980
|
+
return input.request();
|
|
981
|
+
}
|
|
922
982
|
async function writeQuoteConfirmationChallenge(error, input) {
|
|
923
983
|
const challengePath = path.resolve(input.outputPath
|
|
924
984
|
? `${input.outputPath}.quote-confirmation.json`
|