@postplus/cli 0.1.41 → 0.1.43
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/auth-lifecycle.js +6 -11
- package/build/auth-login.js +4 -9
- package/build/auth-session.js +6 -11
- package/build/auth-validate.js +8 -21
- package/build/authed-cloud-request.js +42 -0
- package/build/doctor.js +20 -27
- package/build/generated/hosted-execution-manifest.generated.js +58 -2
- package/build/hosted-domain-commands.js +68 -50
- package/package.json +2 -1
package/build/auth-lifecycle.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { formatAccountBindingLines } from './account-binding-display.js';
|
|
2
2
|
import { refreshRemoteAuthSession } from './auth-session.js';
|
|
3
3
|
import { clearAuthState, generateAuthStatusReport } from './auth.js';
|
|
4
|
-
import {
|
|
4
|
+
import { sendAuthedCloudRequest } from './authed-cloud-request.js';
|
|
5
|
+
import { formatPostPlusCompatibilityError } from './client-compatibility.js';
|
|
5
6
|
import { requireHostedBaseUrl } from './hosted-release.js';
|
|
6
7
|
import { resolveCliSessionTokenState } from './local-state.js';
|
|
7
8
|
import { readSubscriptionStatusField } from './subscription-status.js';
|
|
@@ -38,17 +39,11 @@ export async function revokeRemoteAuth() {
|
|
|
38
39
|
if (!cliSessionTokenState.present || !cliSessionTokenState.value) {
|
|
39
40
|
throw new Error('Run `postplus auth login` before revoking PostPlus auth.');
|
|
40
41
|
}
|
|
41
|
-
const
|
|
42
|
-
|
|
42
|
+
const response = await sendAuthedCloudRequest({
|
|
43
|
+
auth: { apiBaseUrl, cliSessionToken: cliSessionTokenState.value },
|
|
44
|
+
body: {},
|
|
43
45
|
method: 'POST',
|
|
44
|
-
|
|
45
|
-
accept: 'application/json',
|
|
46
|
-
...compatibilityHeaders,
|
|
47
|
-
authorization: `Bearer ${cliSessionTokenState.value}`,
|
|
48
|
-
'content-type': 'application/json',
|
|
49
|
-
},
|
|
50
|
-
body: JSON.stringify({}),
|
|
51
|
-
signal: AbortSignal.timeout(15000),
|
|
46
|
+
pathName: '/api/postplus-cli/auth/revoke',
|
|
52
47
|
});
|
|
53
48
|
const payload = (await response.json());
|
|
54
49
|
if (!response.ok) {
|
package/build/auth-login.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { sendAuthedCloudRequest } from './authed-cloud-request.js';
|
|
2
3
|
import { buildPostPlusClientCompatibilityHeaders, formatPostPlusCompatibilityError, writeCurrentCliVersionToLocalConfig, } from './client-compatibility.js';
|
|
3
4
|
import { requireHostedBaseUrl } from './hosted-release.js';
|
|
4
5
|
import { setLocalSession } from './local-state.js';
|
|
@@ -127,15 +128,9 @@ export async function pollCloudAuthLogin(input) {
|
|
|
127
128
|
throw new Error('PostPlus CLI sign-in poll returned incomplete data.');
|
|
128
129
|
}
|
|
129
130
|
export async function validateCliSession(input) {
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
headers: {
|
|
134
|
-
accept: 'application/json',
|
|
135
|
-
...compatibilityHeaders,
|
|
136
|
-
authorization: `Bearer ${input.cliSessionToken}`,
|
|
137
|
-
},
|
|
138
|
-
signal: AbortSignal.timeout(15000),
|
|
131
|
+
const response = await sendAuthedCloudRequest({
|
|
132
|
+
auth: input,
|
|
133
|
+
pathName: '/api/postplus-cli/auth/whoami',
|
|
139
134
|
});
|
|
140
135
|
const payload = (await response.json());
|
|
141
136
|
if (!response.ok) {
|
package/build/auth-session.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { sendAuthedCloudRequest } from './authed-cloud-request.js';
|
|
2
|
+
import { formatPostPlusCompatibilityError, writeCurrentCliVersionToLocalConfig, } from './client-compatibility.js';
|
|
2
3
|
import { requireHostedBaseUrl } from './hosted-release.js';
|
|
3
4
|
import { resolveCliSessionTokenState, setLocalSession } from './local-state.js';
|
|
4
5
|
export async function resolveFreshRemoteAuth(options = {}) {
|
|
@@ -39,17 +40,11 @@ export async function refreshRemoteAuthSession(input) {
|
|
|
39
40
|
if (!cliSessionToken) {
|
|
40
41
|
throw new Error('Run `postplus auth login` before refreshing PostPlus auth.');
|
|
41
42
|
}
|
|
42
|
-
const
|
|
43
|
-
|
|
43
|
+
const response = await sendAuthedCloudRequest({
|
|
44
|
+
auth: { apiBaseUrl, cliSessionToken },
|
|
45
|
+
body: {},
|
|
44
46
|
method: 'POST',
|
|
45
|
-
|
|
46
|
-
accept: 'application/json',
|
|
47
|
-
...compatibilityHeaders,
|
|
48
|
-
authorization: `Bearer ${cliSessionToken}`,
|
|
49
|
-
'content-type': 'application/json',
|
|
50
|
-
},
|
|
51
|
-
body: JSON.stringify({}),
|
|
52
|
-
signal: AbortSignal.timeout(15000),
|
|
47
|
+
pathName: '/api/postplus-cli/auth/refresh',
|
|
53
48
|
});
|
|
54
49
|
const payload = (await response.json());
|
|
55
50
|
if (!response.ok) {
|
package/build/auth-validate.js
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import { formatAccountBindingLines } from './account-binding-display.js';
|
|
2
2
|
import { resolveFreshRemoteAuth } from './auth-session.js';
|
|
3
|
-
import {
|
|
3
|
+
import { sendAuthedCloudRequest } from './authed-cloud-request.js';
|
|
4
|
+
import { formatPostPlusCompatibilityError } from './client-compatibility.js';
|
|
4
5
|
import { readSubscriptionStatusField } from './subscription-status.js';
|
|
5
6
|
export async function validateRemoteAuth() {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
auth
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
response = await fetchWhoami(auth);
|
|
13
|
-
}
|
|
7
|
+
const auth = await resolveFreshRemoteAuth();
|
|
8
|
+
const response = await sendAuthedCloudRequest({
|
|
9
|
+
auth,
|
|
10
|
+
pathName: '/api/postplus-cli/auth/whoami',
|
|
11
|
+
retryOn401: () => resolveFreshRemoteAuth({ forceRefresh: true }),
|
|
12
|
+
});
|
|
14
13
|
const payload = (await response.json());
|
|
15
14
|
if (!response.ok) {
|
|
16
15
|
const compatibilityError = formatPostPlusCompatibilityError(payload);
|
|
@@ -61,18 +60,6 @@ function readAccountType(payload) {
|
|
|
61
60
|
}
|
|
62
61
|
return value;
|
|
63
62
|
}
|
|
64
|
-
async function fetchWhoami(input) {
|
|
65
|
-
const compatibilityHeaders = await buildPostPlusClientCompatibilityHeaders();
|
|
66
|
-
return fetch(`${input.apiBaseUrl}/api/postplus-cli/auth/whoami`, {
|
|
67
|
-
method: 'GET',
|
|
68
|
-
headers: {
|
|
69
|
-
accept: 'application/json',
|
|
70
|
-
...compatibilityHeaders,
|
|
71
|
-
authorization: `Bearer ${input.cliSessionToken}`,
|
|
72
|
-
},
|
|
73
|
-
signal: AbortSignal.timeout(15000),
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
63
|
function readRequiredString(payload, fieldName) {
|
|
77
64
|
const value = payload[fieldName];
|
|
78
65
|
if (typeof value !== 'string' || !value.trim()) {
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { buildPostPlusClientCompatibilityHeaders } from './client-compatibility.js';
|
|
2
|
+
const DEFAULT_AUTHED_REQUEST_TIMEOUT_MS = 15_000;
|
|
3
|
+
/**
|
|
4
|
+
* Single transport envelope for every authenticated PostPlus Cloud request:
|
|
5
|
+
* canonical header set (`accept` + compatibility headers + `Bearer` token +
|
|
6
|
+
* optional `content-type`), `AbortSignal.timeout`, and an optional once-only
|
|
7
|
+
* 401-refresh-retry. It returns the raw `Response` so each caller keeps its own
|
|
8
|
+
* `!ok` interpretation — this is a narrow transport primitive, not a request
|
|
9
|
+
* framework.
|
|
10
|
+
*/
|
|
11
|
+
export async function sendAuthedCloudRequest(input) {
|
|
12
|
+
let response = await issueAuthedCloudRequest(input.auth, input);
|
|
13
|
+
if (response.status === 401 && input.retryOn401) {
|
|
14
|
+
const refreshedAuth = await input.retryOn401();
|
|
15
|
+
response = await issueAuthedCloudRequest(refreshedAuth, input);
|
|
16
|
+
}
|
|
17
|
+
return response;
|
|
18
|
+
}
|
|
19
|
+
async function issueAuthedCloudRequest(auth, input) {
|
|
20
|
+
const compatibilityHeaders = await buildPostPlusClientCompatibilityHeaders({
|
|
21
|
+
skillName: input.skillName ?? null,
|
|
22
|
+
});
|
|
23
|
+
const hasBody = input.body !== undefined;
|
|
24
|
+
const headers = {
|
|
25
|
+
accept: 'application/json',
|
|
26
|
+
...compatibilityHeaders,
|
|
27
|
+
authorization: `Bearer ${auth.cliSessionToken}`,
|
|
28
|
+
};
|
|
29
|
+
if (hasBody) {
|
|
30
|
+
headers['content-type'] = 'application/json';
|
|
31
|
+
}
|
|
32
|
+
const requestUrl = new URL(input.pathName, normalizeBaseUrl(auth.apiBaseUrl));
|
|
33
|
+
return fetch(requestUrl, {
|
|
34
|
+
method: input.method ?? 'GET',
|
|
35
|
+
headers,
|
|
36
|
+
...(hasBody ? { body: JSON.stringify(input.body) } : {}),
|
|
37
|
+
signal: AbortSignal.timeout(input.timeoutMs ?? DEFAULT_AUTHED_REQUEST_TIMEOUT_MS),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
function normalizeBaseUrl(apiBaseUrl) {
|
|
41
|
+
return apiBaseUrl.endsWith('/') ? apiBaseUrl : `${apiBaseUrl}/`;
|
|
42
|
+
}
|
package/build/doctor.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { formatAccountBindingName } from './account-binding-display.js';
|
|
2
2
|
import { resolveFreshRemoteAuth, } from './auth-session.js';
|
|
3
|
-
import {
|
|
3
|
+
import { sendAuthedCloudRequest } from './authed-cloud-request.js';
|
|
4
|
+
import { formatPostPlusCompatibilityError } from './client-compatibility.js';
|
|
4
5
|
import { resolveHostedBaseUrl } from './hosted-release.js';
|
|
5
6
|
import { formatLocalDependencyReport, generateLocalDependencyReport, } from './local-dependencies.js';
|
|
6
7
|
import { loadPublicSkillCatalog, } from './skill-catalog.js';
|
|
@@ -136,13 +137,11 @@ function buildDoctorReport(checks, skillId) {
|
|
|
136
137
|
}
|
|
137
138
|
async function checkRemoteAuth(input) {
|
|
138
139
|
try {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
response = await requestWithAuth(refreshedAuth, '/api/postplus-cli/auth/whoami');
|
|
145
|
-
}
|
|
140
|
+
const response = await sendAuthedCloudRequest({
|
|
141
|
+
auth: input,
|
|
142
|
+
pathName: '/api/postplus-cli/auth/whoami',
|
|
143
|
+
retryOn401: () => resolveFreshRemoteAuth({ forceRefresh: true }),
|
|
144
|
+
});
|
|
146
145
|
const payload = (await response.json());
|
|
147
146
|
if (!response.ok) {
|
|
148
147
|
return createFail('remote_auth', 'Remote auth', readErrorMessage(payload, 'PostPlus Cloud rejected the CLI session.'), 'Run `postplus auth login`.');
|
|
@@ -172,13 +171,11 @@ async function checkRemoteAuth(input) {
|
|
|
172
171
|
}
|
|
173
172
|
async function checkHostedCapabilities(input, skillScope) {
|
|
174
173
|
try {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
response = await requestWithAuth(refreshedAuth, '/api/postplus-cli/hosted/readiness');
|
|
181
|
-
}
|
|
174
|
+
const response = await sendAuthedCloudRequest({
|
|
175
|
+
auth: input,
|
|
176
|
+
pathName: '/api/postplus-cli/hosted/readiness',
|
|
177
|
+
retryOn401: () => resolveFreshRemoteAuth({ forceRefresh: true }),
|
|
178
|
+
});
|
|
182
179
|
const payload = (await response.json());
|
|
183
180
|
if (!response.ok) {
|
|
184
181
|
return createFail('hosted_capabilities', 'Hosted capabilities', readErrorMessage(payload, 'PostPlus Cloud hosted readiness check failed.'));
|
|
@@ -389,8 +386,15 @@ function collectHostedRequirementKeys(requirements) {
|
|
|
389
386
|
...requirements.sourceKeys,
|
|
390
387
|
]);
|
|
391
388
|
}
|
|
389
|
+
// Capability families whose sub-keys are intentionally NOT expressible as catalog
|
|
390
|
+
// requirement keys: a skill requires the bare family capability, and any released
|
|
391
|
+
// sub-key readiness row satisfies it. social-publishing operations and
|
|
392
|
+
// public-content-discovery tools (e.g. web-search) both have no requirement-key
|
|
393
|
+
// binding, so requiring the family must match the whole family. Without this,
|
|
394
|
+
// `public-content-discovery:web-search` readiness is filtered out for skills that
|
|
395
|
+
// require `public-content-discovery`, producing a false "readiness check missing".
|
|
392
396
|
function isWholeFamilyHostedCapability(prefix) {
|
|
393
|
-
return prefix === 'social-publishing';
|
|
397
|
+
return prefix === 'public-content-discovery' || prefix === 'social-publishing';
|
|
394
398
|
}
|
|
395
399
|
function requiresSocialPublishingPlan(requirements) {
|
|
396
400
|
return requirements.hostedCapabilities.includes('social-publishing');
|
|
@@ -452,17 +456,6 @@ function readErrorMessage(payload, fallback) {
|
|
|
452
456
|
? payload.error
|
|
453
457
|
: fallback;
|
|
454
458
|
}
|
|
455
|
-
async function requestWithAuth(input, path) {
|
|
456
|
-
const compatibilityHeaders = await buildPostPlusClientCompatibilityHeaders();
|
|
457
|
-
return fetch(`${input.apiBaseUrl}${path}`, {
|
|
458
|
-
headers: {
|
|
459
|
-
accept: 'application/json',
|
|
460
|
-
...compatibilityHeaders,
|
|
461
|
-
authorization: `Bearer ${input.cliSessionToken}`,
|
|
462
|
-
},
|
|
463
|
-
signal: AbortSignal.timeout(15000),
|
|
464
|
-
});
|
|
465
|
-
}
|
|
466
459
|
export function formatDoctorReport(report) {
|
|
467
460
|
const lines = ['PostPlus CLI doctor', ''];
|
|
468
461
|
for (const check of report.checks) {
|
|
@@ -441,6 +441,10 @@ export const HOSTED_EXECUTION_MANIFESTS = {
|
|
|
441
441
|
"class": "default",
|
|
442
442
|
"flag": "--output-format",
|
|
443
443
|
"type": "string",
|
|
444
|
+
"enumValues": [
|
|
445
|
+
"png",
|
|
446
|
+
"jpeg"
|
|
447
|
+
],
|
|
444
448
|
"default": "png",
|
|
445
449
|
"required": false
|
|
446
450
|
},
|
|
@@ -518,6 +522,10 @@ export const HOSTED_EXECUTION_MANIFESTS = {
|
|
|
518
522
|
"class": "default",
|
|
519
523
|
"flag": "--output-format",
|
|
520
524
|
"type": "string",
|
|
525
|
+
"enumValues": [
|
|
526
|
+
"png",
|
|
527
|
+
"jpeg"
|
|
528
|
+
],
|
|
521
529
|
"default": "png",
|
|
522
530
|
"required": false
|
|
523
531
|
},
|
|
@@ -576,6 +584,10 @@ export const HOSTED_EXECUTION_MANIFESTS = {
|
|
|
576
584
|
"class": "default",
|
|
577
585
|
"flag": "--output-format",
|
|
578
586
|
"type": "string",
|
|
587
|
+
"enumValues": [
|
|
588
|
+
"png",
|
|
589
|
+
"jpeg"
|
|
590
|
+
],
|
|
579
591
|
"default": "png",
|
|
580
592
|
"required": false
|
|
581
593
|
},
|
|
@@ -634,6 +646,10 @@ export const HOSTED_EXECUTION_MANIFESTS = {
|
|
|
634
646
|
"class": "default",
|
|
635
647
|
"flag": "--output-format",
|
|
636
648
|
"type": "string",
|
|
649
|
+
"enumValues": [
|
|
650
|
+
"png",
|
|
651
|
+
"jpeg"
|
|
652
|
+
],
|
|
637
653
|
"default": "png",
|
|
638
654
|
"required": false
|
|
639
655
|
},
|
|
@@ -692,6 +708,10 @@ export const HOSTED_EXECUTION_MANIFESTS = {
|
|
|
692
708
|
"class": "default",
|
|
693
709
|
"flag": "--output-format",
|
|
694
710
|
"type": "string",
|
|
711
|
+
"enumValues": [
|
|
712
|
+
"png",
|
|
713
|
+
"jpeg"
|
|
714
|
+
],
|
|
695
715
|
"default": "png",
|
|
696
716
|
"required": false
|
|
697
717
|
},
|
|
@@ -758,6 +778,10 @@ export const HOSTED_EXECUTION_MANIFESTS = {
|
|
|
758
778
|
"class": "default",
|
|
759
779
|
"flag": "--output-format",
|
|
760
780
|
"type": "string",
|
|
781
|
+
"enumValues": [
|
|
782
|
+
"png",
|
|
783
|
+
"jpeg"
|
|
784
|
+
],
|
|
761
785
|
"default": "png",
|
|
762
786
|
"required": false
|
|
763
787
|
},
|
|
@@ -824,6 +848,10 @@ export const HOSTED_EXECUTION_MANIFESTS = {
|
|
|
824
848
|
"class": "default",
|
|
825
849
|
"flag": "--output-format",
|
|
826
850
|
"type": "string",
|
|
851
|
+
"enumValues": [
|
|
852
|
+
"png",
|
|
853
|
+
"jpeg"
|
|
854
|
+
],
|
|
827
855
|
"default": "png",
|
|
828
856
|
"required": false
|
|
829
857
|
},
|
|
@@ -890,6 +918,10 @@ export const HOSTED_EXECUTION_MANIFESTS = {
|
|
|
890
918
|
"class": "default",
|
|
891
919
|
"flag": "--output-format",
|
|
892
920
|
"type": "string",
|
|
921
|
+
"enumValues": [
|
|
922
|
+
"png",
|
|
923
|
+
"jpeg"
|
|
924
|
+
],
|
|
893
925
|
"default": "png",
|
|
894
926
|
"required": false
|
|
895
927
|
},
|
|
@@ -1308,7 +1340,6 @@ export const HOSTED_EXECUTION_MANIFESTS = {
|
|
|
1308
1340
|
"flag": null,
|
|
1309
1341
|
"type": "string",
|
|
1310
1342
|
"enumValues": [
|
|
1311
|
-
"480p",
|
|
1312
1343
|
"720p",
|
|
1313
1344
|
"1080p"
|
|
1314
1345
|
],
|
|
@@ -1467,7 +1498,6 @@ export const HOSTED_EXECUTION_MANIFESTS = {
|
|
|
1467
1498
|
"flag": null,
|
|
1468
1499
|
"type": "string",
|
|
1469
1500
|
"enumValues": [
|
|
1470
|
-
"480p",
|
|
1471
1501
|
"720p",
|
|
1472
1502
|
"1080p"
|
|
1473
1503
|
],
|
|
@@ -2388,6 +2418,19 @@ export const HOSTED_EXECUTION_MANIFESTS = {
|
|
|
2388
2418
|
"class": "default",
|
|
2389
2419
|
"flag": null,
|
|
2390
2420
|
"type": "string",
|
|
2421
|
+
"enumValues": [
|
|
2422
|
+
"auto",
|
|
2423
|
+
"Chinese",
|
|
2424
|
+
"English",
|
|
2425
|
+
"German",
|
|
2426
|
+
"Italian",
|
|
2427
|
+
"Portuguese",
|
|
2428
|
+
"Spanish",
|
|
2429
|
+
"Japanese",
|
|
2430
|
+
"Korean",
|
|
2431
|
+
"French",
|
|
2432
|
+
"Russian"
|
|
2433
|
+
],
|
|
2391
2434
|
"default": "auto",
|
|
2392
2435
|
"required": false
|
|
2393
2436
|
},
|
|
@@ -2448,6 +2491,19 @@ export const HOSTED_EXECUTION_MANIFESTS = {
|
|
|
2448
2491
|
"class": "default",
|
|
2449
2492
|
"flag": null,
|
|
2450
2493
|
"type": "string",
|
|
2494
|
+
"enumValues": [
|
|
2495
|
+
"auto",
|
|
2496
|
+
"Chinese",
|
|
2497
|
+
"English",
|
|
2498
|
+
"German",
|
|
2499
|
+
"Italian",
|
|
2500
|
+
"Portuguese",
|
|
2501
|
+
"Spanish",
|
|
2502
|
+
"Japanese",
|
|
2503
|
+
"Korean",
|
|
2504
|
+
"French",
|
|
2505
|
+
"Russian"
|
|
2506
|
+
],
|
|
2451
2507
|
"default": "auto",
|
|
2452
2508
|
"required": false
|
|
2453
2509
|
},
|
|
@@ -3,9 +3,10 @@ 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 {
|
|
7
|
-
import {
|
|
6
|
+
import { sendAuthedCloudRequest } from './authed-cloud-request.js';
|
|
7
|
+
import { formatPostPlusCompatibilityError } from './client-compatibility.js';
|
|
8
8
|
import { buildVerbTargetIndex, } from './hosted-manifest-index.js';
|
|
9
|
+
import { buildHostedRequestSchemaReport, buildMediaGenerationRequestDimensions, } from './hosted-request-schemas.js';
|
|
9
10
|
import { readLargeCreditQuoteConfirmationChallenge, } from './quote-confirmation.js';
|
|
10
11
|
// Manifest-driven verb grammar indexes (SSOT projected from apps/web +
|
|
11
12
|
// public-skill-metadata via the generated manifest). The verb/flag grammar,
|
|
@@ -66,7 +67,9 @@ export async function runHostedDomainCommand(domain, args) {
|
|
|
66
67
|
if (domain === 'media' && subcommand === 'poll') {
|
|
67
68
|
return runMediaPoll(rest);
|
|
68
69
|
}
|
|
69
|
-
if (domain === 'media' &&
|
|
70
|
+
if (domain === 'media' &&
|
|
71
|
+
subcommand &&
|
|
72
|
+
MEDIA_VERB_ENDPOINTS.has(subcommand)) {
|
|
70
73
|
return runMediaVerb(subcommand, rest);
|
|
71
74
|
}
|
|
72
75
|
// publish: the OPERATION is the subcommand (no separate target positional).
|
|
@@ -217,8 +220,7 @@ async function runMediaVerbRequestJson(args) {
|
|
|
217
220
|
// Runner-managed fields are minted/derived by the CLI; reject them in the body so
|
|
218
221
|
// the agent cannot smuggle in ids, tokens, or billing dimensions.
|
|
219
222
|
for (const field of endpoint.fields) {
|
|
220
|
-
if (field.class === 'runner-managed' &&
|
|
221
|
-
Object.hasOwn(input, field.name)) {
|
|
223
|
+
if (field.class === 'runner-managed' && Object.hasOwn(input, field.name)) {
|
|
222
224
|
throw new Error(`media ${verb} ${endpointKey} input must not include runner-managed field "${field.name}"; the CLI mints or derives it.`);
|
|
223
225
|
}
|
|
224
226
|
}
|
|
@@ -244,8 +246,11 @@ function requireResolvedEndpoint(resolved, verb, endpointKey) {
|
|
|
244
246
|
// video-analysis verb (request-json surface). The agent authors an opaque Gemini
|
|
245
247
|
// request object (contents + generationConfig) in `--request <file>`; capability,
|
|
246
248
|
// operation, and modelKey come from the verb + positional, so the body posts
|
|
247
|
-
// EXACTLY the locked Web contract. There is no field classification
|
|
248
|
-
//
|
|
249
|
+
// EXACTLY the locked Web contract. There is no field classification; the payload
|
|
250
|
+
// is forwarded verbatim as the Gemini request. The optional `--video-seconds`
|
|
251
|
+
// flag is the one runner-supplied hint: when provided it is sent as
|
|
252
|
+
// `estimatedUsage.videoSeconds` so the Web boundary can route eligible short
|
|
253
|
+
// videos through its preflight/routing path (omit it to use the default route).
|
|
249
254
|
async function runVideoAnalysisVerb(args) {
|
|
250
255
|
const { modelKey, resolved, verb } = args;
|
|
251
256
|
const flags = parseFlags(args.args, new Set(['json']));
|
|
@@ -256,6 +261,7 @@ async function runVideoAnalysisVerb(args) {
|
|
|
256
261
|
'quote-confirmation-token',
|
|
257
262
|
'request',
|
|
258
263
|
'skill',
|
|
264
|
+
'video-seconds',
|
|
259
265
|
]);
|
|
260
266
|
for (const key of [...flags.values.keys(), ...flags.booleans]) {
|
|
261
267
|
if (!allowedKeys.has(key)) {
|
|
@@ -269,11 +275,26 @@ async function runVideoAnalysisVerb(args) {
|
|
|
269
275
|
throw new Error(`media ${verb} ${modelKey} --request must be a JSON object of Gemini request payload.`);
|
|
270
276
|
}
|
|
271
277
|
const payload = raw;
|
|
278
|
+
// Optional runner-supplied hint: the source video duration. When provided it is
|
|
279
|
+
// forwarded as estimatedUsage.videoSeconds so the Web boundary's video-analysis
|
|
280
|
+
// routing/preflight can consider eligible short videos; omitting it leaves the
|
|
281
|
+
// request on the default route. The CLI does not probe the media itself (no
|
|
282
|
+
// ffprobe in the open-source runner) — it only passes a value the caller knows.
|
|
283
|
+
const videoSecondsFlag = flags.values.get('video-seconds') ?? null;
|
|
284
|
+
let estimatedUsage;
|
|
285
|
+
if (videoSecondsFlag !== null) {
|
|
286
|
+
const parsed = Number(videoSecondsFlag);
|
|
287
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
288
|
+
throw new Error(`media ${verb} --video-seconds must be a positive number of seconds.`);
|
|
289
|
+
}
|
|
290
|
+
estimatedUsage = { videoSeconds: parsed };
|
|
291
|
+
}
|
|
272
292
|
const body = {
|
|
273
293
|
capability: 'video-analysis',
|
|
274
294
|
operation: 'analyze',
|
|
275
295
|
modelKey,
|
|
276
296
|
payload,
|
|
297
|
+
...(estimatedUsage ? { estimatedUsage } : {}),
|
|
277
298
|
operationId: flags.values.get('hosted-operation-id') ??
|
|
278
299
|
`postplus-cli:media:video-analysis:analyze:${randomUUID()}`,
|
|
279
300
|
quoteConfirmationToken: flags.values.get('quote-confirmation-token') ?? undefined,
|
|
@@ -289,14 +310,12 @@ async function runVideoAnalysisVerb(args) {
|
|
|
289
310
|
outputPath,
|
|
290
311
|
});
|
|
291
312
|
}
|
|
292
|
-
// `media-file upload`: the generic local-file -> hosted
|
|
293
|
-
//
|
|
294
|
-
//
|
|
295
|
-
//
|
|
296
|
-
//
|
|
297
|
-
//
|
|
298
|
-
// holds no provider keys — it asks the Web boundary for a signed upload target,
|
|
299
|
-
// PUTs the bytes, and returns the storageReference the Web boundary issued.
|
|
313
|
+
// `media-file upload`: the generic local-file -> hosted media verb. Released
|
|
314
|
+
// skills ship no scripts, so a skill that must place a local file behind hosted
|
|
315
|
+
// media first drives it through this verb. It is capability-generic: it knows no
|
|
316
|
+
// skill request payload. The runner asks the Web boundary for a signed upload
|
|
317
|
+
// target, PUTs bytes outside the JSON envelope, then asks the hosted provider
|
|
318
|
+
// upload operation for the reusable provider-facing result.
|
|
300
319
|
const MEDIA_FILE_MIME_BY_EXTENSION = {
|
|
301
320
|
'.gif': 'image/gif',
|
|
302
321
|
'.jpeg': 'image/jpeg',
|
|
@@ -347,6 +366,7 @@ async function runMediaFileUpload(args) {
|
|
|
347
366
|
}
|
|
348
367
|
const mimeType = flags.values.get('mime') ?? inferUploadMimeType(absolutePath);
|
|
349
368
|
const outputPath = flags.values.get('output') ?? null;
|
|
369
|
+
const hostedOperationId = flags.values.get('hosted-operation-id') ?? null;
|
|
350
370
|
const body = {
|
|
351
371
|
capability: 'media-file',
|
|
352
372
|
operation: 'create-upload-url',
|
|
@@ -355,7 +375,7 @@ async function runMediaFileUpload(args) {
|
|
|
355
375
|
name: path.basename(absolutePath),
|
|
356
376
|
sizeBytes: fileStat.size,
|
|
357
377
|
},
|
|
358
|
-
operationId:
|
|
378
|
+
operationId: hostedOperationId ??
|
|
359
379
|
`postplus-cli:media-file:create-upload-url:${randomUUID()}`,
|
|
360
380
|
quoteConfirmationToken: flags.values.get('quote-confirmation-token') ?? undefined,
|
|
361
381
|
};
|
|
@@ -368,8 +388,25 @@ async function runMediaFileUpload(args) {
|
|
|
368
388
|
});
|
|
369
389
|
const output = readHostedUploadOutput(payload);
|
|
370
390
|
const signedUpload = readSignedUpload(output);
|
|
391
|
+
const storageReference = readStorageReferenceValue(output);
|
|
371
392
|
await putHostedMediaBytes(signedUpload, absolutePath);
|
|
372
|
-
return
|
|
393
|
+
return await postHostedJson({
|
|
394
|
+
body: {
|
|
395
|
+
capability: 'media-file',
|
|
396
|
+
operation: 'upload',
|
|
397
|
+
file: {
|
|
398
|
+
mimeType,
|
|
399
|
+
name: path.basename(absolutePath),
|
|
400
|
+
storageReference,
|
|
401
|
+
},
|
|
402
|
+
operationId: hostedOperationId
|
|
403
|
+
? `${hostedOperationId}:upload`
|
|
404
|
+
: `postplus-cli:media-file:upload:${randomUUID()}`,
|
|
405
|
+
quoteConfirmationToken: flags.values.get('quote-confirmation-token') ?? undefined,
|
|
406
|
+
},
|
|
407
|
+
pathName: '/api/postplus-cli/hosted/capability',
|
|
408
|
+
skillName: flags.values.get('skill') ?? null,
|
|
409
|
+
});
|
|
373
410
|
},
|
|
374
411
|
errorInputLabel: inputFile,
|
|
375
412
|
json: flags.booleans.has('json'),
|
|
@@ -562,7 +599,7 @@ async function runResearchCollect(args) {
|
|
|
562
599
|
const outputPath = flags.values.get('output') ?? null;
|
|
563
600
|
return runHostedCommand({
|
|
564
601
|
request: () => postHostedJson({
|
|
565
|
-
body: { runHandle },
|
|
602
|
+
body: { runHandle, runHandleType: 'public-content-collection' },
|
|
566
603
|
pathName: '/api/postplus-cli/hosted/collection',
|
|
567
604
|
skillName: null,
|
|
568
605
|
}),
|
|
@@ -800,24 +837,16 @@ async function runHostedSchema(domain, args) {
|
|
|
800
837
|
return 0;
|
|
801
838
|
}
|
|
802
839
|
async function postHostedJson(input) {
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
840
|
+
const auth = await resolveFreshRemoteAuth();
|
|
841
|
+
const response = await sendAuthedCloudRequest({
|
|
842
|
+
auth,
|
|
806
843
|
body: input.body,
|
|
807
|
-
|
|
844
|
+
method: 'POST',
|
|
808
845
|
pathName: input.pathName,
|
|
846
|
+
retryOn401: () => resolveFreshRemoteAuth({ forceRefresh: true }),
|
|
809
847
|
skillName: input.skillName,
|
|
848
|
+
timeoutMs: 120000,
|
|
810
849
|
});
|
|
811
|
-
if (response.status === 401) {
|
|
812
|
-
auth = await resolveFreshRemoteAuth({ forceRefresh: true });
|
|
813
|
-
response = await postJson({
|
|
814
|
-
apiBaseUrl: auth.apiBaseUrl,
|
|
815
|
-
body: input.body,
|
|
816
|
-
cliSessionToken: auth.cliSessionToken,
|
|
817
|
-
pathName: input.pathName,
|
|
818
|
-
skillName: input.skillName,
|
|
819
|
-
});
|
|
820
|
-
}
|
|
821
850
|
const payload = await readJsonResponse(response);
|
|
822
851
|
if (!response.ok) {
|
|
823
852
|
const productError = readHostedProductError(payload);
|
|
@@ -876,22 +905,6 @@ async function writeQuoteConfirmationChallenge(error, input) {
|
|
|
876
905
|
});
|
|
877
906
|
return challengePath;
|
|
878
907
|
}
|
|
879
|
-
async function postJson(input) {
|
|
880
|
-
const headers = await buildPostPlusClientCompatibilityHeaders({
|
|
881
|
-
skillName: input.skillName,
|
|
882
|
-
});
|
|
883
|
-
return fetch(`${input.apiBaseUrl}${input.pathName}`, {
|
|
884
|
-
body: JSON.stringify(input.body),
|
|
885
|
-
headers: {
|
|
886
|
-
accept: 'application/json',
|
|
887
|
-
authorization: `Bearer ${input.cliSessionToken}`,
|
|
888
|
-
...headers,
|
|
889
|
-
'content-type': 'application/json',
|
|
890
|
-
},
|
|
891
|
-
method: 'POST',
|
|
892
|
-
signal: AbortSignal.timeout(120000),
|
|
893
|
-
});
|
|
894
|
-
}
|
|
895
908
|
async function readJsonResponse(response) {
|
|
896
909
|
const text = await response.text();
|
|
897
910
|
if (!text.trim()) {
|
|
@@ -1028,10 +1041,13 @@ function printMediaEndpointHelp(domain, verb, targetKey, resolved) {
|
|
|
1028
1041
|
|
|
1029
1042
|
Surface: request-json (opaque Gemini request payload)
|
|
1030
1043
|
Usage:
|
|
1031
|
-
postplus ${domain} ${verb} ${targetKey} --request <input.json> [--json] [--output <result.json>]
|
|
1044
|
+
postplus ${domain} ${verb} ${targetKey} --request <input.json> [--video-seconds <n>] [--json] [--output <result.json>]
|
|
1032
1045
|
|
|
1033
1046
|
--request <file> A JSON object authored verbatim as the Gemini request
|
|
1034
1047
|
(contents + optional generationConfig) under "payload".
|
|
1048
|
+
--video-seconds <n> Optional source video duration in seconds. Supplying it
|
|
1049
|
+
lets the hosted boundary route eligible short videos through
|
|
1050
|
+
its preflight path; omit it to use the default route.
|
|
1035
1051
|
Runner-managed (minted by the CLI; never in the body): operationId, quoteConfirmationToken
|
|
1036
1052
|
`);
|
|
1037
1053
|
return;
|
|
@@ -1116,7 +1132,9 @@ function printOpaqueTargetHelp(domain, verb, targetKey, resolved) {
|
|
|
1116
1132
|
: 'a provider-shaped JSON object of input';
|
|
1117
1133
|
// publish's operation is both the verb and the target, so the header/usage show
|
|
1118
1134
|
// it once; research shows `<verb> <target>`.
|
|
1119
|
-
const header = domain === 'publish'
|
|
1135
|
+
const header = domain === 'publish'
|
|
1136
|
+
? `publish ${targetKey}`
|
|
1137
|
+
: `research ${verb} ${targetKey}`;
|
|
1120
1138
|
const usage = domain === 'publish'
|
|
1121
1139
|
? ` postplus publish ${targetKey} --request <input.json> [--json] [--output <result.json>]`
|
|
1122
1140
|
: ` postplus research ${verb} ${targetKey} --request <input.json> [--skill <skill-id>] [--max-charge-usd <usd>] [--json] [--output <result.json>]`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@postplus/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.43",
|
|
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.",
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"build/auth-session.js",
|
|
13
13
|
"build/auth-validate.js",
|
|
14
14
|
"build/auth.js",
|
|
15
|
+
"build/authed-cloud-request.js",
|
|
15
16
|
"build/client-compatibility.js",
|
|
16
17
|
"build/command-runner.js",
|
|
17
18
|
"build/doctor.js",
|