@postplus/cli 0.1.27 → 0.1.29
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 +2 -1
- package/build/doctor.js +194 -21
- package/build/index.js +135 -12
- package/build/quote-confirmation.js +163 -0
- package/build/skill-catalog.js +49 -12
- package/build/skill-management.js +90 -27
- package/build/status.js +5 -4
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ PostPlus has three public surfaces that work together:
|
|
|
24
24
|
|
|
25
25
|
- `https://postplus.io/`: the hosted product surface for account access, subscription state, and cloud-backed capabilities.
|
|
26
26
|
- `https://github.com/PostPlusAI/postplus-skills`: the public skill repository that installs local marketing workflows into agent tools.
|
|
27
|
-
- `https://github.com/PostPlusAI/postplus-cli`: the local command-line tool that signs you in, checks local readiness, and connects released skills to PostPlus account state.
|
|
27
|
+
- `https://github.com/PostPlusAI/postplus-cli`: the local command-line tool that signs you in, checks local readiness, confirms high-credit hosted requests, and connects released skills to PostPlus account state.
|
|
28
28
|
|
|
29
29
|
## Install
|
|
30
30
|
|
|
@@ -34,6 +34,7 @@ Requires Node.js and npm.
|
|
|
34
34
|
npm install -g @postplus/cli@latest
|
|
35
35
|
postplus auth login
|
|
36
36
|
npx -y skills add PostPlusAI/postplus-skills --global --full-depth --skill '*' --agent claude-code codex cursor github-copilot windsurf trae trae-cn --yes
|
|
37
|
+
postplus skills verify
|
|
37
38
|
```
|
|
38
39
|
|
|
39
40
|
Useful checks:
|
package/build/doctor.js
CHANGED
|
@@ -2,6 +2,7 @@ import { resolveFreshRemoteAuth, } from './auth-session.js';
|
|
|
2
2
|
import { buildPostPlusClientCompatibilityHeaders, formatPostPlusCompatibilityError, } from './client-compatibility.js';
|
|
3
3
|
import { resolveHostedBaseUrl } from './hosted-release.js';
|
|
4
4
|
import { formatLocalDependencyReport, generateLocalDependencyReport, } from './local-dependencies.js';
|
|
5
|
+
import { loadPublicSkillCatalog, } from './skill-catalog.js';
|
|
5
6
|
import { readSubscriptionStatusField } from './subscription-status.js';
|
|
6
7
|
function createPass(id, label, detail, severity = 'required') {
|
|
7
8
|
return {
|
|
@@ -23,12 +24,16 @@ function createFail(id, label, detail, fix, input = {}) {
|
|
|
23
24
|
metadata: input.metadata,
|
|
24
25
|
};
|
|
25
26
|
}
|
|
26
|
-
export async function generateDoctorReport() {
|
|
27
|
+
export async function generateDoctorReport(options = {}) {
|
|
27
28
|
const hostedBaseUrl = await resolveHostedBaseUrl();
|
|
28
29
|
const checks = [
|
|
29
30
|
createPass('hosted_base_url', 'PostPlus Cloud', `Using ${hostedBaseUrl ?? 'https://postplus.io'}`),
|
|
30
31
|
];
|
|
31
|
-
|
|
32
|
+
const skillScope = await resolveSkillScope(options.skillId);
|
|
33
|
+
if (skillScope) {
|
|
34
|
+
checks.push(createPass('skill_catalog', 'Skill selection', `Using ${skillScope.skill.skillId} from catalog ${skillScope.catalog.releaseId}`));
|
|
35
|
+
}
|
|
36
|
+
checks.push(await checkLocalDependencies(skillScope));
|
|
32
37
|
if (!hostedBaseUrl) {
|
|
33
38
|
checks.push(createFail('remote_auth', 'Remote auth', 'PostPlus Cloud base URL could not be resolved.', 'Configure POSTPLUS_API_BASE_URL or run `postplus auth login`.'));
|
|
34
39
|
return buildDoctorReport(checks);
|
|
@@ -46,17 +51,40 @@ export async function generateDoctorReport() {
|
|
|
46
51
|
const authCheck = await checkRemoteAuth(auth);
|
|
47
52
|
checks.push(authCheck);
|
|
48
53
|
if (authCheck.status === 'pass') {
|
|
49
|
-
checks.push(await checkHostedCapabilities(auth));
|
|
54
|
+
checks.push(await checkHostedCapabilities(auth, skillScope));
|
|
55
|
+
}
|
|
56
|
+
return buildDoctorReport(checks, options.skillId);
|
|
57
|
+
}
|
|
58
|
+
async function resolveSkillScope(skillId) {
|
|
59
|
+
if (!skillId) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const catalog = await loadPublicSkillCatalog();
|
|
63
|
+
const skill = catalog.skills.find((entry) => entry.skillId === skillId);
|
|
64
|
+
if (!skill) {
|
|
65
|
+
throw new Error(`Unknown PostPlus skill: ${skillId}. Run \`postplus list\` to see released skill ids.`);
|
|
50
66
|
}
|
|
51
|
-
return
|
|
67
|
+
return { catalog, skill };
|
|
52
68
|
}
|
|
53
|
-
async function checkLocalDependencies() {
|
|
69
|
+
async function checkLocalDependencies(skillScope) {
|
|
54
70
|
try {
|
|
55
|
-
const report = await generateLocalDependencyReport(
|
|
71
|
+
const report = await generateLocalDependencyReport(skillScope
|
|
72
|
+
? {
|
|
73
|
+
loadCatalog: async () => ({
|
|
74
|
+
...skillScope.catalog,
|
|
75
|
+
skills: [skillScope.skill],
|
|
76
|
+
}),
|
|
77
|
+
}
|
|
78
|
+
: {});
|
|
56
79
|
const detail = formatLocalDependencyReport(report);
|
|
57
80
|
if (!report.ok) {
|
|
58
|
-
|
|
59
|
-
|
|
81
|
+
const skillId = skillScope?.skill.skillId;
|
|
82
|
+
return createFail('local_dependencies', skillId
|
|
83
|
+
? `Local dependencies for ${skillId}`
|
|
84
|
+
: 'Task-specific local media dependencies', detail, skillId
|
|
85
|
+
? 'Run the selected PostPlus skill in a local agent. The installed postplus-shared rules tell the agent how to bootstrap approved missing dependencies.'
|
|
86
|
+
: 'Run the affected PostPlus skill in a local agent. The installed postplus-shared rules tell the agent how to bootstrap approved missing media dependencies.', {
|
|
87
|
+
severity: skillId ? 'required' : 'task_specific',
|
|
60
88
|
metadata: {
|
|
61
89
|
bootstrapRule: 'postplus-shared',
|
|
62
90
|
missingDependencies: report.checks
|
|
@@ -69,7 +97,9 @@ async function checkLocalDependencies() {
|
|
|
69
97
|
},
|
|
70
98
|
});
|
|
71
99
|
}
|
|
72
|
-
return createPass('local_dependencies',
|
|
100
|
+
return createPass('local_dependencies', skillScope
|
|
101
|
+
? `Local dependencies for ${skillScope.skill.skillId}`
|
|
102
|
+
: 'Local dependencies', detail);
|
|
73
103
|
}
|
|
74
104
|
catch (error) {
|
|
75
105
|
return createFail('local_dependencies', 'Local dependencies', error instanceof Error
|
|
@@ -77,13 +107,14 @@ async function checkLocalDependencies() {
|
|
|
77
107
|
: 'Failed to check local dependencies.');
|
|
78
108
|
}
|
|
79
109
|
}
|
|
80
|
-
function buildDoctorReport(checks) {
|
|
110
|
+
function buildDoctorReport(checks, skillId) {
|
|
81
111
|
const requiredOk = checks.every((check) => check.severity !== 'required' || check.status === 'pass');
|
|
82
112
|
return {
|
|
83
113
|
schemaVersion: 1,
|
|
84
114
|
ok: checks.every((check) => check.status === 'pass'),
|
|
85
115
|
requiredOk,
|
|
86
116
|
checks,
|
|
117
|
+
...(skillId ? { skillId } : {}),
|
|
87
118
|
};
|
|
88
119
|
}
|
|
89
120
|
async function checkRemoteAuth(input) {
|
|
@@ -114,7 +145,7 @@ async function checkRemoteAuth(input) {
|
|
|
114
145
|
: 'Failed to validate PostPlus Cloud auth.', 'Run `postplus auth validate` after confirming network access.');
|
|
115
146
|
}
|
|
116
147
|
}
|
|
117
|
-
async function checkHostedCapabilities(input) {
|
|
148
|
+
async function checkHostedCapabilities(input, skillScope) {
|
|
118
149
|
try {
|
|
119
150
|
let response = await requestWithAuth(input, '/api/postplus-cli/hosted/readiness');
|
|
120
151
|
if (response.status === 401) {
|
|
@@ -127,17 +158,28 @@ async function checkHostedCapabilities(input) {
|
|
|
127
158
|
if (!response.ok) {
|
|
128
159
|
return createFail('hosted_capabilities', 'Hosted capabilities', readErrorMessage(payload, 'PostPlus Cloud hosted readiness check failed.'));
|
|
129
160
|
}
|
|
130
|
-
const capabilities =
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
161
|
+
const capabilities = readHostedCapabilityEntries(payload.capabilities);
|
|
162
|
+
const relevantCapabilities = skillScope
|
|
163
|
+
? filterCapabilitiesForSkill(capabilities, skillScope.skill.requirements)
|
|
164
|
+
: capabilities;
|
|
165
|
+
const failedLabels = relevantCapabilities
|
|
166
|
+
.map((value) => readCapabilityFailureLabel(value, skillScope))
|
|
135
167
|
.filter((value) => value !== null);
|
|
136
|
-
if (
|
|
137
|
-
|
|
168
|
+
if (skillScope && hasHostedRequirements(skillScope.skill.requirements)) {
|
|
169
|
+
const missingRequirements = collectMissingHostedRequirementLabels(relevantCapabilities, skillScope.skill.requirements);
|
|
170
|
+
failedLabels.push(...missingRequirements);
|
|
171
|
+
}
|
|
172
|
+
if (failedLabels.length > 0 ||
|
|
173
|
+
(!skillScope && payload.ok !== true && capabilities.length === 0)) {
|
|
174
|
+
const skillId = skillScope?.skill.skillId;
|
|
175
|
+
return createFail('hosted_capabilities', skillId ? `Hosted capabilities for ${skillId}` : 'Hosted capabilities', `Not ready: ${failedLabels.join(', ') || 'unknown capability failure'}`, 'Check PostPlus Cloud provider configuration and subscription state.', {
|
|
176
|
+
severity: skillId ? 'required' : 'task_specific',
|
|
177
|
+
});
|
|
138
178
|
}
|
|
139
179
|
const subscription = readSubscriptionStatusField(payload).label;
|
|
140
|
-
return createPass('hosted_capabilities',
|
|
180
|
+
return createPass('hosted_capabilities', skillScope
|
|
181
|
+
? `Hosted capabilities for ${skillScope.skill.skillId}`
|
|
182
|
+
: 'Hosted capabilities', `Ready (${relevantCapabilities.length} capability checks passed; subscription ${subscription})`);
|
|
141
183
|
}
|
|
142
184
|
catch (error) {
|
|
143
185
|
return createFail('hosted_capabilities', 'Hosted capabilities', error instanceof Error
|
|
@@ -145,7 +187,13 @@ async function checkHostedCapabilities(input) {
|
|
|
145
187
|
: 'Failed to check hosted capability readiness.');
|
|
146
188
|
}
|
|
147
189
|
}
|
|
148
|
-
function
|
|
190
|
+
function readHostedCapabilityEntries(value) {
|
|
191
|
+
if (!Array.isArray(value)) {
|
|
192
|
+
return [];
|
|
193
|
+
}
|
|
194
|
+
return value.filter((entry) => !!entry && typeof entry === 'object' && !Array.isArray(entry));
|
|
195
|
+
}
|
|
196
|
+
function readCapabilityFailureLabel(value, skillScope) {
|
|
149
197
|
if (!value || typeof value !== 'object') {
|
|
150
198
|
return 'invalid capability response';
|
|
151
199
|
}
|
|
@@ -163,9 +211,12 @@ function readCapabilityFailureLabel(value) {
|
|
|
163
211
|
.map(readReadinessCheckFailureLabel)
|
|
164
212
|
.filter((check) => check !== null)
|
|
165
213
|
: [];
|
|
166
|
-
|
|
214
|
+
const labelWithFailures = failedChecks.length > 0
|
|
167
215
|
? `${label} (${failedChecks.join(', ')})`
|
|
168
216
|
: label;
|
|
217
|
+
return skillScope
|
|
218
|
+
? `${labelWithFailures} for ${skillScope.skill.skillId}`
|
|
219
|
+
: labelWithFailures;
|
|
169
220
|
}
|
|
170
221
|
function readReadinessCheckFailureLabel(value) {
|
|
171
222
|
if (!value || typeof value !== 'object') {
|
|
@@ -181,6 +232,128 @@ function readReadinessCheckFailureLabel(value) {
|
|
|
181
232
|
? record.id
|
|
182
233
|
: 'unknown check';
|
|
183
234
|
}
|
|
235
|
+
function filterCapabilitiesForSkill(capabilities, requirements) {
|
|
236
|
+
if (!hasHostedRequirements(requirements)) {
|
|
237
|
+
return [];
|
|
238
|
+
}
|
|
239
|
+
return capabilities.filter((capability) => capabilityMatchesRequirements(capability, requirements));
|
|
240
|
+
}
|
|
241
|
+
function capabilityMatchesRequirements(capability, requirements) {
|
|
242
|
+
const identifiers = collectCapabilityIdentifiers(capability);
|
|
243
|
+
const hostedCapabilities = new Set(requirements.hostedCapabilities);
|
|
244
|
+
const requirementKeys = collectHostedRequirementKeys(requirements);
|
|
245
|
+
return identifiers.some((identifier) => {
|
|
246
|
+
if (identifier === 'media-file:upload' &&
|
|
247
|
+
hostedCapabilities.has('media-file') &&
|
|
248
|
+
!requiresHostedMediaFileUpload(requirements)) {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
const [prefix, suffix] = splitCapabilityIdentifier(identifier);
|
|
252
|
+
if (prefix === 'media-file' &&
|
|
253
|
+
suffix &&
|
|
254
|
+
suffix !== 'upload' &&
|
|
255
|
+
hostedCapabilities.has('media-file')) {
|
|
256
|
+
return true;
|
|
257
|
+
}
|
|
258
|
+
if (hostedCapabilities.has(identifier)) {
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
if (prefix &&
|
|
262
|
+
suffix &&
|
|
263
|
+
hostedCapabilities.has(prefix) &&
|
|
264
|
+
requirementKeys.has(suffix)) {
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
return requirementKeys.has(identifier) || requirementKeys.has(suffix);
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
function requiresHostedMediaFileUpload(requirements) {
|
|
271
|
+
return (requirements.hostedCapabilities.includes('media-generation') ||
|
|
272
|
+
requirements.endpointKeys.length > 0);
|
|
273
|
+
}
|
|
274
|
+
function collectCapabilityIdentifiers(capability) {
|
|
275
|
+
const identifiers = new Set();
|
|
276
|
+
for (const key of [
|
|
277
|
+
'id',
|
|
278
|
+
'key',
|
|
279
|
+
'capability',
|
|
280
|
+
'capabilityKey',
|
|
281
|
+
'collectionKey',
|
|
282
|
+
'endpointKey',
|
|
283
|
+
'modelKey',
|
|
284
|
+
'sourceKey',
|
|
285
|
+
'accountConnection',
|
|
286
|
+
]) {
|
|
287
|
+
const value = capability[key];
|
|
288
|
+
if (typeof value === 'string' && value.trim()) {
|
|
289
|
+
identifiers.add(value.trim());
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
if (Array.isArray(capability.checks)) {
|
|
293
|
+
for (const check of capability.checks) {
|
|
294
|
+
if (!check || typeof check !== 'object' || Array.isArray(check)) {
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
for (const identifier of collectCapabilityIdentifiers(check)) {
|
|
298
|
+
identifiers.add(identifier);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return [...identifiers];
|
|
303
|
+
}
|
|
304
|
+
function collectHostedRequirementKeys(requirements) {
|
|
305
|
+
return new Set([
|
|
306
|
+
...requirements.accountConnections,
|
|
307
|
+
...requirements.collectionKeys,
|
|
308
|
+
...requirements.endpointKeys,
|
|
309
|
+
...requirements.modelKeys,
|
|
310
|
+
...requirements.sourceKeys,
|
|
311
|
+
]);
|
|
312
|
+
}
|
|
313
|
+
function hasHostedRequirements(requirements) {
|
|
314
|
+
return (requirements.accountConnections.length > 0 ||
|
|
315
|
+
requirements.collectionKeys.length > 0 ||
|
|
316
|
+
requirements.endpointKeys.length > 0 ||
|
|
317
|
+
requirements.hostedCapabilities.length > 0 ||
|
|
318
|
+
requirements.modelKeys.length > 0 ||
|
|
319
|
+
requirements.sourceKeys.length > 0);
|
|
320
|
+
}
|
|
321
|
+
function collectMissingHostedRequirementLabels(capabilities, requirements) {
|
|
322
|
+
const availableIdentifiers = new Set(capabilities.flatMap(collectCapabilityIdentifiers));
|
|
323
|
+
const missing = [];
|
|
324
|
+
for (const capability of requirements.hostedCapabilities) {
|
|
325
|
+
if (![...availableIdentifiers].some((identifier) => identifierMatchesCapability(identifier, capability))) {
|
|
326
|
+
missing.push(capability);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
for (const key of collectHostedRequirementKeys(requirements)) {
|
|
330
|
+
if (![...availableIdentifiers].some((identifier) => identifierMatchesKey(identifier, key))) {
|
|
331
|
+
missing.push(key);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return missing.map((value) => `${value} readiness check missing`);
|
|
335
|
+
}
|
|
336
|
+
function identifierMatchesKey(identifier, key) {
|
|
337
|
+
if (identifier === key) {
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
const [, suffix] = splitCapabilityIdentifier(identifier);
|
|
341
|
+
return suffix === key;
|
|
342
|
+
}
|
|
343
|
+
function identifierMatchesCapability(identifier, capability) {
|
|
344
|
+
if (identifier === capability) {
|
|
345
|
+
return true;
|
|
346
|
+
}
|
|
347
|
+
const [prefix] = splitCapabilityIdentifier(identifier);
|
|
348
|
+
return prefix === capability;
|
|
349
|
+
}
|
|
350
|
+
function splitCapabilityIdentifier(identifier) {
|
|
351
|
+
const index = identifier.indexOf(':');
|
|
352
|
+
if (index === -1) {
|
|
353
|
+
return [null, identifier];
|
|
354
|
+
}
|
|
355
|
+
return [identifier.slice(0, index), identifier.slice(index + 1)];
|
|
356
|
+
}
|
|
184
357
|
function readErrorMessage(payload, fallback) {
|
|
185
358
|
const compatibilityError = formatPostPlusCompatibilityError(payload);
|
|
186
359
|
if (compatibilityError) {
|
package/build/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
2
3
|
import { formatAuthRefreshReport, refreshRemoteAuth, revokeRemoteAuthAndReport, } from './auth-lifecycle.js';
|
|
3
4
|
import { loginWithCloudHandoff } from './auth-login.js';
|
|
4
5
|
import { formatAuthValidateReport, validateRemoteAuth, } from './auth-validate.js';
|
|
@@ -6,8 +7,9 @@ import { clearAuthState, formatAuthStatusReport, generateAuthStatusReport, } fro
|
|
|
6
7
|
import { readCurrentCliVersion } from './client-compatibility.js';
|
|
7
8
|
import { formatDoctorReport, generateDoctorReport } from './doctor.js';
|
|
8
9
|
import { assertConfigFilePermissions } from './local-state.js';
|
|
10
|
+
import { readLargeCreditQuoteConfirmationChallenge, resolveLargeCreditQuoteConfirmation, } from './quote-confirmation.js';
|
|
9
11
|
import { POSTPLUS_SKILLS_INSTALL_COMMAND, loadPublicSkillCatalog, } from './skill-catalog.js';
|
|
10
|
-
import { runPostPlusSkillUninstall, runPostPlusSkillUpdate, } from './skill-management.js';
|
|
12
|
+
import { formatSkillBaselineVerifyReport, runPostPlusSkillUninstall, runPostPlusSkillUpdate, runPostPlusSkillVerify, } from './skill-management.js';
|
|
11
13
|
import { formatStatusReport, generateStatusReport } from './status.js';
|
|
12
14
|
import { refreshUpdateCheckCache, runCliSelfUpdateIfOutdated, } from './update-check.js';
|
|
13
15
|
function printAuthHelp() {
|
|
@@ -37,27 +39,32 @@ Usage:
|
|
|
37
39
|
postplus auth status [--json]
|
|
38
40
|
postplus auth validate [--json]
|
|
39
41
|
postplus auth logout [--json]
|
|
40
|
-
postplus doctor [--json]
|
|
42
|
+
postplus doctor [--skill <skill-id>] [--json]
|
|
43
|
+
postplus quote confirm --json --challenge-file <path>
|
|
44
|
+
postplus skills verify [--json]
|
|
41
45
|
postplus update
|
|
42
46
|
postplus uninstall
|
|
43
47
|
postplus list [--json]
|
|
44
|
-
postplus status [--json]
|
|
48
|
+
postplus status [--skill <skill-id>] [--json]
|
|
45
49
|
postplus version
|
|
46
50
|
postplus help
|
|
47
51
|
|
|
48
52
|
Skills:
|
|
49
53
|
${POSTPLUS_SKILLS_INSTALL_COMMAND}
|
|
54
|
+
|
|
55
|
+
After first install, run:
|
|
56
|
+
postplus skills verify
|
|
50
57
|
`);
|
|
51
58
|
}
|
|
52
|
-
async function runDoctor(
|
|
53
|
-
const report = await generateDoctorReport();
|
|
54
|
-
if (json) {
|
|
59
|
+
async function runDoctor(options) {
|
|
60
|
+
const report = await generateDoctorReport({ skillId: options.skillId });
|
|
61
|
+
if (options.json) {
|
|
55
62
|
writeJson(report);
|
|
56
63
|
}
|
|
57
64
|
else {
|
|
58
65
|
process.stdout.write(`${formatDoctorReport(report)}\n`);
|
|
59
66
|
}
|
|
60
|
-
return report.
|
|
67
|
+
return report.requiredOk ? 0 : 1;
|
|
61
68
|
}
|
|
62
69
|
async function runAuthStatus(json) {
|
|
63
70
|
const report = await generateAuthStatusReport();
|
|
@@ -69,9 +76,9 @@ async function runAuthStatus(json) {
|
|
|
69
76
|
}
|
|
70
77
|
return report.ok ? 0 : 1;
|
|
71
78
|
}
|
|
72
|
-
async function runStatus(
|
|
73
|
-
const report = await generateStatusReport();
|
|
74
|
-
if (json) {
|
|
79
|
+
async function runStatus(options) {
|
|
80
|
+
const report = await generateStatusReport({ skillId: options.skillId });
|
|
81
|
+
if (options.json) {
|
|
75
82
|
writeJson(report);
|
|
76
83
|
}
|
|
77
84
|
else {
|
|
@@ -116,9 +123,116 @@ async function runSkillUpdateCommand() {
|
|
|
116
123
|
async function runSkillUninstallCommand() {
|
|
117
124
|
return runPostPlusSkillUninstall();
|
|
118
125
|
}
|
|
126
|
+
async function runSkillsCommand(rest) {
|
|
127
|
+
const [subcommand] = rest;
|
|
128
|
+
switch (subcommand) {
|
|
129
|
+
case 'verify': {
|
|
130
|
+
const options = rest.slice(1);
|
|
131
|
+
const unknownOption = options.find((option) => option !== '--json');
|
|
132
|
+
if (unknownOption) {
|
|
133
|
+
process.stderr.write(`Unknown option for skills verify: ${unknownOption}\n`);
|
|
134
|
+
return 1;
|
|
135
|
+
}
|
|
136
|
+
const report = await runPostPlusSkillVerify();
|
|
137
|
+
if (options.includes('--json')) {
|
|
138
|
+
writeJson(report);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
process.stdout.write(`${formatSkillBaselineVerifyReport(report)}\n`);
|
|
142
|
+
}
|
|
143
|
+
return report.ok ? 0 : 1;
|
|
144
|
+
}
|
|
145
|
+
case 'help':
|
|
146
|
+
case '--help':
|
|
147
|
+
case '-h':
|
|
148
|
+
case undefined:
|
|
149
|
+
process.stdout.write(`PostPlus CLI — skills commands
|
|
150
|
+
|
|
151
|
+
Usage:
|
|
152
|
+
postplus skills verify [--json] Verify installed public skills and record the managed baseline
|
|
153
|
+
|
|
154
|
+
Options:
|
|
155
|
+
--json Output results as JSON
|
|
156
|
+
`);
|
|
157
|
+
return 0;
|
|
158
|
+
default:
|
|
159
|
+
process.stderr.write(`Unknown skills command: ${subcommand}\n`);
|
|
160
|
+
return 1;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
async function runQuoteCommand(rest) {
|
|
164
|
+
const [subcommand, ...options] = rest;
|
|
165
|
+
if (subcommand !== 'confirm') {
|
|
166
|
+
process.stderr.write(`Unknown quote command: ${subcommand ?? ''}\n`);
|
|
167
|
+
return 1;
|
|
168
|
+
}
|
|
169
|
+
const parsed = parseQuoteConfirmOptions(options);
|
|
170
|
+
if (!parsed.json) {
|
|
171
|
+
process.stderr.write('quote confirm requires --json.\n');
|
|
172
|
+
return 1;
|
|
173
|
+
}
|
|
174
|
+
if (!parsed.challengeFile) {
|
|
175
|
+
process.stderr.write('quote confirm requires --challenge-file.\n');
|
|
176
|
+
return 1;
|
|
177
|
+
}
|
|
178
|
+
const challenge = readLargeCreditQuoteConfirmationChallenge(JSON.parse(await readFile(parsed.challengeFile, 'utf8')));
|
|
179
|
+
if (!challenge) {
|
|
180
|
+
process.stderr.write('Invalid large credit quote confirmation challenge.\n');
|
|
181
|
+
return 1;
|
|
182
|
+
}
|
|
183
|
+
writeJson(await resolveLargeCreditQuoteConfirmation(challenge));
|
|
184
|
+
return 0;
|
|
185
|
+
}
|
|
186
|
+
function parseQuoteConfirmOptions(args) {
|
|
187
|
+
const options = {
|
|
188
|
+
challengeFile: null,
|
|
189
|
+
json: false,
|
|
190
|
+
};
|
|
191
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
192
|
+
const arg = args[index];
|
|
193
|
+
if (arg === '--json') {
|
|
194
|
+
options.json = true;
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (arg === '--challenge-file') {
|
|
198
|
+
const challengeFile = args[index + 1];
|
|
199
|
+
if (!challengeFile || challengeFile.startsWith('--')) {
|
|
200
|
+
throw new Error('Missing value for --challenge-file.');
|
|
201
|
+
}
|
|
202
|
+
options.challengeFile = challengeFile;
|
|
203
|
+
index += 1;
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
throw new Error(`Unknown option for quote confirm: ${arg}`);
|
|
207
|
+
}
|
|
208
|
+
return options;
|
|
209
|
+
}
|
|
119
210
|
function writeJson(value) {
|
|
120
211
|
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
121
212
|
}
|
|
213
|
+
function parseDiagnosticOptions(args) {
|
|
214
|
+
const options = {
|
|
215
|
+
json: false,
|
|
216
|
+
};
|
|
217
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
218
|
+
const arg = args[index];
|
|
219
|
+
if (arg === '--json') {
|
|
220
|
+
options.json = true;
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
if (arg === '--skill') {
|
|
224
|
+
const skillId = args[index + 1];
|
|
225
|
+
if (!skillId || skillId.startsWith('--')) {
|
|
226
|
+
throw new Error('Missing value for --skill.');
|
|
227
|
+
}
|
|
228
|
+
options.skillId = skillId;
|
|
229
|
+
index += 1;
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
throw new Error(`Unknown option for diagnostics command: ${arg}`);
|
|
233
|
+
}
|
|
234
|
+
return options;
|
|
235
|
+
}
|
|
122
236
|
async function runAuthLogout(json) {
|
|
123
237
|
const report = await clearAuthState();
|
|
124
238
|
if (json) {
|
|
@@ -192,6 +306,9 @@ async function main() {
|
|
|
192
306
|
if (helpTopic === 'auth') {
|
|
193
307
|
printAuthHelp();
|
|
194
308
|
}
|
|
309
|
+
else if (helpTopic === 'skills') {
|
|
310
|
+
await runSkillsCommand(['help']);
|
|
311
|
+
}
|
|
195
312
|
else {
|
|
196
313
|
printHelp();
|
|
197
314
|
}
|
|
@@ -199,7 +316,13 @@ async function main() {
|
|
|
199
316
|
return;
|
|
200
317
|
}
|
|
201
318
|
case 'doctor':
|
|
202
|
-
process.exitCode = await runDoctor(
|
|
319
|
+
process.exitCode = await runDoctor(parseDiagnosticOptions(rest));
|
|
320
|
+
return;
|
|
321
|
+
case 'quote':
|
|
322
|
+
process.exitCode = await runQuoteCommand(rest);
|
|
323
|
+
return;
|
|
324
|
+
case 'skills':
|
|
325
|
+
process.exitCode = await runSkillsCommand(rest);
|
|
203
326
|
return;
|
|
204
327
|
case 'install':
|
|
205
328
|
process.stderr.write(`PostPlus CLI does not install skills directly. Run \`${POSTPLUS_SKILLS_INSTALL_COMMAND}\`.\n`);
|
|
@@ -215,7 +338,7 @@ async function main() {
|
|
|
215
338
|
process.exitCode = await runList(json);
|
|
216
339
|
return;
|
|
217
340
|
case 'status':
|
|
218
|
-
process.exitCode = await runStatus(
|
|
341
|
+
process.exitCode = await runStatus(parseDiagnosticOptions(rest));
|
|
219
342
|
return;
|
|
220
343
|
case 'auth': {
|
|
221
344
|
const [subcommand, ...authRest] = rest;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import readline from 'node:readline/promises';
|
|
2
|
+
import { readLocalConfig, updateLocalConfig } from './local-state.js';
|
|
3
|
+
const PRODUCT_ERROR_CODE = 'postplus_cli_quote_confirmation_required';
|
|
4
|
+
export function readLargeCreditQuoteConfirmationChallenge(value) {
|
|
5
|
+
if (!value || typeof value !== 'object') {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
const record = value;
|
|
9
|
+
const challenge = record.productErrorCode === PRODUCT_ERROR_CODE
|
|
10
|
+
? record.quoteConfirmation
|
|
11
|
+
: record.quoteConfirmation && typeof record.quoteConfirmation === 'object'
|
|
12
|
+
? record.quoteConfirmation
|
|
13
|
+
: value;
|
|
14
|
+
if (!challenge || typeof challenge !== 'object') {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
const parsed = challenge;
|
|
18
|
+
if (typeof parsed.accountId !== 'string' ||
|
|
19
|
+
typeof parsed.action !== 'string' ||
|
|
20
|
+
typeof parsed.estimatedMillicredits !== 'number' ||
|
|
21
|
+
typeof parsed.featureLabel !== 'string' ||
|
|
22
|
+
typeof parsed.operationId !== 'string' ||
|
|
23
|
+
typeof parsed.requiredTierMillicredits !== 'number' ||
|
|
24
|
+
typeof parsed.reservedMillicredits !== 'number' ||
|
|
25
|
+
typeof parsed.serviceLabel !== 'string' ||
|
|
26
|
+
typeof parsed.token !== 'string') {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
accountId: parsed.accountId,
|
|
31
|
+
action: parsed.action,
|
|
32
|
+
billingUnit: typeof parsed.billingUnit === 'string' ? parsed.billingUnit : undefined,
|
|
33
|
+
drivers: parseDrivers(parsed.drivers),
|
|
34
|
+
estimatedCredits: typeof parsed.estimatedCredits === 'number'
|
|
35
|
+
? parsed.estimatedCredits
|
|
36
|
+
: undefined,
|
|
37
|
+
estimatedMillicredits: parsed.estimatedMillicredits,
|
|
38
|
+
estimatedOnly: parsed.estimatedOnly === true,
|
|
39
|
+
featureLabel: parsed.featureLabel,
|
|
40
|
+
operationId: parsed.operationId,
|
|
41
|
+
requiredTierCredits: typeof parsed.requiredTierCredits === 'number'
|
|
42
|
+
? parsed.requiredTierCredits
|
|
43
|
+
: undefined,
|
|
44
|
+
requiredTierMillicredits: parsed.requiredTierMillicredits,
|
|
45
|
+
reservedCredits: typeof parsed.reservedCredits === 'number'
|
|
46
|
+
? parsed.reservedCredits
|
|
47
|
+
: undefined,
|
|
48
|
+
reservedMillicredits: parsed.reservedMillicredits,
|
|
49
|
+
serviceLabel: parsed.serviceLabel,
|
|
50
|
+
token: parsed.token,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export async function resolveLargeCreditQuoteConfirmation(challenge, dependencies = {
|
|
54
|
+
confirm: confirmLargeCreditQuote,
|
|
55
|
+
}) {
|
|
56
|
+
const acknowledgedTierMillicredits = await readAcknowledgedTierMillicredits(challenge);
|
|
57
|
+
if (acknowledgedTierMillicredits < challenge.requiredTierMillicredits) {
|
|
58
|
+
await dependencies.confirm(challenge);
|
|
59
|
+
await writeAcknowledgedTierMillicredits(challenge);
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
schemaVersion: 1,
|
|
63
|
+
token: challenge.token,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
export async function confirmLargeCreditQuote(challenge) {
|
|
67
|
+
const terminal = readline.createInterface({
|
|
68
|
+
input: process.stdin,
|
|
69
|
+
output: process.stderr,
|
|
70
|
+
});
|
|
71
|
+
try {
|
|
72
|
+
const answer = await terminal.question(buildLargeCreditConfirmationPrompt(challenge));
|
|
73
|
+
if (answer.trim() !== 'CONFIRM') {
|
|
74
|
+
throw new Error('Large credit charge was not confirmed.');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
terminal.close();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
export function buildLargeCreditConfirmationPrompt(challenge) {
|
|
82
|
+
const lines = [
|
|
83
|
+
'',
|
|
84
|
+
'PostPlus large credit warning',
|
|
85
|
+
`This request crosses the ${formatCredits(challenge.requiredTierMillicredits)}-credit warning tier.`,
|
|
86
|
+
`Estimated charge: ${formatCredits(challenge.estimatedMillicredits)} credits${challenge.estimatedOnly ? ' (estimate)' : ''}.`,
|
|
87
|
+
`Reserved before execution: ${formatCredits(challenge.reservedMillicredits)} credits.`,
|
|
88
|
+
`Capability: ${formatText(challenge.featureLabel)} / ${formatText(challenge.action)}.`,
|
|
89
|
+
`Service: ${formatText(challenge.serviceLabel)}.`,
|
|
90
|
+
];
|
|
91
|
+
const drivers = Array.isArray(challenge.drivers)
|
|
92
|
+
? challenge.drivers.filter((driver) => {
|
|
93
|
+
return (driver &&
|
|
94
|
+
typeof driver === 'object' &&
|
|
95
|
+
typeof driver.label === 'string' &&
|
|
96
|
+
driver.value !== undefined &&
|
|
97
|
+
driver.value !== null);
|
|
98
|
+
})
|
|
99
|
+
: [];
|
|
100
|
+
if (drivers.length > 0) {
|
|
101
|
+
lines.push('High-credit drivers:');
|
|
102
|
+
for (const driver of drivers.slice(0, 8)) {
|
|
103
|
+
lines.push(`- ${driver.label}: ${String(driver.value)}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
lines.push('PostPlus will warn again only when a future request crosses a higher tier.', 'Type CONFIRM to continue: ');
|
|
107
|
+
return lines.join('\n');
|
|
108
|
+
}
|
|
109
|
+
async function readAcknowledgedTierMillicredits(challenge) {
|
|
110
|
+
const config = await readLocalConfig();
|
|
111
|
+
const tier = config?.largeCreditConfirmation?.acknowledgedTierMillicreditsByAccountId?.[challenge.accountId];
|
|
112
|
+
return typeof tier === 'number' && Number.isSafeInteger(tier) && tier > 0
|
|
113
|
+
? tier
|
|
114
|
+
: 0;
|
|
115
|
+
}
|
|
116
|
+
async function writeAcknowledgedTierMillicredits(challenge) {
|
|
117
|
+
await updateLocalConfig((current) => {
|
|
118
|
+
const config = current ?? {};
|
|
119
|
+
const largeCreditConfirmation = config.largeCreditConfirmation ?? {};
|
|
120
|
+
const currentTiers = largeCreditConfirmation.acknowledgedTierMillicreditsByAccountId ?? {};
|
|
121
|
+
const previousTier = currentTiers[challenge.accountId];
|
|
122
|
+
return {
|
|
123
|
+
...config,
|
|
124
|
+
largeCreditConfirmation: {
|
|
125
|
+
...largeCreditConfirmation,
|
|
126
|
+
acknowledgedTierMillicreditsByAccountId: {
|
|
127
|
+
...currentTiers,
|
|
128
|
+
[challenge.accountId]: Math.max(typeof previousTier === 'number' &&
|
|
129
|
+
Number.isSafeInteger(previousTier)
|
|
130
|
+
? previousTier
|
|
131
|
+
: 0, challenge.requiredTierMillicredits),
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
function parseDrivers(value) {
|
|
138
|
+
if (!Array.isArray(value)) {
|
|
139
|
+
return [];
|
|
140
|
+
}
|
|
141
|
+
return value
|
|
142
|
+
.filter((driver) => {
|
|
143
|
+
return Boolean(driver) && typeof driver === 'object';
|
|
144
|
+
})
|
|
145
|
+
.filter((driver) => typeof driver.label === 'string')
|
|
146
|
+
.map((driver) => ({
|
|
147
|
+
key: typeof driver.key === 'string' ? driver.key : undefined,
|
|
148
|
+
label: driver.label,
|
|
149
|
+
value: driver.value,
|
|
150
|
+
}));
|
|
151
|
+
}
|
|
152
|
+
function formatText(value) {
|
|
153
|
+
return value.trim() ? value : 'unknown';
|
|
154
|
+
}
|
|
155
|
+
function formatCredits(millicredits) {
|
|
156
|
+
const credits = millicredits / 1_000;
|
|
157
|
+
if (!Number.isFinite(credits)) {
|
|
158
|
+
return 'unknown';
|
|
159
|
+
}
|
|
160
|
+
return Number.isInteger(credits)
|
|
161
|
+
? String(credits)
|
|
162
|
+
: credits.toFixed(3).replace(/0+$/, '').replace(/\.$/, '');
|
|
163
|
+
}
|
package/build/skill-catalog.js
CHANGED
|
@@ -15,6 +15,15 @@ export const POSTPLUS_SKILLS_INSTALL_COMMAND = formatPostPlusSkillsInstallComman
|
|
|
15
15
|
export const POSTPLUS_SKILLS_LIST_COMMAND = formatPostPlusSkillsListCommand();
|
|
16
16
|
const POSTPLUS_SKILLS_INDEX_URL = 'https://raw.githubusercontent.com/PostPlusAI/postplus-skills/main/skills/INDEX.md';
|
|
17
17
|
const POSTPLUS_SKILLS_CATALOG_URL = 'https://raw.githubusercontent.com/PostPlusAI/postplus-skills/main/skills/catalog.json';
|
|
18
|
+
export const PUBLIC_SKILL_REQUIREMENT_KEYS = [
|
|
19
|
+
'accountConnections',
|
|
20
|
+
'collectionKeys',
|
|
21
|
+
'endpointKeys',
|
|
22
|
+
'hostedCapabilities',
|
|
23
|
+
'localDependencies',
|
|
24
|
+
'modelKeys',
|
|
25
|
+
'sourceKeys',
|
|
26
|
+
];
|
|
18
27
|
export async function loadPublicSkillCatalog(fetchFn = fetch, env = process.env) {
|
|
19
28
|
const catalogUrl = resolvePostPlusSkillsCatalogUrl(env);
|
|
20
29
|
const skillsSource = resolvePostPlusSkillsSource(env);
|
|
@@ -95,17 +104,7 @@ function parsePublicSkillCatalog(payload) {
|
|
|
95
104
|
const path = typeof skill.path === 'string' && skill.path.trim()
|
|
96
105
|
? skill.path.trim()
|
|
97
106
|
: null;
|
|
98
|
-
const requirements = skill.requirements
|
|
99
|
-
typeof skill.requirements === 'object' &&
|
|
100
|
-
!Array.isArray(skill.requirements)
|
|
101
|
-
? skill.requirements
|
|
102
|
-
: {};
|
|
103
|
-
const localDependencies = Array.isArray(requirements.localDependencies)
|
|
104
|
-
? requirements.localDependencies
|
|
105
|
-
.filter((value) => typeof value === 'string')
|
|
106
|
-
.map((value) => value.trim())
|
|
107
|
-
.filter(Boolean)
|
|
108
|
-
: [];
|
|
107
|
+
const requirements = parsePublicSkillRequirements(skill.requirements);
|
|
109
108
|
const status = typeof skill.status === 'string' ? skill.status.trim() : '';
|
|
110
109
|
if (!skillId ||
|
|
111
110
|
!path ||
|
|
@@ -113,9 +112,10 @@ function parsePublicSkillCatalog(payload) {
|
|
|
113
112
|
throw new Error('PostPlus public skill catalog has an invalid skill.');
|
|
114
113
|
}
|
|
115
114
|
return {
|
|
116
|
-
localDependencies,
|
|
115
|
+
localDependencies: requirements.localDependencies,
|
|
117
116
|
skillId,
|
|
118
117
|
path,
|
|
118
|
+
requirements,
|
|
119
119
|
};
|
|
120
120
|
});
|
|
121
121
|
if (skills.length === 0) {
|
|
@@ -127,3 +127,40 @@ function parsePublicSkillCatalog(payload) {
|
|
|
127
127
|
source,
|
|
128
128
|
};
|
|
129
129
|
}
|
|
130
|
+
function parsePublicSkillRequirements(value) {
|
|
131
|
+
if (value === undefined) {
|
|
132
|
+
return createEmptyRequirements();
|
|
133
|
+
}
|
|
134
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
135
|
+
throw new Error('PostPlus public skill catalog has invalid skill requirements.');
|
|
136
|
+
}
|
|
137
|
+
const record = value;
|
|
138
|
+
const requirements = createEmptyRequirements();
|
|
139
|
+
for (const key of PUBLIC_SKILL_REQUIREMENT_KEYS) {
|
|
140
|
+
const raw = record[key];
|
|
141
|
+
if (raw === undefined) {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (!Array.isArray(raw)) {
|
|
145
|
+
throw new Error(`PostPlus public skill catalog has invalid ${key} requirements.`);
|
|
146
|
+
}
|
|
147
|
+
requirements[key] = raw.map((item) => {
|
|
148
|
+
if (typeof item !== 'string' || !item.trim()) {
|
|
149
|
+
throw new Error(`PostPlus public skill catalog has invalid ${key} requirements.`);
|
|
150
|
+
}
|
|
151
|
+
return item.trim();
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
return requirements;
|
|
155
|
+
}
|
|
156
|
+
function createEmptyRequirements() {
|
|
157
|
+
return {
|
|
158
|
+
accountConnections: [],
|
|
159
|
+
collectionKeys: [],
|
|
160
|
+
endpointKeys: [],
|
|
161
|
+
hostedCapabilities: [],
|
|
162
|
+
localDependencies: [],
|
|
163
|
+
modelKeys: [],
|
|
164
|
+
sourceKeys: [],
|
|
165
|
+
};
|
|
166
|
+
}
|
|
@@ -49,8 +49,38 @@ export async function runPostPlusSkillUninstall(dependencies = {
|
|
|
49
49
|
export async function generateSkillInstallStatusReport(dependencies = {
|
|
50
50
|
runCommand,
|
|
51
51
|
}) {
|
|
52
|
+
return (await inspectPostPlusSkillInstall(dependencies)).report;
|
|
53
|
+
}
|
|
54
|
+
export async function runPostPlusSkillVerify(dependencies = {
|
|
55
|
+
runCommand,
|
|
56
|
+
}) {
|
|
57
|
+
const inspection = await inspectPostPlusSkillInstall(dependencies);
|
|
58
|
+
const previousManagedSkillsReleaseId = inspection.report.managedSkillsReleaseId;
|
|
59
|
+
if (!inspection.report.ok) {
|
|
60
|
+
return {
|
|
61
|
+
...inspection.report,
|
|
62
|
+
baselineUpdated: false,
|
|
63
|
+
previousManagedSkillsReleaseId,
|
|
64
|
+
verifiedSkillsReleaseId: null,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
await writeManagedSkillBaseline({
|
|
68
|
+
releaseId: inspection.catalog.releaseId,
|
|
69
|
+
skillNames: inspection.requiredSkillNames,
|
|
70
|
+
});
|
|
71
|
+
await writeCurrentCliVersionToLocalConfig();
|
|
72
|
+
return {
|
|
73
|
+
...inspection.report,
|
|
74
|
+
baselineUpdated: true,
|
|
75
|
+
managedSkillsReleaseId: inspection.catalog.releaseId,
|
|
76
|
+
previousManagedSkillsReleaseId,
|
|
77
|
+
verifiedSkillsReleaseId: inspection.catalog.releaseId,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
async function inspectPostPlusSkillInstall(dependencies) {
|
|
52
81
|
const catalog = await loadPublicSkillCatalog();
|
|
53
|
-
const
|
|
82
|
+
const requiredSkillNames = catalog.skills.map((skill) => skill.skillId);
|
|
83
|
+
const requiredSkills = new Set(requiredSkillNames);
|
|
54
84
|
const baseline = await readManagedSkillBaseline();
|
|
55
85
|
const retiredManagedSkills = baseline.skillNames.filter((skillName) => !requiredSkills.has(skillName));
|
|
56
86
|
try {
|
|
@@ -64,36 +94,44 @@ export async function generateSkillInstallStatusReport(dependencies = {
|
|
|
64
94
|
.filter((scope) => scope.trim().length > 0)),
|
|
65
95
|
].sort();
|
|
66
96
|
return {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
97
|
+
catalog,
|
|
98
|
+
report: {
|
|
99
|
+
ok: missingSkills.length === 0,
|
|
100
|
+
error: null,
|
|
101
|
+
installCommand: formatPostPlusSkillsInstallCommand(catalog.source),
|
|
102
|
+
installedCount: installedNames.size,
|
|
103
|
+
managedSkillsReleaseId: baseline.releaseId,
|
|
104
|
+
missingSkills,
|
|
105
|
+
requiredCount: requiredSkills.size,
|
|
106
|
+
retiredManagedSkills,
|
|
107
|
+
scopes,
|
|
108
|
+
source: catalog.source,
|
|
109
|
+
updateCommand: formatPostPlusSkillUpdateCommand(),
|
|
110
|
+
uninstallCommand: formatPostPlusSkillUninstallCommand(),
|
|
111
|
+
},
|
|
112
|
+
requiredSkillNames,
|
|
79
113
|
};
|
|
80
114
|
}
|
|
81
115
|
catch (error) {
|
|
82
116
|
return {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
:
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
117
|
+
catalog,
|
|
118
|
+
report: {
|
|
119
|
+
ok: false,
|
|
120
|
+
error: error instanceof Error
|
|
121
|
+
? error.message
|
|
122
|
+
: 'Failed to inspect installed PostPlus skills.',
|
|
123
|
+
installCommand: formatPostPlusSkillsInstallCommand(catalog.source),
|
|
124
|
+
installedCount: 0,
|
|
125
|
+
managedSkillsReleaseId: baseline.releaseId,
|
|
126
|
+
missingSkills: [...requiredSkills],
|
|
127
|
+
requiredCount: requiredSkills.size,
|
|
128
|
+
retiredManagedSkills,
|
|
129
|
+
scopes: [],
|
|
130
|
+
source: catalog.source,
|
|
131
|
+
updateCommand: formatPostPlusSkillUpdateCommand(),
|
|
132
|
+
uninstallCommand: formatPostPlusSkillUninstallCommand(),
|
|
133
|
+
},
|
|
134
|
+
requiredSkillNames,
|
|
97
135
|
};
|
|
98
136
|
}
|
|
99
137
|
}
|
|
@@ -122,6 +160,31 @@ export function formatSkillInstallStatusReport(report) {
|
|
|
122
160
|
}
|
|
123
161
|
return lines.join('\n');
|
|
124
162
|
}
|
|
163
|
+
export function formatSkillBaselineVerifyReport(report) {
|
|
164
|
+
const lines = ['PostPlus skills verify', ''];
|
|
165
|
+
if (report.error) {
|
|
166
|
+
lines.push(`[FAIL] Skill installer: ${report.error}`);
|
|
167
|
+
}
|
|
168
|
+
else if (report.ok) {
|
|
169
|
+
lines.push(`[PASS] Installed released skills: ${report.installedCount}/${report.requiredCount}`);
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
lines.push(`[FAIL] Installed released skills: ${report.installedCount}/${report.requiredCount}`);
|
|
173
|
+
}
|
|
174
|
+
lines.push(` Source: ${report.source}`);
|
|
175
|
+
lines.push(` Previous managed baseline: ${report.previousManagedSkillsReleaseId ?? 'none'}`);
|
|
176
|
+
if (report.baselineUpdated && report.verifiedSkillsReleaseId) {
|
|
177
|
+
lines.push(` Verified baseline: ${report.verifiedSkillsReleaseId}`);
|
|
178
|
+
lines.push(' Next: postplus status');
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
lines.push(' Verified baseline: unchanged');
|
|
182
|
+
}
|
|
183
|
+
if (report.missingSkills.length > 0) {
|
|
184
|
+
lines.push(` Missing: ${formatSkillList(report.missingSkills, 8)}`, ` Fix: ${report.installCommand}`);
|
|
185
|
+
}
|
|
186
|
+
return lines.join('\n');
|
|
187
|
+
}
|
|
125
188
|
export function buildPostPlusSkillUpdateArgs(skillNames) {
|
|
126
189
|
if (skillNames.length === 0) {
|
|
127
190
|
throw new Error('PostPlus public skill catalog has no released skills.');
|
package/build/status.js
CHANGED
|
@@ -3,17 +3,17 @@ import { writeCurrentCliVersionToLocalConfig } from './client-compatibility.js';
|
|
|
3
3
|
import { formatDoctorReport, generateDoctorReport, } from './doctor.js';
|
|
4
4
|
import { formatSkillInstallStatusReport, generateSkillInstallStatusReport, } from './skill-management.js';
|
|
5
5
|
import { formatUpdateStatusReport, generateUpdateStatusReport, } from './update-check.js';
|
|
6
|
-
export async function generateStatusReport() {
|
|
7
|
-
return generateStatusReportWithDependencies();
|
|
6
|
+
export async function generateStatusReport(options = {}) {
|
|
7
|
+
return generateStatusReportWithDependencies({}, options);
|
|
8
8
|
}
|
|
9
|
-
export async function generateStatusReportWithDependencies(dependencies = {}) {
|
|
9
|
+
export async function generateStatusReportWithDependencies(dependencies = {}, options = {}) {
|
|
10
10
|
await writeCurrentCliVersionToLocalConfig();
|
|
11
11
|
const generateAuthStatus = dependencies.generateAuthStatus ?? generateAuthStatusReport;
|
|
12
12
|
const generateDoctor = dependencies.generateDoctor ?? generateDoctorReport;
|
|
13
13
|
const generateSkillStatus = dependencies.generateSkillStatus ?? generateSkillInstallStatusReport;
|
|
14
14
|
const generateUpdateStatus = dependencies.generateUpdateStatus ?? generateUpdateStatusReport;
|
|
15
15
|
const [doctor, auth, skills, updates] = await Promise.all([
|
|
16
|
-
generateDoctor(),
|
|
16
|
+
generateDoctor({ skillId: options.skillId }),
|
|
17
17
|
generateAuthStatus(),
|
|
18
18
|
generateSkillStatus(),
|
|
19
19
|
generateUpdateStatus(),
|
|
@@ -25,6 +25,7 @@ export async function generateStatusReportWithDependencies(dependencies = {}) {
|
|
|
25
25
|
auth,
|
|
26
26
|
skills,
|
|
27
27
|
updates,
|
|
28
|
+
...(options.skillId ? { skillId: options.skillId } : {}),
|
|
28
29
|
};
|
|
29
30
|
}
|
|
30
31
|
export function formatStatusReport(report) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@postplus/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.29",
|
|
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/index.js",
|
|
19
19
|
"build/local-dependencies.js",
|
|
20
20
|
"build/local-state.js",
|
|
21
|
+
"build/quote-confirmation.js",
|
|
21
22
|
"build/skill-catalog.js",
|
|
22
23
|
"build/skill-management.js",
|
|
23
24
|
"build/status.js",
|