@postplus/cli 0.1.28 → 0.1.30
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 +16 -1
- package/build/index.js +101 -1
- package/build/quote-confirmation.js +163 -0
- package/build/skill-management.js +90 -27
- 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
|
@@ -166,6 +166,11 @@ async function checkHostedCapabilities(input, skillScope) {
|
|
|
166
166
|
.map((value) => readCapabilityFailureLabel(value, skillScope))
|
|
167
167
|
.filter((value) => value !== null);
|
|
168
168
|
if (skillScope && hasHostedRequirements(skillScope.skill.requirements)) {
|
|
169
|
+
const subscription = readSubscriptionStatusField(payload).label;
|
|
170
|
+
if (requiresSocialPublishingPlan(skillScope.skill.requirements) &&
|
|
171
|
+
subscription === 'none') {
|
|
172
|
+
failedLabels.push(`PostPlus Plus or Pro plan required; current subscription ${subscription}`);
|
|
173
|
+
}
|
|
169
174
|
const missingRequirements = collectMissingHostedRequirementLabels(relevantCapabilities, skillScope.skill.requirements);
|
|
170
175
|
failedLabels.push(...missingRequirements);
|
|
171
176
|
}
|
|
@@ -261,7 +266,7 @@ function capabilityMatchesRequirements(capability, requirements) {
|
|
|
261
266
|
if (prefix &&
|
|
262
267
|
suffix &&
|
|
263
268
|
hostedCapabilities.has(prefix) &&
|
|
264
|
-
requirementKeys.has(suffix)) {
|
|
269
|
+
(isWholeFamilyHostedCapability(prefix) || requirementKeys.has(suffix))) {
|
|
265
270
|
return true;
|
|
266
271
|
}
|
|
267
272
|
return requirementKeys.has(identifier) || requirementKeys.has(suffix);
|
|
@@ -310,6 +315,12 @@ function collectHostedRequirementKeys(requirements) {
|
|
|
310
315
|
...requirements.sourceKeys,
|
|
311
316
|
]);
|
|
312
317
|
}
|
|
318
|
+
function isWholeFamilyHostedCapability(prefix) {
|
|
319
|
+
return prefix === 'social-publishing';
|
|
320
|
+
}
|
|
321
|
+
function requiresSocialPublishingPlan(requirements) {
|
|
322
|
+
return requirements.hostedCapabilities.includes('social-publishing');
|
|
323
|
+
}
|
|
313
324
|
function hasHostedRequirements(requirements) {
|
|
314
325
|
return (requirements.accountConnections.length > 0 ||
|
|
315
326
|
requirements.collectionKeys.length > 0 ||
|
|
@@ -337,6 +348,10 @@ function identifierMatchesKey(identifier, key) {
|
|
|
337
348
|
if (identifier === key) {
|
|
338
349
|
return true;
|
|
339
350
|
}
|
|
351
|
+
if (key === 'social-publishing-workspace' &&
|
|
352
|
+
identifierMatchesCapability(identifier, 'social-publishing')) {
|
|
353
|
+
return true;
|
|
354
|
+
}
|
|
340
355
|
const [, suffix] = splitCapabilityIdentifier(identifier);
|
|
341
356
|
return suffix === key;
|
|
342
357
|
}
|
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() {
|
|
@@ -38,6 +40,8 @@ Usage:
|
|
|
38
40
|
postplus auth validate [--json]
|
|
39
41
|
postplus auth logout [--json]
|
|
40
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]
|
|
@@ -47,6 +51,9 @@ Usage:
|
|
|
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
59
|
async function runDoctor(options) {
|
|
@@ -116,6 +123,90 @@ 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
|
}
|
|
@@ -215,6 +306,9 @@ async function main() {
|
|
|
215
306
|
if (helpTopic === 'auth') {
|
|
216
307
|
printAuthHelp();
|
|
217
308
|
}
|
|
309
|
+
else if (helpTopic === 'skills') {
|
|
310
|
+
await runSkillsCommand(['help']);
|
|
311
|
+
}
|
|
218
312
|
else {
|
|
219
313
|
printHelp();
|
|
220
314
|
}
|
|
@@ -224,6 +318,12 @@ async function main() {
|
|
|
224
318
|
case 'doctor':
|
|
225
319
|
process.exitCode = await runDoctor(parseDiagnosticOptions(rest));
|
|
226
320
|
return;
|
|
321
|
+
case 'quote':
|
|
322
|
+
process.exitCode = await runQuoteCommand(rest);
|
|
323
|
+
return;
|
|
324
|
+
case 'skills':
|
|
325
|
+
process.exitCode = await runSkillsCommand(rest);
|
|
326
|
+
return;
|
|
227
327
|
case 'install':
|
|
228
328
|
process.stderr.write(`PostPlus CLI does not install skills directly. Run \`${POSTPLUS_SKILLS_INSTALL_COMMAND}\`.\n`);
|
|
229
329
|
process.exitCode = 1;
|
|
@@ -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
|
+
}
|
|
@@ -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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@postplus/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.30",
|
|
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",
|