@postplus/cli 0.1.18 → 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 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,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
- return typeof record.label === 'string'
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 { refreshUpdateCheckBaseline } from './update-check.js';
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 refreshUpdateCheckBaseline().catch(() => { });
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
+ }
@@ -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
+ }
@@ -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
- export async function loadPublicSkillCatalog() {
16
- const response = await fetch(POSTPLUS_SKILLS_INDEX_URL, {
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: 'text/markdown,text/plain',
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 indexText = await response.text();
26
- const skills = parseSkillIndex(indexText);
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
- source: POSTPLUS_SKILLS_REPO,
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 parseSkillIndex(indexText) {
39
- const skills = [];
40
- let inReleasedSkills = false;
41
- let sawReleasedSkillsSection = false;
42
- let currentSkill = null;
43
- for (const line of indexText.split('\n')) {
44
- if (line.trim() === '## Released Skills') {
45
- inReleasedSkills = true;
46
- sawReleasedSkillsSection = true;
47
- continue;
48
- }
49
- if (!inReleasedSkills) {
50
- continue;
51
- }
52
- const skillMatch = line.match(/^- `([^`]+)`\s*$/);
53
- if (skillMatch) {
54
- currentSkill = skillMatch[1] ?? null;
55
- if (currentSkill) {
56
- skills.push({
57
- skillId: currentSkill,
58
- path: null,
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 pathMatch = line.match(/^\s+- Path: `([^`]+)`\s*$/);
64
- if (pathMatch && currentSkill) {
65
- const last = skills.at(-1);
66
- if (last?.skillId === currentSkill) {
67
- last.path = pathMatch[1] ?? null;
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
- if (!sawReleasedSkillsSection) {
72
- throw new Error('PostPlus public skill catalog is invalid: missing ## Released Skills section.');
73
- }
74
- return skills;
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
- return runInteractiveCommand('npx', buildPostPlusSkillUpdateArgs(skillNames));
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
- if (skillNames.length === 0) {
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
- return runInteractiveCommand('npx', buildPostPlusSkillUninstallArgs(skillNames));
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 ? `${visible.join(', ')} (+${rest} more)` : visible.join(', ');
194
+ return rest > 0
195
+ ? `${visible.join(', ')} (+${rest} more)`
196
+ : visible.join(', ');
155
197
  }
package/build/status.js CHANGED
@@ -17,6 +17,7 @@ export async function generateStatusReportWithDependencies(dependencies = {}) {
17
17
  generateUpdateStatus(),
18
18
  ]);
19
19
  return {
20
+ schemaVersion: 1,
20
21
  ok: doctor.ok && auth.ok && skills.ok && updates.ok,
21
22
  doctor,
22
23
  auth,
@@ -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
- previousSkillRevision: cache.skills.latestRevision,
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
- previousSkillRevision: input.resetSkillBaseline
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
- previousSkillRevision: cache.skills.latestRevision,
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: null,
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 refreshUpdateCheckBaseline() {
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) > 0,
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.previousSkillRevision,
120
+ currentRevision: input.currentSkillRevision,
124
121
  latestRevision: input.cache.skills.latestRevision,
125
- updateAvailable: input.cache.skills.latestRevision !== input.previousSkillRevision,
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
- const response = await fetchFn(POSTPLUS_SKILLS_MAIN_URL, {
159
- headers: {
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
- const payload = (await response.json());
169
- if (typeof payload.sha === 'string' && payload.sha.trim()) {
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.18",
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",