@postplus/cli 0.1.18 → 0.1.20
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 +1 -1
- package/build/doctor.js +70 -5
- package/build/index.js +12 -2
- package/build/local-dependencies.js +101 -0
- package/build/local-state.js +38 -0
- package/build/skill-catalog.js +80 -43
- package/build/skill-management.js +51 -11
- package/build/status.js +8 -2
- package/build/update-check.js +17 -31
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ Requires Node.js and npm.
|
|
|
33
33
|
```bash
|
|
34
34
|
npm install -g @postplus/cli
|
|
35
35
|
postplus auth login
|
|
36
|
-
npx -y skills add PostPlusAI/postplus-skills --full-depth --skill '*' --agent claude-code codex cursor github-copilot windsurf trae trae-cn --yes
|
|
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
37
|
```
|
|
38
38
|
|
|
39
39
|
Useful checks:
|
package/build/doctor.js
CHANGED
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
import { resolveFreshRemoteAuth, } from './auth-session.js';
|
|
2
2
|
import { resolveHostedBaseUrl } from './hosted-release.js';
|
|
3
|
-
|
|
3
|
+
import { formatLocalDependencyReport, generateLocalDependencyReport, } from './local-dependencies.js';
|
|
4
|
+
function createPass(id, label, detail, severity = 'required') {
|
|
4
5
|
return {
|
|
5
6
|
id,
|
|
6
7
|
label,
|
|
7
8
|
status: 'pass',
|
|
9
|
+
severity,
|
|
8
10
|
detail,
|
|
9
11
|
};
|
|
10
12
|
}
|
|
11
|
-
function createFail(id, label, detail, fix) {
|
|
13
|
+
function createFail(id, label, detail, fix, input = {}) {
|
|
12
14
|
return {
|
|
13
15
|
id,
|
|
14
16
|
label,
|
|
15
17
|
status: 'fail',
|
|
18
|
+
severity: input.severity ?? 'required',
|
|
16
19
|
detail,
|
|
17
20
|
fix,
|
|
21
|
+
metadata: input.metadata,
|
|
18
22
|
};
|
|
19
23
|
}
|
|
20
24
|
export async function generateDoctorReport() {
|
|
@@ -22,6 +26,7 @@ export async function generateDoctorReport() {
|
|
|
22
26
|
const checks = [
|
|
23
27
|
createPass('hosted_base_url', 'PostPlus Cloud', `Using ${hostedBaseUrl ?? 'https://postplus.io'}`),
|
|
24
28
|
];
|
|
29
|
+
checks.push(await checkLocalDependencies());
|
|
25
30
|
if (!hostedBaseUrl) {
|
|
26
31
|
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`.'));
|
|
27
32
|
return buildDoctorReport(checks);
|
|
@@ -43,9 +48,39 @@ export async function generateDoctorReport() {
|
|
|
43
48
|
}
|
|
44
49
|
return buildDoctorReport(checks);
|
|
45
50
|
}
|
|
51
|
+
async function checkLocalDependencies() {
|
|
52
|
+
try {
|
|
53
|
+
const report = await generateLocalDependencyReport();
|
|
54
|
+
const detail = formatLocalDependencyReport(report);
|
|
55
|
+
if (!report.ok) {
|
|
56
|
+
return createFail('local_dependencies', 'Task-specific local media dependencies', detail, 'Run the affected PostPlus skill in a local agent. The installed postplus-shared rules tell the agent how to bootstrap approved missing media dependencies.', {
|
|
57
|
+
severity: 'task_specific',
|
|
58
|
+
metadata: {
|
|
59
|
+
bootstrapRule: 'postplus-shared',
|
|
60
|
+
missingDependencies: report.checks
|
|
61
|
+
.filter((check) => !check.ok)
|
|
62
|
+
.map((check) => ({
|
|
63
|
+
dependency: check.dependency,
|
|
64
|
+
detail: check.detail,
|
|
65
|
+
skillIds: check.skillIds,
|
|
66
|
+
})),
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return createPass('local_dependencies', 'Local dependencies', detail);
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
return createFail('local_dependencies', 'Local dependencies', error instanceof Error
|
|
74
|
+
? error.message
|
|
75
|
+
: 'Failed to check local dependencies.');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
46
78
|
function buildDoctorReport(checks) {
|
|
79
|
+
const requiredOk = checks.every((check) => check.severity !== 'required' || check.status === 'pass');
|
|
47
80
|
return {
|
|
81
|
+
schemaVersion: 1,
|
|
48
82
|
ok: checks.every((check) => check.status === 'pass'),
|
|
83
|
+
requiredOk,
|
|
49
84
|
checks,
|
|
50
85
|
};
|
|
51
86
|
}
|
|
@@ -120,11 +155,33 @@ function readCapabilityFailureLabel(value) {
|
|
|
120
155
|
if (record.ok === true || record.required === false) {
|
|
121
156
|
return null;
|
|
122
157
|
}
|
|
123
|
-
|
|
158
|
+
const label = typeof record.label === 'string'
|
|
124
159
|
? record.label
|
|
125
160
|
: typeof record.id === 'string'
|
|
126
161
|
? record.id
|
|
127
162
|
: 'unknown capability';
|
|
163
|
+
const failedChecks = Array.isArray(record.checks)
|
|
164
|
+
? record.checks
|
|
165
|
+
.map(readReadinessCheckFailureLabel)
|
|
166
|
+
.filter((check) => check !== null)
|
|
167
|
+
: [];
|
|
168
|
+
return failedChecks.length > 0
|
|
169
|
+
? `${label} (${failedChecks.join(', ')})`
|
|
170
|
+
: label;
|
|
171
|
+
}
|
|
172
|
+
function readReadinessCheckFailureLabel(value) {
|
|
173
|
+
if (!value || typeof value !== 'object') {
|
|
174
|
+
return 'invalid readiness check';
|
|
175
|
+
}
|
|
176
|
+
const record = value;
|
|
177
|
+
if (record.ok === true || record.required === false) {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
return typeof record.label === 'string'
|
|
181
|
+
? record.label
|
|
182
|
+
: typeof record.id === 'string'
|
|
183
|
+
? record.id
|
|
184
|
+
: 'unknown check';
|
|
128
185
|
}
|
|
129
186
|
function readErrorMessage(payload, fallback) {
|
|
130
187
|
return typeof payload.error === 'string' && payload.error.trim().length > 0
|
|
@@ -143,12 +200,20 @@ function requestWithAuth(input, path) {
|
|
|
143
200
|
export function formatDoctorReport(report) {
|
|
144
201
|
const lines = ['PostPlus CLI doctor', ''];
|
|
145
202
|
for (const check of report.checks) {
|
|
146
|
-
const marker = check.status === 'pass'
|
|
203
|
+
const marker = check.status === 'pass'
|
|
204
|
+
? '[PASS]'
|
|
205
|
+
: check.severity === 'task_specific'
|
|
206
|
+
? '[WARN]'
|
|
207
|
+
: '[FAIL]';
|
|
147
208
|
lines.push(`${marker} ${check.label}: ${check.detail}`);
|
|
148
209
|
if (check.fix) {
|
|
149
210
|
lines.push(` Fix: ${check.fix}`);
|
|
150
211
|
}
|
|
151
212
|
}
|
|
152
|
-
lines.push('', report.ok
|
|
213
|
+
lines.push('', report.ok
|
|
214
|
+
? 'Doctor passed.'
|
|
215
|
+
: report.requiredOk
|
|
216
|
+
? 'Doctor incomplete: task-specific checks need attention.'
|
|
217
|
+
: 'Doctor failed.');
|
|
153
218
|
return lines.join('\n');
|
|
154
219
|
}
|
package/build/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import { assertConfigFilePermissions } from './local-state.js';
|
|
|
8
8
|
import { POSTPLUS_SKILLS_INSTALL_COMMAND, loadPublicSkillCatalog, } from './skill-catalog.js';
|
|
9
9
|
import { runPostPlusSkillUninstall, runPostPlusSkillUpdate, } from './skill-management.js';
|
|
10
10
|
import { formatStatusReport, generateStatusReport } from './status.js';
|
|
11
|
-
import {
|
|
11
|
+
import { readCurrentCliVersion, refreshUpdateCheckCache, } from './update-check.js';
|
|
12
12
|
function printAuthHelp() {
|
|
13
13
|
process.stdout.write(`PostPlus CLI — auth commands
|
|
14
14
|
|
|
@@ -41,6 +41,7 @@ Usage:
|
|
|
41
41
|
postplus uninstall
|
|
42
42
|
postplus list [--json]
|
|
43
43
|
postplus status [--json]
|
|
44
|
+
postplus version
|
|
44
45
|
postplus help
|
|
45
46
|
|
|
46
47
|
Skills:
|
|
@@ -96,10 +97,14 @@ async function runList(json) {
|
|
|
96
97
|
process.stdout.write(`${lines.join('\n')}\n`);
|
|
97
98
|
return 0;
|
|
98
99
|
}
|
|
100
|
+
async function runVersion() {
|
|
101
|
+
process.stdout.write(`${await readCurrentCliVersion()}\n`);
|
|
102
|
+
return 0;
|
|
103
|
+
}
|
|
99
104
|
async function runSkillUpdateCommand() {
|
|
100
105
|
const exitCode = await runPostPlusSkillUpdate();
|
|
101
106
|
if (exitCode === 0) {
|
|
102
|
-
await
|
|
107
|
+
await refreshUpdateCheckCache().catch(() => { });
|
|
103
108
|
}
|
|
104
109
|
return exitCode;
|
|
105
110
|
}
|
|
@@ -172,6 +177,11 @@ async function main() {
|
|
|
172
177
|
printHelp();
|
|
173
178
|
process.exitCode = 0;
|
|
174
179
|
return;
|
|
180
|
+
case '--version':
|
|
181
|
+
case '-v':
|
|
182
|
+
case 'version':
|
|
183
|
+
process.exitCode = await runVersion();
|
|
184
|
+
return;
|
|
175
185
|
case 'help': {
|
|
176
186
|
const [helpTopic] = rest;
|
|
177
187
|
if (helpTopic === 'auth') {
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { runCommand } from './command-runner.js';
|
|
2
|
+
import { loadPublicSkillCatalog, } from './skill-catalog.js';
|
|
3
|
+
const LOCAL_DEPENDENCY_CHECK_TIMEOUT_MS = 10_000;
|
|
4
|
+
export async function generateLocalDependencyReport(options = {}) {
|
|
5
|
+
const loadCatalog = options.loadCatalog ?? loadPublicSkillCatalog;
|
|
6
|
+
const runDependencyCheck = options.runDependencyCheck ?? runLocalDependencyCommand;
|
|
7
|
+
const catalog = await loadCatalog();
|
|
8
|
+
const requirements = collectLocalDependencyRequirements(catalog);
|
|
9
|
+
const checks = await Promise.all(requirements.map(({ dependency, skillIds }) => checkLocalDependency(dependency, skillIds, runDependencyCheck)));
|
|
10
|
+
return {
|
|
11
|
+
ok: checks.every((check) => check.ok),
|
|
12
|
+
revision: catalog.revision,
|
|
13
|
+
source: catalog.source,
|
|
14
|
+
requiredCount: checks.length,
|
|
15
|
+
checks,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export function formatLocalDependencyReport(report) {
|
|
19
|
+
if (report.requiredCount === 0) {
|
|
20
|
+
return `No local runtime dependencies are required by released PostPlus skills (${report.revision}).`;
|
|
21
|
+
}
|
|
22
|
+
const missing = report.checks.filter((check) => !check.ok);
|
|
23
|
+
if (missing.length === 0) {
|
|
24
|
+
return `Ready (${report.requiredCount} local dependencies present; catalog ${report.revision})`;
|
|
25
|
+
}
|
|
26
|
+
return `Missing ${missing.length}/${report.requiredCount}: ${missing
|
|
27
|
+
.map((check) => `${check.dependency} for ${formatSkillList(check.skillIds)}`)
|
|
28
|
+
.join('; ')}`;
|
|
29
|
+
}
|
|
30
|
+
function collectLocalDependencyRequirements(catalog) {
|
|
31
|
+
const dependencyToSkills = new Map();
|
|
32
|
+
for (const skill of catalog.skills) {
|
|
33
|
+
for (const dependency of skill.localDependencies) {
|
|
34
|
+
if (!dependencyToSkills.has(dependency)) {
|
|
35
|
+
dependencyToSkills.set(dependency, new Set());
|
|
36
|
+
}
|
|
37
|
+
dependencyToSkills.get(dependency)?.add(skill.skillId);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return [...dependencyToSkills.entries()]
|
|
41
|
+
.map(([dependency, skillIds]) => ({
|
|
42
|
+
dependency,
|
|
43
|
+
skillIds: [...skillIds].sort((a, b) => a.localeCompare(b)),
|
|
44
|
+
}))
|
|
45
|
+
.sort((a, b) => a.dependency.localeCompare(b.dependency));
|
|
46
|
+
}
|
|
47
|
+
async function checkLocalDependency(dependency, skillIds, runDependencyCheck) {
|
|
48
|
+
try {
|
|
49
|
+
const command = buildLocalDependencyCommand(dependency);
|
|
50
|
+
await runDependencyCheck(command.command, command.args);
|
|
51
|
+
return {
|
|
52
|
+
dependency,
|
|
53
|
+
ok: true,
|
|
54
|
+
detail: 'available',
|
|
55
|
+
skillIds,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
return {
|
|
60
|
+
dependency,
|
|
61
|
+
ok: false,
|
|
62
|
+
detail: error instanceof Error
|
|
63
|
+
? error.message
|
|
64
|
+
: 'Local dependency check failed.',
|
|
65
|
+
skillIds,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function buildLocalDependencyCommand(dependency) {
|
|
70
|
+
const parts = dependency.split(':');
|
|
71
|
+
if (parts.length === 1) {
|
|
72
|
+
return {
|
|
73
|
+
command: dependency,
|
|
74
|
+
args: ['--version'],
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
const [runtime, moduleName] = parts;
|
|
78
|
+
if (parts.length === 2 &&
|
|
79
|
+
runtime === 'python3' &&
|
|
80
|
+
/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(moduleName)) {
|
|
81
|
+
return {
|
|
82
|
+
command: 'python3',
|
|
83
|
+
args: [
|
|
84
|
+
'-c',
|
|
85
|
+
`import importlib; importlib.import_module(${JSON.stringify(moduleName)})`,
|
|
86
|
+
],
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
throw new Error(`Unsupported local dependency requirement: ${dependency}`);
|
|
90
|
+
}
|
|
91
|
+
async function runLocalDependencyCommand(command, args) {
|
|
92
|
+
await runCommand(command, args, {
|
|
93
|
+
timeoutMs: LOCAL_DEPENDENCY_CHECK_TIMEOUT_MS,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
function formatSkillList(skillIds) {
|
|
97
|
+
if (skillIds.length <= 3) {
|
|
98
|
+
return skillIds.join(', ');
|
|
99
|
+
}
|
|
100
|
+
return `${skillIds.slice(0, 3).join(', ')} +${skillIds.length - 3} more`;
|
|
101
|
+
}
|
package/build/local-state.js
CHANGED
|
@@ -103,6 +103,41 @@ export async function clearLocalAuthState() {
|
|
|
103
103
|
return next;
|
|
104
104
|
});
|
|
105
105
|
}
|
|
106
|
+
export async function readManagedSkillBaseline() {
|
|
107
|
+
const config = await readLocalConfig();
|
|
108
|
+
const managedSkills = config?.managedSkills;
|
|
109
|
+
if (!managedSkills ||
|
|
110
|
+
typeof managedSkills.revision !== 'string' ||
|
|
111
|
+
!Array.isArray(managedSkills.skillNames)) {
|
|
112
|
+
return {
|
|
113
|
+
revision: null,
|
|
114
|
+
skillNames: [],
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
revision: managedSkills.revision,
|
|
119
|
+
skillNames: normalizeSkillNames(managedSkills.skillNames),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
export async function writeManagedSkillBaseline(input) {
|
|
123
|
+
return updateLocalConfig((current) => ({
|
|
124
|
+
...(current ?? {}),
|
|
125
|
+
managedSkills: {
|
|
126
|
+
revision: input.revision,
|
|
127
|
+
skillNames: normalizeSkillNames(input.skillNames),
|
|
128
|
+
updatedAt: new Date().toISOString(),
|
|
129
|
+
},
|
|
130
|
+
}));
|
|
131
|
+
}
|
|
132
|
+
export async function clearManagedSkillBaseline() {
|
|
133
|
+
return updateLocalConfig((current) => {
|
|
134
|
+
const next = {
|
|
135
|
+
...(current ?? {}),
|
|
136
|
+
};
|
|
137
|
+
delete next.managedSkills;
|
|
138
|
+
return next;
|
|
139
|
+
});
|
|
140
|
+
}
|
|
106
141
|
export async function setLocalApiBaseUrl(apiBaseUrl) {
|
|
107
142
|
const normalizedApiBaseUrl = apiBaseUrl.trim();
|
|
108
143
|
if (normalizedApiBaseUrl.length === 0) {
|
|
@@ -226,3 +261,6 @@ function omitLegacyAuthFields(current) {
|
|
|
226
261
|
const { apiKey: _apiKey, machineId: _machineId, ...rest } = (current ?? {});
|
|
227
262
|
return rest;
|
|
228
263
|
}
|
|
264
|
+
function normalizeSkillNames(values) {
|
|
265
|
+
return [...new Set(values.map((value) => value.trim()).filter(Boolean))].sort((left, right) => left.localeCompare(right));
|
|
266
|
+
}
|
package/build/skill-catalog.js
CHANGED
|
@@ -12,64 +12,101 @@ const POSTPLUS_SKILLS_AGENT_ARGS = POSTPLUS_SKILLS_AGENT_TARGETS.join(' ');
|
|
|
12
12
|
export const POSTPLUS_SKILLS_INSTALL_COMMAND = `npx -y skills add PostPlusAI/postplus-skills --global --full-depth --skill '*' --agent ${POSTPLUS_SKILLS_AGENT_ARGS} --yes`;
|
|
13
13
|
export const POSTPLUS_SKILLS_LIST_COMMAND = 'npx -y skills add PostPlusAI/postplus-skills --list --full-depth';
|
|
14
14
|
const POSTPLUS_SKILLS_INDEX_URL = 'https://raw.githubusercontent.com/PostPlusAI/postplus-skills/main/skills/INDEX.md';
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
const POSTPLUS_SKILLS_CATALOG_URL = 'https://raw.githubusercontent.com/PostPlusAI/postplus-skills/main/skills/catalog.json';
|
|
16
|
+
export async function loadPublicSkillCatalog(fetchFn = fetch) {
|
|
17
|
+
const response = await fetchFn(POSTPLUS_SKILLS_CATALOG_URL, {
|
|
17
18
|
headers: {
|
|
18
|
-
accept: '
|
|
19
|
+
accept: 'application/json',
|
|
19
20
|
},
|
|
20
21
|
signal: AbortSignal.timeout(15000),
|
|
21
22
|
});
|
|
22
23
|
if (!response.ok) {
|
|
23
24
|
throw new Error(`Failed to load PostPlus skill catalog (${response.status}): ${response.statusText}`);
|
|
24
25
|
}
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
throw new Error('PostPlus public skill catalog is invalid: no released skills were found.');
|
|
29
|
-
}
|
|
26
|
+
const raw = await response.text();
|
|
27
|
+
const payload = parseJsonResponse(raw, POSTPLUS_SKILLS_CATALOG_URL);
|
|
28
|
+
const catalog = parsePublicSkillCatalog(payload);
|
|
30
29
|
return {
|
|
31
|
-
|
|
30
|
+
...catalog,
|
|
31
|
+
catalogUrl: POSTPLUS_SKILLS_CATALOG_URL,
|
|
32
32
|
indexUrl: POSTPLUS_SKILLS_INDEX_URL,
|
|
33
33
|
installCommand: POSTPLUS_SKILLS_INSTALL_COMMAND,
|
|
34
34
|
listCommand: POSTPLUS_SKILLS_LIST_COMMAND,
|
|
35
|
-
skills,
|
|
36
35
|
};
|
|
37
36
|
}
|
|
38
|
-
function
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if (
|
|
45
|
-
|
|
46
|
-
sawReleasedSkillsSection = true;
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
if (!inReleasedSkills) {
|
|
50
|
-
continue;
|
|
37
|
+
function parseJsonResponse(raw, url) {
|
|
38
|
+
try {
|
|
39
|
+
return JSON.parse(raw);
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
const trimmed = raw.trimStart();
|
|
43
|
+
if (trimmed.startsWith('<')) {
|
|
44
|
+
throw new Error(`PostPlus public skill catalog returned HTML instead of JSON: ${url}`);
|
|
51
45
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
46
|
+
throw new Error(error instanceof Error
|
|
47
|
+
? `PostPlus public skill catalog returned invalid JSON: ${error.message}`
|
|
48
|
+
: 'PostPlus public skill catalog returned invalid JSON.');
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function parsePublicSkillCatalog(payload) {
|
|
52
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
53
|
+
throw new Error('PostPlus public skill catalog is invalid.');
|
|
54
|
+
}
|
|
55
|
+
const record = payload;
|
|
56
|
+
const revision = typeof record.revision === 'string' && record.revision.trim()
|
|
57
|
+
? record.revision.trim()
|
|
58
|
+
: null;
|
|
59
|
+
const source = typeof record.source === 'string' && record.source.trim()
|
|
60
|
+
? record.source.trim()
|
|
61
|
+
: null;
|
|
62
|
+
if (record.schemaVersion !== 1 ||
|
|
63
|
+
source !== POSTPLUS_SKILLS_REPO ||
|
|
64
|
+
!revision) {
|
|
65
|
+
throw new Error('PostPlus public skill catalog metadata is invalid.');
|
|
66
|
+
}
|
|
67
|
+
if (!Array.isArray(record.skills)) {
|
|
68
|
+
throw new Error('PostPlus public skill catalog has no skills array.');
|
|
69
|
+
}
|
|
70
|
+
const skills = record.skills.map((value) => {
|
|
71
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
72
|
+
throw new Error('PostPlus public skill catalog has an invalid skill.');
|
|
62
73
|
}
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
74
|
+
const skill = value;
|
|
75
|
+
const skillId = typeof skill.name === 'string' && skill.name.trim()
|
|
76
|
+
? skill.name.trim()
|
|
77
|
+
: null;
|
|
78
|
+
const path = typeof skill.path === 'string' && skill.path.trim()
|
|
79
|
+
? skill.path.trim()
|
|
80
|
+
: null;
|
|
81
|
+
const requirements = skill.requirements &&
|
|
82
|
+
typeof skill.requirements === 'object' &&
|
|
83
|
+
!Array.isArray(skill.requirements)
|
|
84
|
+
? skill.requirements
|
|
85
|
+
: {};
|
|
86
|
+
const localDependencies = Array.isArray(requirements.localDependencies)
|
|
87
|
+
? requirements.localDependencies
|
|
88
|
+
.filter((value) => typeof value === 'string')
|
|
89
|
+
.map((value) => value.trim())
|
|
90
|
+
.filter(Boolean)
|
|
91
|
+
: [];
|
|
92
|
+
const status = typeof skill.status === 'string' ? skill.status.trim() : '';
|
|
93
|
+
if (!skillId ||
|
|
94
|
+
!path ||
|
|
95
|
+
!(status === 'released' || status.startsWith('released/'))) {
|
|
96
|
+
throw new Error('PostPlus public skill catalog has an invalid skill.');
|
|
69
97
|
}
|
|
98
|
+
return {
|
|
99
|
+
localDependencies,
|
|
100
|
+
skillId,
|
|
101
|
+
path,
|
|
102
|
+
};
|
|
103
|
+
});
|
|
104
|
+
if (skills.length === 0) {
|
|
105
|
+
throw new Error('PostPlus public skill catalog is invalid: no released skills were found.');
|
|
70
106
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
107
|
+
return {
|
|
108
|
+
revision,
|
|
109
|
+
skills,
|
|
110
|
+
source,
|
|
111
|
+
};
|
|
75
112
|
}
|
|
@@ -1,27 +1,56 @@
|
|
|
1
|
-
import { POSTPLUS_SKILLS_AGENT_TARGETS, POSTPLUS_SKILLS_INSTALL_COMMAND, POSTPLUS_SKILLS_REPO, loadPublicSkillCatalog, } from './skill-catalog.js';
|
|
2
1
|
import { runCommand, runInteractiveCommand } from './command-runner.js';
|
|
2
|
+
import { clearManagedSkillBaseline, readManagedSkillBaseline, writeManagedSkillBaseline, } from './local-state.js';
|
|
3
|
+
import { POSTPLUS_SKILLS_AGENT_TARGETS, POSTPLUS_SKILLS_INSTALL_COMMAND, POSTPLUS_SKILLS_REPO, loadPublicSkillCatalog, } from './skill-catalog.js';
|
|
3
4
|
const NPX_SKILLS = ['-y', 'skills'];
|
|
4
|
-
export async function runPostPlusSkillUpdate(
|
|
5
|
+
export async function runPostPlusSkillUpdate(dependencies = {
|
|
6
|
+
runInteractiveCommand,
|
|
7
|
+
}) {
|
|
5
8
|
const catalog = await loadPublicSkillCatalog();
|
|
6
9
|
const skillNames = catalog.skills.map((skill) => skill.skillId);
|
|
10
|
+
const baseline = await readManagedSkillBaseline();
|
|
11
|
+
const retiredSkillNames = baseline.skillNames.filter((skillName) => !skillNames.includes(skillName));
|
|
7
12
|
if (skillNames.length === 0) {
|
|
8
13
|
throw new Error('PostPlus public skill catalog has no released skills.');
|
|
9
14
|
}
|
|
10
|
-
|
|
15
|
+
const updateExitCode = await dependencies.runInteractiveCommand('npx', buildPostPlusSkillUpdateArgs(skillNames));
|
|
16
|
+
if (updateExitCode !== 0) {
|
|
17
|
+
return updateExitCode;
|
|
18
|
+
}
|
|
19
|
+
if (retiredSkillNames.length > 0) {
|
|
20
|
+
const removeExitCode = await dependencies.runInteractiveCommand('npx', buildPostPlusSkillUninstallArgs(retiredSkillNames));
|
|
21
|
+
if (removeExitCode !== 0) {
|
|
22
|
+
return removeExitCode;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
await writeManagedSkillBaseline({
|
|
26
|
+
revision: catalog.revision,
|
|
27
|
+
skillNames,
|
|
28
|
+
});
|
|
29
|
+
return 0;
|
|
11
30
|
}
|
|
12
|
-
export async function runPostPlusSkillUninstall(
|
|
31
|
+
export async function runPostPlusSkillUninstall(dependencies = {
|
|
32
|
+
runInteractiveCommand,
|
|
33
|
+
}) {
|
|
13
34
|
const catalog = await loadPublicSkillCatalog();
|
|
14
35
|
const skillNames = catalog.skills.map((skill) => skill.skillId);
|
|
15
|
-
|
|
36
|
+
const baseline = await readManagedSkillBaseline();
|
|
37
|
+
const allKnownSkillNames = mergeSkillNames(skillNames, baseline.skillNames);
|
|
38
|
+
if (allKnownSkillNames.length === 0) {
|
|
16
39
|
throw new Error('PostPlus public skill catalog has no released skills.');
|
|
17
40
|
}
|
|
18
|
-
|
|
41
|
+
const exitCode = await dependencies.runInteractiveCommand('npx', buildPostPlusSkillUninstallArgs(allKnownSkillNames));
|
|
42
|
+
if (exitCode === 0) {
|
|
43
|
+
await clearManagedSkillBaseline();
|
|
44
|
+
}
|
|
45
|
+
return exitCode;
|
|
19
46
|
}
|
|
20
47
|
export async function generateSkillInstallStatusReport(dependencies = {
|
|
21
48
|
runCommand,
|
|
22
49
|
}) {
|
|
23
50
|
const catalog = await loadPublicSkillCatalog();
|
|
24
51
|
const requiredSkills = new Set(catalog.skills.map((skill) => skill.skillId));
|
|
52
|
+
const baseline = await readManagedSkillBaseline();
|
|
53
|
+
const retiredManagedSkills = baseline.skillNames.filter((skillName) => !requiredSkills.has(skillName));
|
|
25
54
|
try {
|
|
26
55
|
const installed = await listInstalledSkills(dependencies);
|
|
27
56
|
const postPlusInstalled = installed.filter((skill) => requiredSkills.has(skill.name));
|
|
@@ -37,8 +66,10 @@ export async function generateSkillInstallStatusReport(dependencies = {
|
|
|
37
66
|
error: null,
|
|
38
67
|
installCommand: POSTPLUS_SKILLS_INSTALL_COMMAND,
|
|
39
68
|
installedCount: installedNames.size,
|
|
69
|
+
managedRevision: baseline.revision,
|
|
40
70
|
missingSkills,
|
|
41
71
|
requiredCount: requiredSkills.size,
|
|
72
|
+
retiredManagedSkills,
|
|
42
73
|
scopes,
|
|
43
74
|
source: POSTPLUS_SKILLS_REPO,
|
|
44
75
|
updateCommand: formatPostPlusSkillUpdateCommand(),
|
|
@@ -53,8 +84,10 @@ export async function generateSkillInstallStatusReport(dependencies = {
|
|
|
53
84
|
: 'Failed to inspect installed PostPlus skills.',
|
|
54
85
|
installCommand: POSTPLUS_SKILLS_INSTALL_COMMAND,
|
|
55
86
|
installedCount: 0,
|
|
87
|
+
managedRevision: baseline.revision,
|
|
56
88
|
missingSkills: [...requiredSkills],
|
|
57
89
|
requiredCount: requiredSkills.size,
|
|
90
|
+
retiredManagedSkills,
|
|
58
91
|
scopes: [],
|
|
59
92
|
source: POSTPLUS_SKILLS_REPO,
|
|
60
93
|
updateCommand: formatPostPlusSkillUpdateCommand(),
|
|
@@ -74,7 +107,11 @@ export function formatSkillInstallStatusReport(report) {
|
|
|
74
107
|
lines.push(`[FAIL] Installed released skills: ${report.installedCount}/${report.requiredCount}`);
|
|
75
108
|
}
|
|
76
109
|
lines.push(` Source: ${report.source}`);
|
|
110
|
+
lines.push(` Managed baseline: ${report.managedRevision ?? 'none'}`);
|
|
77
111
|
lines.push(` Scope: ${report.scopes.length > 0 ? report.scopes.join(', ') : 'none detected'}`);
|
|
112
|
+
if (report.retiredManagedSkills.length > 0) {
|
|
113
|
+
lines.push(` Retired managed skills: ${formatSkillList(report.retiredManagedSkills, 8)}`, ` Cleanup: ${report.updateCommand}`);
|
|
114
|
+
}
|
|
78
115
|
if (report.missingSkills.length > 0) {
|
|
79
116
|
lines.push(` Missing: ${formatSkillList(report.missingSkills, 8)}`, ` Fix: ${report.installCommand}`);
|
|
80
117
|
}
|
|
@@ -103,11 +140,12 @@ export function formatPostPlusSkillUpdateCommand() {
|
|
|
103
140
|
export function formatPostPlusSkillUninstallCommand() {
|
|
104
141
|
return 'postplus uninstall';
|
|
105
142
|
}
|
|
143
|
+
function mergeSkillNames(left, right) {
|
|
144
|
+
return [...new Set([...left, ...right])].sort((a, b) => a.localeCompare(b));
|
|
145
|
+
}
|
|
106
146
|
async function listInstalledSkills(dependencies) {
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
listInstalledSkillsForScope(dependencies, ['--global']),
|
|
110
|
-
]);
|
|
147
|
+
const project = await listInstalledSkillsForScope(dependencies, []);
|
|
148
|
+
const global = await listInstalledSkillsForScope(dependencies, ['--global']);
|
|
111
149
|
const byKey = new Map();
|
|
112
150
|
for (const skill of [...project, ...global]) {
|
|
113
151
|
byKey.set(`${skill.scope}:${skill.name}:${skill.path}`, skill);
|
|
@@ -151,5 +189,7 @@ function normalizeInstalledSkillEntry(value) {
|
|
|
151
189
|
function formatSkillList(skills, limit) {
|
|
152
190
|
const visible = skills.slice(0, limit);
|
|
153
191
|
const rest = skills.length - visible.length;
|
|
154
|
-
return rest > 0
|
|
192
|
+
return rest > 0
|
|
193
|
+
? `${visible.join(', ')} (+${rest} more)`
|
|
194
|
+
: visible.join(', ');
|
|
155
195
|
}
|
package/build/status.js
CHANGED
|
@@ -17,7 +17,8 @@ export async function generateStatusReportWithDependencies(dependencies = {}) {
|
|
|
17
17
|
generateUpdateStatus(),
|
|
18
18
|
]);
|
|
19
19
|
return {
|
|
20
|
-
|
|
20
|
+
schemaVersion: 1,
|
|
21
|
+
ok: doctor.requiredOk && auth.ok && skills.ok && updates.ok,
|
|
21
22
|
doctor,
|
|
22
23
|
auth,
|
|
23
24
|
skills,
|
|
@@ -25,10 +26,15 @@ export async function generateStatusReportWithDependencies(dependencies = {}) {
|
|
|
25
26
|
};
|
|
26
27
|
}
|
|
27
28
|
export function formatStatusReport(report) {
|
|
29
|
+
const taskSpecificChecksNeedAttention = report.doctor.requiredOk && !report.doctor.ok;
|
|
28
30
|
return [
|
|
29
31
|
'PostPlus CLI status',
|
|
30
32
|
'',
|
|
31
|
-
`Overall: ${report.ok
|
|
33
|
+
`Overall: ${report.ok
|
|
34
|
+
? taskSpecificChecksNeedAttention
|
|
35
|
+
? 'OK (task-specific checks need attention)'
|
|
36
|
+
: 'OK'
|
|
37
|
+
: 'INCOMPLETE'}`,
|
|
32
38
|
'',
|
|
33
39
|
formatDoctorReport(report.doctor),
|
|
34
40
|
'',
|
package/build/update-check.js
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
2
1
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
3
2
|
import { dirname, join } from 'node:path';
|
|
4
|
-
import { getPostPlusConfigDir } from './local-state.js';
|
|
5
|
-
import { POSTPLUS_SKILLS_REPO } from './skill-catalog.js';
|
|
3
|
+
import { getPostPlusConfigDir, readManagedSkillBaseline, } from './local-state.js';
|
|
4
|
+
import { POSTPLUS_SKILLS_REPO, loadPublicSkillCatalog, } from './skill-catalog.js';
|
|
6
5
|
const UPDATE_CHECK_TTL_MS = 24 * 60 * 60 * 1000;
|
|
7
6
|
const UPDATE_CHECK_CACHE_FILE = 'update-check.json';
|
|
8
7
|
const NPM_PACKAGE_NAME = '@postplus/cli';
|
|
9
8
|
const NPM_LATEST_URL = `https://registry.npmjs.org/${encodeURIComponent(NPM_PACKAGE_NAME)}/latest`;
|
|
10
|
-
const POSTPLUS_SKILLS_MAIN_URL = 'https://api.github.com/repos/PostPlusAI/postplus-skills/commits/main';
|
|
11
9
|
export async function generateUpdateStatusReport(input = {}, dependencies = {
|
|
12
10
|
fetchFn: fetch,
|
|
13
11
|
}) {
|
|
14
12
|
const currentVersion = await readCurrentCliVersion();
|
|
13
|
+
const managedSkillBaseline = await readManagedSkillBaseline();
|
|
15
14
|
const cache = await readUpdateCheckCache();
|
|
16
15
|
if (cache &&
|
|
17
16
|
!input.force &&
|
|
@@ -20,7 +19,7 @@ export async function generateUpdateStatusReport(input = {}, dependencies = {
|
|
|
20
19
|
return buildUpdateReport({
|
|
21
20
|
cache,
|
|
22
21
|
currentVersion,
|
|
23
|
-
|
|
22
|
+
currentSkillRevision: managedSkillBaseline.revision,
|
|
24
23
|
source: 'cache',
|
|
25
24
|
});
|
|
26
25
|
}
|
|
@@ -43,9 +42,7 @@ export async function generateUpdateStatusReport(input = {}, dependencies = {
|
|
|
43
42
|
return buildUpdateReport({
|
|
44
43
|
cache: nextCache,
|
|
45
44
|
currentVersion,
|
|
46
|
-
|
|
47
|
-
? latestSkillRevision
|
|
48
|
-
: cache?.skills.latestRevision ?? latestSkillRevision,
|
|
45
|
+
currentSkillRevision: managedSkillBaseline.revision,
|
|
49
46
|
source: 'remote',
|
|
50
47
|
});
|
|
51
48
|
}
|
|
@@ -56,7 +53,7 @@ export async function generateUpdateStatusReport(input = {}, dependencies = {
|
|
|
56
53
|
...buildUpdateReport({
|
|
57
54
|
cache,
|
|
58
55
|
currentVersion,
|
|
59
|
-
|
|
56
|
+
currentSkillRevision: managedSkillBaseline.revision,
|
|
60
57
|
source: 'cache',
|
|
61
58
|
}),
|
|
62
59
|
warning,
|
|
@@ -73,7 +70,7 @@ export async function generateUpdateStatusReport(input = {}, dependencies = {
|
|
|
73
70
|
updateCommand: 'npm install -g @postplus/cli',
|
|
74
71
|
},
|
|
75
72
|
skills: {
|
|
76
|
-
currentRevision:
|
|
73
|
+
currentRevision: managedSkillBaseline.revision,
|
|
77
74
|
latestRevision: null,
|
|
78
75
|
updateAvailable: false,
|
|
79
76
|
updateCommand: 'postplus update',
|
|
@@ -82,10 +79,9 @@ export async function generateUpdateStatusReport(input = {}, dependencies = {
|
|
|
82
79
|
};
|
|
83
80
|
}
|
|
84
81
|
}
|
|
85
|
-
export async function
|
|
82
|
+
export async function refreshUpdateCheckCache() {
|
|
86
83
|
await generateUpdateStatusReport({
|
|
87
84
|
force: true,
|
|
88
|
-
resetSkillBaseline: true,
|
|
89
85
|
});
|
|
90
86
|
}
|
|
91
87
|
export function formatUpdateStatusReport(report) {
|
|
@@ -116,19 +112,20 @@ function buildUpdateReport(input) {
|
|
|
116
112
|
cli: {
|
|
117
113
|
currentVersion: input.currentVersion,
|
|
118
114
|
latestVersion: input.cache.cli.latestVersion,
|
|
119
|
-
updateAvailable: compareVersions(input.cache.cli.latestVersion, input.currentVersion) >
|
|
115
|
+
updateAvailable: compareVersions(input.cache.cli.latestVersion, input.currentVersion) >
|
|
116
|
+
0,
|
|
120
117
|
updateCommand: 'npm install -g @postplus/cli',
|
|
121
118
|
},
|
|
122
119
|
skills: {
|
|
123
|
-
currentRevision: input.
|
|
120
|
+
currentRevision: input.currentSkillRevision,
|
|
124
121
|
latestRevision: input.cache.skills.latestRevision,
|
|
125
|
-
updateAvailable: input.cache.skills.latestRevision !== input.
|
|
122
|
+
updateAvailable: input.cache.skills.latestRevision !== input.currentSkillRevision,
|
|
126
123
|
updateCommand: 'postplus update',
|
|
127
124
|
},
|
|
128
125
|
warning: null,
|
|
129
126
|
};
|
|
130
127
|
}
|
|
131
|
-
async function readCurrentCliVersion() {
|
|
128
|
+
export async function readCurrentCliVersion() {
|
|
132
129
|
const packageJsonPath = new URL('../package.json', import.meta.url);
|
|
133
130
|
const raw = await readFile(packageJsonPath, 'utf8');
|
|
134
131
|
const parsed = JSON.parse(raw);
|
|
@@ -155,23 +152,12 @@ async function fetchLatestCliVersion(fetchFn) {
|
|
|
155
152
|
return payload.version.trim();
|
|
156
153
|
}
|
|
157
154
|
async function fetchLatestSkillRevision(fetchFn) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
accept: 'application/vnd.github+json',
|
|
161
|
-
'user-agent': `postplus-cli-update-check/${await readCurrentCliVersion()}`,
|
|
162
|
-
},
|
|
163
|
-
signal: AbortSignal.timeout(15_000),
|
|
164
|
-
});
|
|
165
|
-
if (!response.ok) {
|
|
166
|
-
throw new Error(`Failed to check latest ${POSTPLUS_SKILLS_REPO} revision (${response.status}).`);
|
|
155
|
+
try {
|
|
156
|
+
return (await loadPublicSkillCatalog(fetchFn)).revision;
|
|
167
157
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
return payload.sha.trim();
|
|
158
|
+
catch (error) {
|
|
159
|
+
throw new Error(`Failed to check latest ${POSTPLUS_SKILLS_REPO} revision: ${error instanceof Error ? error.message : String(error)}`);
|
|
171
160
|
}
|
|
172
|
-
return createHash('sha256')
|
|
173
|
-
.update(JSON.stringify(payload))
|
|
174
|
-
.digest('hex');
|
|
175
161
|
}
|
|
176
162
|
async function readUpdateCheckCache() {
|
|
177
163
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@postplus/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.20",
|
|
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.",
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"build/doctor.js",
|
|
16
16
|
"build/hosted-release.js",
|
|
17
17
|
"build/index.js",
|
|
18
|
+
"build/local-dependencies.js",
|
|
18
19
|
"build/local-state.js",
|
|
19
20
|
"build/skill-catalog.js",
|
|
20
21
|
"build/skill-management.js",
|