@postplus/cli 0.1.17 → 0.1.19
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 +0 -3
- package/build/doctor.js +41 -1
- package/build/index.js +2 -2
- package/build/local-dependencies.js +101 -0
- package/build/local-state.js +38 -0
- package/build/skill-catalog.js +66 -44
- package/build/skill-management.js +49 -7
- package/build/status.js +1 -0
- package/build/update-check.js +16 -30
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -36,9 +36,6 @@ 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
37
|
```
|
|
38
38
|
|
|
39
|
-
Start a new agent session or restart the local agent app after installation so
|
|
40
|
-
the global skills are loaded.
|
|
41
|
-
|
|
42
39
|
Useful checks:
|
|
43
40
|
|
|
44
41
|
```bash
|
package/build/doctor.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { resolveFreshRemoteAuth, } from './auth-session.js';
|
|
2
2
|
import { resolveHostedBaseUrl } from './hosted-release.js';
|
|
3
|
+
import { formatLocalDependencyReport, generateLocalDependencyReport, } from './local-dependencies.js';
|
|
3
4
|
function createPass(id, label, detail) {
|
|
4
5
|
return {
|
|
5
6
|
id,
|
|
@@ -22,6 +23,7 @@ export async function generateDoctorReport() {
|
|
|
22
23
|
const checks = [
|
|
23
24
|
createPass('hosted_base_url', 'PostPlus Cloud', `Using ${hostedBaseUrl ?? 'https://postplus.io'}`),
|
|
24
25
|
];
|
|
26
|
+
checks.push(await checkLocalDependencies());
|
|
25
27
|
if (!hostedBaseUrl) {
|
|
26
28
|
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
29
|
return buildDoctorReport(checks);
|
|
@@ -43,8 +45,24 @@ export async function generateDoctorReport() {
|
|
|
43
45
|
}
|
|
44
46
|
return buildDoctorReport(checks);
|
|
45
47
|
}
|
|
48
|
+
async function checkLocalDependencies() {
|
|
49
|
+
try {
|
|
50
|
+
const report = await generateLocalDependencyReport();
|
|
51
|
+
const detail = formatLocalDependencyReport(report);
|
|
52
|
+
if (!report.ok) {
|
|
53
|
+
return createFail('local_dependencies', 'Local 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.');
|
|
54
|
+
}
|
|
55
|
+
return createPass('local_dependencies', 'Local dependencies', detail);
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
return createFail('local_dependencies', 'Local dependencies', error instanceof Error
|
|
59
|
+
? error.message
|
|
60
|
+
: 'Failed to check local dependencies.');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
46
63
|
function buildDoctorReport(checks) {
|
|
47
64
|
return {
|
|
65
|
+
schemaVersion: 1,
|
|
48
66
|
ok: checks.every((check) => check.status === 'pass'),
|
|
49
67
|
checks,
|
|
50
68
|
};
|
|
@@ -120,11 +138,33 @@ function readCapabilityFailureLabel(value) {
|
|
|
120
138
|
if (record.ok === true || record.required === false) {
|
|
121
139
|
return null;
|
|
122
140
|
}
|
|
123
|
-
|
|
141
|
+
const label = typeof record.label === 'string'
|
|
124
142
|
? record.label
|
|
125
143
|
: typeof record.id === 'string'
|
|
126
144
|
? record.id
|
|
127
145
|
: 'unknown capability';
|
|
146
|
+
const failedChecks = Array.isArray(record.checks)
|
|
147
|
+
? record.checks
|
|
148
|
+
.map(readReadinessCheckFailureLabel)
|
|
149
|
+
.filter((check) => check !== null)
|
|
150
|
+
: [];
|
|
151
|
+
return failedChecks.length > 0
|
|
152
|
+
? `${label} (${failedChecks.join(', ')})`
|
|
153
|
+
: label;
|
|
154
|
+
}
|
|
155
|
+
function readReadinessCheckFailureLabel(value) {
|
|
156
|
+
if (!value || typeof value !== 'object') {
|
|
157
|
+
return 'invalid readiness check';
|
|
158
|
+
}
|
|
159
|
+
const record = value;
|
|
160
|
+
if (record.ok === true || record.required === false) {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
return typeof record.label === 'string'
|
|
164
|
+
? record.label
|
|
165
|
+
: typeof record.id === 'string'
|
|
166
|
+
? record.id
|
|
167
|
+
: 'unknown check';
|
|
128
168
|
}
|
|
129
169
|
function readErrorMessage(payload, fallback) {
|
|
130
170
|
return typeof payload.error === 'string' && payload.error.trim().length > 0
|
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 { refreshUpdateCheckCache } from './update-check.js';
|
|
12
12
|
function printAuthHelp() {
|
|
13
13
|
process.stdout.write(`PostPlus CLI — auth commands
|
|
14
14
|
|
|
@@ -99,7 +99,7 @@ async function runList(json) {
|
|
|
99
99
|
async function runSkillUpdateCommand() {
|
|
100
100
|
const exitCode = await runPostPlusSkillUpdate();
|
|
101
101
|
if (exitCode === 0) {
|
|
102
|
-
await
|
|
102
|
+
await refreshUpdateCheckCache().catch(() => { });
|
|
103
103
|
}
|
|
104
104
|
return exitCode;
|
|
105
105
|
}
|
|
@@ -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,86 @@ 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
|
-
if (skills.length === 0) {
|
|
28
|
-
throw new Error('PostPlus public skill catalog is invalid: no released skills were found.');
|
|
29
|
-
}
|
|
26
|
+
const payload = (await response.json());
|
|
27
|
+
const catalog = parsePublicSkillCatalog(payload);
|
|
30
28
|
return {
|
|
31
|
-
|
|
29
|
+
...catalog,
|
|
30
|
+
catalogUrl: POSTPLUS_SKILLS_CATALOG_URL,
|
|
32
31
|
indexUrl: POSTPLUS_SKILLS_INDEX_URL,
|
|
33
32
|
installCommand: POSTPLUS_SKILLS_INSTALL_COMMAND,
|
|
34
33
|
listCommand: POSTPLUS_SKILLS_LIST_COMMAND,
|
|
35
|
-
skills,
|
|
36
34
|
};
|
|
37
35
|
}
|
|
38
|
-
function
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
continue;
|
|
36
|
+
function parsePublicSkillCatalog(payload) {
|
|
37
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
38
|
+
throw new Error('PostPlus public skill catalog is invalid.');
|
|
39
|
+
}
|
|
40
|
+
const record = payload;
|
|
41
|
+
const revision = typeof record.revision === 'string' && record.revision.trim()
|
|
42
|
+
? record.revision.trim()
|
|
43
|
+
: null;
|
|
44
|
+
const source = typeof record.source === 'string' && record.source.trim()
|
|
45
|
+
? record.source.trim()
|
|
46
|
+
: null;
|
|
47
|
+
if (record.schemaVersion !== 1 ||
|
|
48
|
+
source !== POSTPLUS_SKILLS_REPO ||
|
|
49
|
+
!revision) {
|
|
50
|
+
throw new Error('PostPlus public skill catalog metadata is invalid.');
|
|
51
|
+
}
|
|
52
|
+
if (!Array.isArray(record.skills)) {
|
|
53
|
+
throw new Error('PostPlus public skill catalog has no skills array.');
|
|
54
|
+
}
|
|
55
|
+
const skills = record.skills.map((value) => {
|
|
56
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
57
|
+
throw new Error('PostPlus public skill catalog has an invalid skill.');
|
|
62
58
|
}
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
59
|
+
const skill = value;
|
|
60
|
+
const skillId = typeof skill.name === 'string' && skill.name.trim()
|
|
61
|
+
? skill.name.trim()
|
|
62
|
+
: null;
|
|
63
|
+
const path = typeof skill.path === 'string' && skill.path.trim()
|
|
64
|
+
? skill.path.trim()
|
|
65
|
+
: null;
|
|
66
|
+
const requirements = skill.requirements &&
|
|
67
|
+
typeof skill.requirements === 'object' &&
|
|
68
|
+
!Array.isArray(skill.requirements)
|
|
69
|
+
? skill.requirements
|
|
70
|
+
: {};
|
|
71
|
+
const localDependencies = Array.isArray(requirements.localDependencies)
|
|
72
|
+
? requirements.localDependencies
|
|
73
|
+
.filter((value) => typeof value === 'string')
|
|
74
|
+
.map((value) => value.trim())
|
|
75
|
+
.filter(Boolean)
|
|
76
|
+
: [];
|
|
77
|
+
const status = typeof skill.status === 'string' ? skill.status.trim() : '';
|
|
78
|
+
if (!skillId ||
|
|
79
|
+
!path ||
|
|
80
|
+
!(status === 'released' || status.startsWith('released/'))) {
|
|
81
|
+
throw new Error('PostPlus public skill catalog has an invalid skill.');
|
|
69
82
|
}
|
|
83
|
+
return {
|
|
84
|
+
localDependencies,
|
|
85
|
+
skillId,
|
|
86
|
+
path,
|
|
87
|
+
};
|
|
88
|
+
});
|
|
89
|
+
if (skills.length === 0) {
|
|
90
|
+
throw new Error('PostPlus public skill catalog is invalid: no released skills were found.');
|
|
70
91
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
92
|
+
return {
|
|
93
|
+
revision,
|
|
94
|
+
skills,
|
|
95
|
+
source,
|
|
96
|
+
};
|
|
75
97
|
}
|
|
@@ -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,6 +140,9 @@ 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
147
|
const [project, global] = await Promise.all([
|
|
108
148
|
listInstalledSkillsForScope(dependencies, []),
|
|
@@ -151,5 +191,7 @@ function normalizeInstalledSkillEntry(value) {
|
|
|
151
191
|
function formatSkillList(skills, limit) {
|
|
152
192
|
const visible = skills.slice(0, limit);
|
|
153
193
|
const rest = skills.length - visible.length;
|
|
154
|
-
return rest > 0
|
|
194
|
+
return rest > 0
|
|
195
|
+
? `${visible.join(', ')} (+${rest} more)`
|
|
196
|
+
: visible.join(', ');
|
|
155
197
|
}
|
package/build/status.js
CHANGED
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,13 +112,14 @@ 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,
|
|
@@ -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.19",
|
|
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",
|