@postplus/cli 0.1.22 → 0.1.24

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
@@ -31,7 +31,7 @@ PostPlus has three public surfaces that work together:
31
31
  Requires Node.js and npm.
32
32
 
33
33
  ```bash
34
- npm install -g @postplus/cli
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
37
  ```
@@ -1,6 +1,6 @@
1
1
  import { refreshRemoteAuthSession } from './auth-session.js';
2
2
  import { clearAuthState, generateAuthStatusReport } from './auth.js';
3
- import { buildPostPlusClientCompatibilityHeaders, formatPostPlusClientUpgradeError, } from './client-compatibility.js';
3
+ import { buildPostPlusClientCompatibilityHeaders, formatPostPlusCompatibilityError, } from './client-compatibility.js';
4
4
  import { requireHostedBaseUrl } from './hosted-release.js';
5
5
  import { resolveCliSessionTokenState } from './local-state.js';
6
6
  export async function refreshRemoteAuth() {
@@ -47,9 +47,9 @@ export async function revokeRemoteAuth() {
47
47
  });
48
48
  const payload = (await response.json());
49
49
  if (!response.ok) {
50
- if ('code' in payload &&
51
- payload.code === 'postplus_client_upgrade_required') {
52
- throw new Error(formatPostPlusClientUpgradeError(payload));
50
+ const compatibilityError = formatPostPlusCompatibilityError(payload);
51
+ if (compatibilityError) {
52
+ throw new Error(compatibilityError);
53
53
  }
54
54
  throw new Error('error' in payload && typeof payload.error === 'string'
55
55
  ? payload.error
@@ -1,4 +1,5 @@
1
- import { buildPostPlusClientCompatibilityHeaders, formatPostPlusClientUpgradeError, writeCurrentCliVersionToLocalConfig, } from './client-compatibility.js';
1
+ import { execFileSync } from 'node:child_process';
2
+ import { buildPostPlusClientCompatibilityHeaders, formatPostPlusCompatibilityError, writeCurrentCliVersionToLocalConfig, } from './client-compatibility.js';
2
3
  import { requireHostedBaseUrl } from './hosted-release.js';
3
4
  import { setLocalSession } from './local-state.js';
4
5
  export const CLI_AUTH_LOGIN_TIMEOUT_MS = 30 * 60 * 1000;
@@ -16,6 +17,10 @@ export async function loginWithCloudHandoff() {
16
17
  'Waiting for browser sign-in...',
17
18
  '',
18
19
  ].join('\n'));
20
+ const didOpen = openCloudAuthVerificationUrlIfConfigured(started.verificationUrl);
21
+ if (didOpen) {
22
+ process.stdout.write('Browser opened for sign-in.\n\n');
23
+ }
19
24
  const handoffPayload = await waitForCloudAuthLogin({
20
25
  apiBaseUrl: baseUrl,
21
26
  expiresAt: started.expiresAt,
@@ -63,6 +68,16 @@ export async function startCloudAuthLogin(apiBaseUrl) {
63
68
  }
64
69
  return payload;
65
70
  }
71
+ export function openCloudAuthVerificationUrlIfConfigured(verificationUrl) {
72
+ const command = process.env.POSTPLUS_CLI_AUTH_OPEN_URL_COMMAND?.trim();
73
+ if (!command) {
74
+ return false;
75
+ }
76
+ execFileSync(command, [verificationUrl], {
77
+ stdio: 'ignore',
78
+ });
79
+ return true;
80
+ }
66
81
  async function waitForCloudAuthLogin(input) {
67
82
  const expiresAtMs = Date.parse(input.expiresAt);
68
83
  const deadlineMs = Number.isFinite(expiresAtMs)
@@ -130,8 +145,9 @@ export function formatCliSessionAuthError(payload) {
130
145
  'Once the environment is ready, the CLI will automatically obtain and store its session.',
131
146
  ].join(' ');
132
147
  }
133
- if (payload.code === 'postplus_client_upgrade_required') {
134
- return formatPostPlusClientUpgradeError(payload);
148
+ const compatibilityError = formatPostPlusCompatibilityError(payload);
149
+ if (compatibilityError) {
150
+ return compatibilityError;
135
151
  }
136
152
  if (typeof payload.error === 'string' && payload.error.trim().length > 0) {
137
153
  return payload.error;
@@ -158,8 +174,9 @@ function isCliAuthLoginPendingPayload(payload) {
158
174
  return 'status' in payload && payload.status === 'pending';
159
175
  }
160
176
  function formatRemoteAuthLoginError(payload) {
161
- if ('code' in payload && payload.code === 'postplus_client_upgrade_required') {
162
- return formatPostPlusClientUpgradeError(payload);
177
+ const compatibilityError = formatPostPlusCompatibilityError(payload);
178
+ if (compatibilityError) {
179
+ return compatibilityError;
163
180
  }
164
181
  return 'error' in payload &&
165
182
  typeof payload.error === 'string' &&
@@ -1,6 +1,6 @@
1
- import { buildPostPlusClientCompatibilityHeaders, formatPostPlusClientUpgradeError, writeCurrentCliVersionToLocalConfig, } from './client-compatibility.js';
1
+ import { buildPostPlusClientCompatibilityHeaders, formatPostPlusCompatibilityError, writeCurrentCliVersionToLocalConfig, } from './client-compatibility.js';
2
2
  import { requireHostedBaseUrl } from './hosted-release.js';
3
- import { resolveCliSessionTokenState, setLocalSession, } from './local-state.js';
3
+ import { resolveCliSessionTokenState, setLocalSession } from './local-state.js';
4
4
  export async function resolveFreshRemoteAuth(options = {}) {
5
5
  const [apiBaseUrl, cliSessionTokenState] = await Promise.all([
6
6
  requireHostedBaseUrl(),
@@ -53,9 +53,9 @@ export async function refreshRemoteAuthSession(input) {
53
53
  });
54
54
  const payload = (await response.json());
55
55
  if (!response.ok) {
56
- if ('code' in payload &&
57
- payload.code === 'postplus_client_upgrade_required') {
58
- throw new Error(formatPostPlusClientUpgradeError(payload));
56
+ const compatibilityError = formatPostPlusCompatibilityError(payload);
57
+ if (compatibilityError) {
58
+ throw new Error(compatibilityError);
59
59
  }
60
60
  throw new Error('error' in payload && typeof payload.error === 'string'
61
61
  ? payload.error
@@ -83,7 +83,8 @@ function isRemoteAuthRefreshSuccessPayload(payload) {
83
83
  payload !== null &&
84
84
  typeof payload.cliSessionToken ===
85
85
  'string' &&
86
- payload.cliSessionToken.trim().length > 0 &&
86
+ payload.cliSessionToken.trim().length >
87
+ 0 &&
87
88
  typeof payload.accountId === 'string' &&
88
89
  typeof payload.userId === 'string');
89
90
  }
@@ -1,5 +1,5 @@
1
1
  import { resolveFreshRemoteAuth } from './auth-session.js';
2
- import { buildPostPlusClientCompatibilityHeaders, formatPostPlusClientUpgradeError, } from './client-compatibility.js';
2
+ import { buildPostPlusClientCompatibilityHeaders, formatPostPlusCompatibilityError, } from './client-compatibility.js';
3
3
  export async function validateRemoteAuth() {
4
4
  let auth = await resolveFreshRemoteAuth();
5
5
  let response = await fetchWhoami(auth);
@@ -11,9 +11,9 @@ export async function validateRemoteAuth() {
11
11
  }
12
12
  const payload = (await response.json());
13
13
  if (!response.ok) {
14
- if ('code' in payload &&
15
- payload.code === 'postplus_client_upgrade_required') {
16
- throw new Error(formatPostPlusClientUpgradeError(payload));
14
+ const compatibilityError = formatPostPlusCompatibilityError(payload);
15
+ if (compatibilityError) {
16
+ throw new Error(compatibilityError);
17
17
  }
18
18
  throw new Error('error' in payload && typeof payload.error === 'string'
19
19
  ? payload.error
@@ -6,7 +6,7 @@ export const POSTPLUS_CLIENT_COMPATIBILITY_HEADERS = {
6
6
  cliVersion: 'x-postplus-cli-version',
7
7
  contractVersion: 'x-postplus-client-contract-version',
8
8
  runtime: 'x-postplus-client-runtime',
9
- skillCatalogRevision: 'x-postplus-skill-catalog-revision',
9
+ skillsReleaseId: 'x-postplus-skills-release-id',
10
10
  skillName: 'x-postplus-skill-name',
11
11
  };
12
12
  export async function buildPostPlusClientCompatibilityHeaders(input = {}) {
@@ -19,11 +19,11 @@ export async function buildPostPlusClientCompatibilityHeaders(input = {}) {
19
19
  [POSTPLUS_CLIENT_COMPATIBILITY_HEADERS.contractVersion]: String(POSTPLUS_CLIENT_CONTRACT_VERSION),
20
20
  [POSTPLUS_CLIENT_COMPATIBILITY_HEADERS.runtime]: POSTPLUS_CLIENT_RUNTIME,
21
21
  };
22
- const skillCatalogRevision = config?.managedSkills?.revision?.trim();
22
+ const skillsReleaseId = config?.managedSkills?.releaseId?.trim();
23
23
  const skillName = input.skillName?.trim();
24
- if (skillCatalogRevision) {
25
- headers[POSTPLUS_CLIENT_COMPATIBILITY_HEADERS.skillCatalogRevision] =
26
- skillCatalogRevision;
24
+ if (skillsReleaseId) {
25
+ headers[POSTPLUS_CLIENT_COMPATIBILITY_HEADERS.skillsReleaseId] =
26
+ skillsReleaseId;
27
27
  }
28
28
  if (skillName) {
29
29
  headers[POSTPLUS_CLIENT_COMPATIBILITY_HEADERS.skillName] = skillName;
@@ -51,7 +51,7 @@ export function formatPostPlusClientUpgradeError(payload) {
51
51
  ? payload
52
52
  : {};
53
53
  const cliCommand = record.compatibility?.upgrade?.cli?.command ??
54
- 'npm install -g @postplus/cli';
54
+ 'npm install -g @postplus/cli@latest';
55
55
  const skillsCommand = record.compatibility?.upgrade?.skills?.command ?? 'postplus update';
56
56
  const restart = record.compatibility?.upgrade?.restartAgentSession
57
57
  ? ' Then restart your agent session.'
@@ -67,3 +67,34 @@ export function formatPostPlusClientUpgradeError(payload) {
67
67
  .filter(Boolean)
68
68
  .join(' ');
69
69
  }
70
+ export function formatPostPlusCloudReleaseInProgressError(payload) {
71
+ const record = payload && typeof payload === 'object' && !Array.isArray(payload)
72
+ ? payload
73
+ : {};
74
+ return typeof record.error === 'string' && record.error.trim().length > 0
75
+ ? record.error.trim()
76
+ : 'PostPlus Cloud is updating. Please retry in about one minute.';
77
+ }
78
+ export function formatPostPlusCompatibilityError(payload) {
79
+ if (isPostPlusClientUpgradePayload(payload)) {
80
+ return formatPostPlusClientUpgradeError(payload);
81
+ }
82
+ if (isPostPlusCloudReleaseInProgressPayload(payload)) {
83
+ return formatPostPlusCloudReleaseInProgressError(payload);
84
+ }
85
+ return null;
86
+ }
87
+ export function isPostPlusClientUpgradePayload(payload) {
88
+ return (payload &&
89
+ typeof payload === 'object' &&
90
+ !Array.isArray(payload) &&
91
+ 'code' in payload &&
92
+ payload.code === 'postplus_client_upgrade_required');
93
+ }
94
+ export function isPostPlusCloudReleaseInProgressPayload(payload) {
95
+ return (payload &&
96
+ typeof payload === 'object' &&
97
+ !Array.isArray(payload) &&
98
+ 'code' in payload &&
99
+ payload.code === 'postplus_cli_cloud_release_in_progress');
100
+ }
package/build/doctor.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { resolveFreshRemoteAuth, } from './auth-session.js';
2
- import { buildPostPlusClientCompatibilityHeaders, formatPostPlusClientUpgradeError, } from './client-compatibility.js';
2
+ import { buildPostPlusClientCompatibilityHeaders, formatPostPlusCompatibilityError, } from './client-compatibility.js';
3
3
  import { resolveHostedBaseUrl } from './hosted-release.js';
4
4
  import { formatLocalDependencyReport, generateLocalDependencyReport, } from './local-dependencies.js';
5
5
  function createPass(id, label, detail, severity = 'required') {
@@ -185,8 +185,9 @@ function readReadinessCheckFailureLabel(value) {
185
185
  : 'unknown check';
186
186
  }
187
187
  function readErrorMessage(payload, fallback) {
188
- if (payload.code === 'postplus_client_upgrade_required') {
189
- return formatPostPlusClientUpgradeError(payload);
188
+ const compatibilityError = formatPostPlusCompatibilityError(payload);
189
+ if (compatibilityError) {
190
+ return compatibilityError;
190
191
  }
191
192
  return typeof payload.error === 'string' && payload.error.trim().length > 0
192
193
  ? payload.error
package/build/index.js CHANGED
@@ -3,13 +3,13 @@ import { formatAuthRefreshReport, refreshRemoteAuth, revokeRemoteAuthAndReport,
3
3
  import { loginWithCloudHandoff } from './auth-login.js';
4
4
  import { formatAuthValidateReport, validateRemoteAuth, } from './auth-validate.js';
5
5
  import { clearAuthState, formatAuthStatusReport, generateAuthStatusReport, } from './auth.js';
6
+ import { readCurrentCliVersion } from './client-compatibility.js';
6
7
  import { formatDoctorReport, generateDoctorReport } from './doctor.js';
7
8
  import { assertConfigFilePermissions } from './local-state.js';
8
9
  import { POSTPLUS_SKILLS_INSTALL_COMMAND, loadPublicSkillCatalog, } from './skill-catalog.js';
9
10
  import { runPostPlusSkillUninstall, runPostPlusSkillUpdate, } from './skill-management.js';
10
11
  import { formatStatusReport, generateStatusReport } from './status.js';
11
- import { readCurrentCliVersion, } from './client-compatibility.js';
12
- import { refreshUpdateCheckCache } from './update-check.js';
12
+ import { refreshUpdateCheckCache, runCliSelfUpdateIfOutdated, } from './update-check.js';
13
13
  function printAuthHelp() {
14
14
  process.stdout.write(`PostPlus CLI — auth commands
15
15
 
@@ -103,6 +103,10 @@ async function runVersion() {
103
103
  return 0;
104
104
  }
105
105
  async function runSkillUpdateCommand() {
106
+ const cliSelfUpdate = await runCliSelfUpdateIfOutdated();
107
+ if (cliSelfUpdate.updateAvailable) {
108
+ return cliSelfUpdate.exitCode ?? 1;
109
+ }
106
110
  const exitCode = await runPostPlusSkillUpdate();
107
111
  if (exitCode === 0) {
108
112
  await refreshUpdateCheckCache().catch(() => { });
@@ -9,7 +9,7 @@ export async function generateLocalDependencyReport(options = {}) {
9
9
  const checks = await Promise.all(requirements.map(({ dependency, skillIds }) => checkLocalDependency(dependency, skillIds, runDependencyCheck)));
10
10
  return {
11
11
  ok: checks.every((check) => check.ok),
12
- revision: catalog.revision,
12
+ releaseId: catalog.releaseId,
13
13
  source: catalog.source,
14
14
  requiredCount: checks.length,
15
15
  checks,
@@ -17,11 +17,11 @@ export async function generateLocalDependencyReport(options = {}) {
17
17
  }
18
18
  export function formatLocalDependencyReport(report) {
19
19
  if (report.requiredCount === 0) {
20
- return `No local runtime dependencies are required by released PostPlus skills (${report.revision}).`;
20
+ return `No local runtime dependencies are required by released PostPlus skills (${report.releaseId}).`;
21
21
  }
22
22
  const missing = report.checks.filter((check) => !check.ok);
23
23
  if (missing.length === 0) {
24
- return `Ready (${report.requiredCount} local dependencies present; catalog ${report.revision})`;
24
+ return `Ready (${report.requiredCount} local dependencies present; catalog ${report.releaseId})`;
25
25
  }
26
26
  return `Missing ${missing.length}/${report.requiredCount}: ${missing
27
27
  .map((check) => `${check.dependency} for ${formatSkillList(check.skillIds)}`)
@@ -108,15 +108,15 @@ export async function readManagedSkillBaseline() {
108
108
  const config = await readLocalConfig();
109
109
  const managedSkills = config?.managedSkills;
110
110
  if (!managedSkills ||
111
- typeof managedSkills.revision !== 'string' ||
111
+ typeof managedSkills.releaseId !== 'string' ||
112
112
  !Array.isArray(managedSkills.skillNames)) {
113
113
  return {
114
- revision: null,
114
+ releaseId: null,
115
115
  skillNames: [],
116
116
  };
117
117
  }
118
118
  return {
119
- revision: managedSkills.revision,
119
+ releaseId: managedSkills.releaseId,
120
120
  skillNames: normalizeSkillNames(managedSkills.skillNames),
121
121
  };
122
122
  }
@@ -124,7 +124,7 @@ export async function writeManagedSkillBaseline(input) {
124
124
  return updateLocalConfig((current) => ({
125
125
  ...(current ?? {}),
126
126
  managedSkills: {
127
- revision: input.revision,
127
+ releaseId: input.releaseId,
128
128
  skillNames: normalizeSkillNames(input.skillNames),
129
129
  updatedAt: new Date().toISOString(),
130
130
  },
@@ -1,4 +1,6 @@
1
1
  export const POSTPLUS_SKILLS_REPO = 'PostPlusAI/postplus-skills';
2
+ export const POSTPLUS_SKILLS_SOURCE_ENV = 'POSTPLUS_SKILLS_SOURCE';
3
+ export const POSTPLUS_SKILLS_CATALOG_URL_ENV = 'POSTPLUS_SKILLS_CATALOG_URL';
2
4
  export const POSTPLUS_SKILLS_AGENT_TARGETS = [
3
5
  'claude-code',
4
6
  'codex',
@@ -9,12 +11,14 @@ export const POSTPLUS_SKILLS_AGENT_TARGETS = [
9
11
  'trae-cn',
10
12
  ];
11
13
  const POSTPLUS_SKILLS_AGENT_ARGS = POSTPLUS_SKILLS_AGENT_TARGETS.join(' ');
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
- export const POSTPLUS_SKILLS_LIST_COMMAND = 'npx -y skills add PostPlusAI/postplus-skills --list --full-depth';
14
+ export const POSTPLUS_SKILLS_INSTALL_COMMAND = formatPostPlusSkillsInstallCommand();
15
+ export const POSTPLUS_SKILLS_LIST_COMMAND = formatPostPlusSkillsListCommand();
14
16
  const POSTPLUS_SKILLS_INDEX_URL = 'https://raw.githubusercontent.com/PostPlusAI/postplus-skills/main/skills/INDEX.md';
15
17
  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, {
18
+ export async function loadPublicSkillCatalog(fetchFn = fetch, env = process.env) {
19
+ const catalogUrl = resolvePostPlusSkillsCatalogUrl(env);
20
+ const skillsSource = resolvePostPlusSkillsSource(env);
21
+ const response = await fetchFn(catalogUrl, {
18
22
  headers: {
19
23
  accept: 'application/json',
20
24
  },
@@ -24,16 +28,29 @@ export async function loadPublicSkillCatalog(fetchFn = fetch) {
24
28
  throw new Error(`Failed to load PostPlus skill catalog (${response.status}): ${response.statusText}`);
25
29
  }
26
30
  const raw = await response.text();
27
- const payload = parseJsonResponse(raw, POSTPLUS_SKILLS_CATALOG_URL);
31
+ const payload = parseJsonResponse(raw, catalogUrl);
28
32
  const catalog = parsePublicSkillCatalog(payload);
29
33
  return {
30
34
  ...catalog,
31
- catalogUrl: POSTPLUS_SKILLS_CATALOG_URL,
35
+ catalogUrl,
32
36
  indexUrl: POSTPLUS_SKILLS_INDEX_URL,
33
- installCommand: POSTPLUS_SKILLS_INSTALL_COMMAND,
34
- listCommand: POSTPLUS_SKILLS_LIST_COMMAND,
37
+ installCommand: formatPostPlusSkillsInstallCommand(skillsSource),
38
+ listCommand: formatPostPlusSkillsListCommand(skillsSource),
39
+ source: skillsSource,
35
40
  };
36
41
  }
42
+ export function resolvePostPlusSkillsSource(env = process.env) {
43
+ return env[POSTPLUS_SKILLS_SOURCE_ENV]?.trim() || POSTPLUS_SKILLS_REPO;
44
+ }
45
+ export function resolvePostPlusSkillsCatalogUrl(env = process.env) {
46
+ return env[POSTPLUS_SKILLS_CATALOG_URL_ENV]?.trim() || POSTPLUS_SKILLS_CATALOG_URL;
47
+ }
48
+ export function formatPostPlusSkillsInstallCommand(source = POSTPLUS_SKILLS_REPO) {
49
+ return `npx -y skills add ${source} --global --full-depth --skill '*' --agent ${POSTPLUS_SKILLS_AGENT_ARGS} --yes`;
50
+ }
51
+ export function formatPostPlusSkillsListCommand(source = POSTPLUS_SKILLS_REPO) {
52
+ return `npx -y skills add ${source} --list --full-depth`;
53
+ }
37
54
  function parseJsonResponse(raw, url) {
38
55
  try {
39
56
  return JSON.parse(raw);
@@ -53,15 +70,15 @@ function parsePublicSkillCatalog(payload) {
53
70
  throw new Error('PostPlus public skill catalog is invalid.');
54
71
  }
55
72
  const record = payload;
56
- const revision = typeof record.revision === 'string' && record.revision.trim()
57
- ? record.revision.trim()
73
+ const releaseId = typeof record.releaseId === 'string' && record.releaseId.trim()
74
+ ? record.releaseId.trim()
58
75
  : null;
59
76
  const source = typeof record.source === 'string' && record.source.trim()
60
77
  ? record.source.trim()
61
78
  : null;
62
79
  if (record.schemaVersion !== 1 ||
63
80
  source !== POSTPLUS_SKILLS_REPO ||
64
- !revision) {
81
+ !releaseId) {
65
82
  throw new Error('PostPlus public skill catalog metadata is invalid.');
66
83
  }
67
84
  if (!Array.isArray(record.skills)) {
@@ -105,7 +122,7 @@ function parsePublicSkillCatalog(payload) {
105
122
  throw new Error('PostPlus public skill catalog is invalid: no released skills were found.');
106
123
  }
107
124
  return {
108
- revision,
125
+ releaseId,
109
126
  skills,
110
127
  source,
111
128
  };
@@ -1,7 +1,7 @@
1
- import { runCommand, runInteractiveCommand } from './command-runner.js';
2
1
  import { writeCurrentCliVersionToLocalConfig } from './client-compatibility.js';
2
+ import { runCommand, runInteractiveCommand } from './command-runner.js';
3
3
  import { clearManagedSkillBaseline, readManagedSkillBaseline, writeManagedSkillBaseline, } from './local-state.js';
4
- import { POSTPLUS_SKILLS_AGENT_TARGETS, POSTPLUS_SKILLS_INSTALL_COMMAND, POSTPLUS_SKILLS_REPO, loadPublicSkillCatalog, } from './skill-catalog.js';
4
+ import { POSTPLUS_SKILLS_AGENT_TARGETS, formatPostPlusSkillsInstallCommand, resolvePostPlusSkillsSource, loadPublicSkillCatalog, } from './skill-catalog.js';
5
5
  const NPX_SKILLS = ['-y', 'skills'];
6
6
  export async function runPostPlusSkillUpdate(dependencies = {
7
7
  runInteractiveCommand,
@@ -24,7 +24,7 @@ export async function runPostPlusSkillUpdate(dependencies = {
24
24
  }
25
25
  }
26
26
  await writeManagedSkillBaseline({
27
- revision: catalog.revision,
27
+ releaseId: catalog.releaseId,
28
28
  skillNames,
29
29
  });
30
30
  await writeCurrentCliVersionToLocalConfig();
@@ -66,14 +66,14 @@ export async function generateSkillInstallStatusReport(dependencies = {
66
66
  return {
67
67
  ok: missingSkills.length === 0,
68
68
  error: null,
69
- installCommand: POSTPLUS_SKILLS_INSTALL_COMMAND,
69
+ installCommand: formatPostPlusSkillsInstallCommand(catalog.source),
70
70
  installedCount: installedNames.size,
71
- managedRevision: baseline.revision,
71
+ managedSkillsReleaseId: baseline.releaseId,
72
72
  missingSkills,
73
73
  requiredCount: requiredSkills.size,
74
74
  retiredManagedSkills,
75
75
  scopes,
76
- source: POSTPLUS_SKILLS_REPO,
76
+ source: catalog.source,
77
77
  updateCommand: formatPostPlusSkillUpdateCommand(),
78
78
  uninstallCommand: formatPostPlusSkillUninstallCommand(),
79
79
  };
@@ -84,14 +84,14 @@ export async function generateSkillInstallStatusReport(dependencies = {
84
84
  error: error instanceof Error
85
85
  ? error.message
86
86
  : 'Failed to inspect installed PostPlus skills.',
87
- installCommand: POSTPLUS_SKILLS_INSTALL_COMMAND,
87
+ installCommand: formatPostPlusSkillsInstallCommand(catalog.source),
88
88
  installedCount: 0,
89
- managedRevision: baseline.revision,
89
+ managedSkillsReleaseId: baseline.releaseId,
90
90
  missingSkills: [...requiredSkills],
91
91
  requiredCount: requiredSkills.size,
92
92
  retiredManagedSkills,
93
93
  scopes: [],
94
- source: POSTPLUS_SKILLS_REPO,
94
+ source: catalog.source,
95
95
  updateCommand: formatPostPlusSkillUpdateCommand(),
96
96
  uninstallCommand: formatPostPlusSkillUninstallCommand(),
97
97
  };
@@ -109,7 +109,7 @@ export function formatSkillInstallStatusReport(report) {
109
109
  lines.push(`[FAIL] Installed released skills: ${report.installedCount}/${report.requiredCount}`);
110
110
  }
111
111
  lines.push(` Source: ${report.source}`);
112
- lines.push(` Managed baseline: ${report.managedRevision ?? 'none'}`);
112
+ lines.push(` Managed baseline: ${report.managedSkillsReleaseId ?? 'none'}`);
113
113
  lines.push(` Scope: ${report.scopes.length > 0 ? report.scopes.join(', ') : 'none detected'}`);
114
114
  if (report.retiredManagedSkills.length > 0) {
115
115
  lines.push(` Retired managed skills: ${formatSkillList(report.retiredManagedSkills, 8)}`, ` Cleanup: ${report.updateCommand}`);
@@ -123,7 +123,22 @@ export function formatSkillInstallStatusReport(report) {
123
123
  return lines.join('\n');
124
124
  }
125
125
  export function buildPostPlusSkillUpdateArgs(skillNames) {
126
- return [...NPX_SKILLS, 'update', ...skillNames, '--global', '--yes'];
126
+ if (skillNames.length === 0) {
127
+ throw new Error('PostPlus public skill catalog has no released skills.');
128
+ }
129
+ const skillsSource = resolvePostPlusSkillsSource();
130
+ return [
131
+ ...NPX_SKILLS,
132
+ 'add',
133
+ skillsSource,
134
+ '--global',
135
+ '--full-depth',
136
+ '--skill',
137
+ '*',
138
+ '--agent',
139
+ ...POSTPLUS_SKILLS_AGENT_TARGETS,
140
+ '--yes',
141
+ ];
127
142
  }
128
143
  export function buildPostPlusSkillUninstallArgs(skillNames) {
129
144
  return [
@@ -1,12 +1,15 @@
1
1
  import { mkdir, readFile, writeFile } from 'node:fs/promises';
2
2
  import { dirname, join } from 'node:path';
3
3
  import { readCurrentCliVersion } from './client-compatibility.js';
4
+ import { runInteractiveCommand as runDefaultInteractiveCommand, } from './command-runner.js';
4
5
  import { getPostPlusConfigDir, readManagedSkillBaseline, } from './local-state.js';
5
6
  import { POSTPLUS_SKILLS_REPO, loadPublicSkillCatalog, } from './skill-catalog.js';
6
7
  const UPDATE_CHECK_TTL_MS = 24 * 60 * 60 * 1000;
7
8
  const UPDATE_CHECK_CACHE_FILE = 'update-check.json';
8
9
  const NPM_PACKAGE_NAME = '@postplus/cli';
9
10
  const NPM_LATEST_URL = `https://registry.npmjs.org/${encodeURIComponent(NPM_PACKAGE_NAME)}/latest`;
11
+ export const POSTPLUS_CLI_UPDATE_COMMAND = 'npm install -g @postplus/cli@latest';
12
+ const POSTPLUS_CLI_UPDATE_ARGS = ['install', '-g', '@postplus/cli@latest'];
10
13
  export async function generateUpdateStatusReport(input = {}, dependencies = {
11
14
  fetchFn: fetch,
12
15
  }) {
@@ -20,14 +23,14 @@ export async function generateUpdateStatusReport(input = {}, dependencies = {
20
23
  return buildUpdateReport({
21
24
  cache,
22
25
  currentVersion,
23
- currentSkillRevision: managedSkillBaseline.revision,
26
+ currentSkillsReleaseId: managedSkillBaseline.releaseId,
24
27
  source: 'cache',
25
28
  });
26
29
  }
27
30
  try {
28
- const [latestCliVersion, latestSkillRevision] = await Promise.all([
31
+ const [latestCliVersion, latestSkillsReleaseId] = await Promise.all([
29
32
  fetchLatestCliVersion(dependencies.fetchFn),
30
- fetchLatestSkillRevision(dependencies.fetchFn),
33
+ fetchLatestSkillReleaseId(dependencies.fetchFn),
31
34
  ]);
32
35
  const nextCache = {
33
36
  checkedAt: new Date().toISOString(),
@@ -36,14 +39,14 @@ export async function generateUpdateStatusReport(input = {}, dependencies = {
36
39
  latestVersion: latestCliVersion,
37
40
  },
38
41
  skills: {
39
- latestRevision: latestSkillRevision,
42
+ latestReleaseId: latestSkillsReleaseId,
40
43
  },
41
44
  };
42
45
  await writeUpdateCheckCache(nextCache);
43
46
  return buildUpdateReport({
44
47
  cache: nextCache,
45
48
  currentVersion,
46
- currentSkillRevision: managedSkillBaseline.revision,
49
+ currentSkillsReleaseId: managedSkillBaseline.releaseId,
47
50
  source: 'remote',
48
51
  });
49
52
  }
@@ -54,7 +57,7 @@ export async function generateUpdateStatusReport(input = {}, dependencies = {
54
57
  ...buildUpdateReport({
55
58
  cache,
56
59
  currentVersion,
57
- currentSkillRevision: managedSkillBaseline.revision,
60
+ currentSkillsReleaseId: managedSkillBaseline.releaseId,
58
61
  source: 'cache',
59
62
  }),
60
63
  warning,
@@ -68,11 +71,11 @@ export async function generateUpdateStatusReport(input = {}, dependencies = {
68
71
  currentVersion,
69
72
  latestVersion: null,
70
73
  updateAvailable: false,
71
- updateCommand: 'npm install -g @postplus/cli',
74
+ updateCommand: POSTPLUS_CLI_UPDATE_COMMAND,
72
75
  },
73
76
  skills: {
74
- currentRevision: managedSkillBaseline.revision,
75
- latestRevision: null,
77
+ currentReleaseId: managedSkillBaseline.releaseId,
78
+ latestReleaseId: null,
76
79
  updateAvailable: false,
77
80
  updateCommand: 'postplus update',
78
81
  },
@@ -85,6 +88,49 @@ export async function refreshUpdateCheckCache() {
85
88
  force: true,
86
89
  });
87
90
  }
91
+ export async function runCliSelfUpdateIfOutdated(dependencies = {}) {
92
+ const fetchFn = dependencies.fetchFn ?? fetch;
93
+ const runInteractiveCommand = dependencies.runInteractiveCommand ?? runDefaultInteractiveCommand;
94
+ const writeOutput = dependencies.writeOutput ?? ((message) => process.stdout.write(message));
95
+ const currentVersion = await readCurrentCliVersion();
96
+ const latestVersion = await fetchLatestCliVersion(fetchFn);
97
+ if (compareVersions(latestVersion, currentVersion) <= 0) {
98
+ return {
99
+ command: POSTPLUS_CLI_UPDATE_COMMAND,
100
+ currentVersion,
101
+ exitCode: null,
102
+ latestVersion,
103
+ updateAvailable: false,
104
+ };
105
+ }
106
+ writeOutput([
107
+ `PostPlus CLI ${currentVersion} is older than latest ${latestVersion}.`,
108
+ `Updating CLI: ${POSTPLUS_CLI_UPDATE_COMMAND}`,
109
+ '',
110
+ ].join('\n'));
111
+ const exitCode = await runInteractiveCommand('npm', POSTPLUS_CLI_UPDATE_ARGS);
112
+ if (exitCode === 0) {
113
+ writeOutput([
114
+ `PostPlus CLI updated to ${latestVersion}.`,
115
+ 'Re-run `postplus update` to update skills with the new CLI process.',
116
+ '',
117
+ ].join('\n'));
118
+ }
119
+ else {
120
+ writeOutput([
121
+ `PostPlus CLI update failed with exit code ${exitCode}.`,
122
+ `Fix the npm install error, then run: ${POSTPLUS_CLI_UPDATE_COMMAND}`,
123
+ '',
124
+ ].join('\n'));
125
+ }
126
+ return {
127
+ command: POSTPLUS_CLI_UPDATE_COMMAND,
128
+ currentVersion,
129
+ exitCode,
130
+ latestVersion,
131
+ updateAvailable: true,
132
+ };
133
+ }
88
134
  export function formatUpdateStatusReport(report) {
89
135
  const lines = ['PostPlus update status', ''];
90
136
  const cliMarker = report.cli.updateAvailable ? '[WARN]' : '[PASS]';
@@ -93,8 +139,8 @@ export function formatUpdateStatusReport(report) {
93
139
  lines.push(` Update: ${report.cli.updateCommand}`);
94
140
  }
95
141
  const skillMarker = report.skills.updateAvailable ? '[WARN]' : '[PASS]';
96
- lines.push(`${skillMarker} Skills: ${report.skills.latestRevision
97
- ? `release ${shortRevision(report.skills.latestRevision)}`
142
+ lines.push(`${skillMarker} Skills: ${report.skills.latestReleaseId
143
+ ? `release ${shortReleaseId(report.skills.latestReleaseId)}`
98
144
  : 'release unknown'}`);
99
145
  if (report.skills.updateAvailable) {
100
146
  lines.push(` Update: ${report.skills.updateCommand}`);
@@ -115,12 +161,12 @@ function buildUpdateReport(input) {
115
161
  latestVersion: input.cache.cli.latestVersion,
116
162
  updateAvailable: compareVersions(input.cache.cli.latestVersion, input.currentVersion) >
117
163
  0,
118
- updateCommand: 'npm install -g @postplus/cli',
164
+ updateCommand: POSTPLUS_CLI_UPDATE_COMMAND,
119
165
  },
120
166
  skills: {
121
- currentRevision: input.currentSkillRevision,
122
- latestRevision: input.cache.skills.latestRevision,
123
- updateAvailable: input.cache.skills.latestRevision !== input.currentSkillRevision,
167
+ currentReleaseId: input.currentSkillsReleaseId,
168
+ latestReleaseId: input.cache.skills.latestReleaseId,
169
+ updateAvailable: input.cache.skills.latestReleaseId !== input.currentSkillsReleaseId,
124
170
  updateCommand: 'postplus update',
125
171
  },
126
172
  warning: null,
@@ -143,12 +189,12 @@ async function fetchLatestCliVersion(fetchFn) {
143
189
  }
144
190
  return payload.version.trim();
145
191
  }
146
- async function fetchLatestSkillRevision(fetchFn) {
192
+ async function fetchLatestSkillReleaseId(fetchFn) {
147
193
  try {
148
- return (await loadPublicSkillCatalog(fetchFn)).revision;
194
+ return (await loadPublicSkillCatalog(fetchFn)).releaseId;
149
195
  }
150
196
  catch (error) {
151
- throw new Error(`Failed to check latest ${POSTPLUS_SKILLS_REPO} revision: ${error instanceof Error ? error.message : String(error)}`);
197
+ throw new Error(`Failed to check latest ${POSTPLUS_SKILLS_REPO} releaseId: ${error instanceof Error ? error.message : String(error)}`);
152
198
  }
153
199
  }
154
200
  async function readUpdateCheckCache() {
@@ -158,7 +204,7 @@ async function readUpdateCheckCache() {
158
204
  if (typeof parsed.checkedAt !== 'string' ||
159
205
  typeof parsed.cli?.currentVersion !== 'string' ||
160
206
  typeof parsed.cli?.latestVersion !== 'string' ||
161
- typeof parsed.skills?.latestRevision !== 'string') {
207
+ typeof parsed.skills?.latestReleaseId !== 'string') {
162
208
  return null;
163
209
  }
164
210
  return parsed;
@@ -202,6 +248,6 @@ function parseVersion(value) {
202
248
  .map((part) => Number.parseInt(part, 10))
203
249
  .map((part) => (Number.isFinite(part) ? part : 0));
204
250
  }
205
- function shortRevision(revision) {
206
- return revision.length > 12 ? revision.slice(0, 12) : revision;
251
+ function shortReleaseId(releaseId) {
252
+ return releaseId.length > 12 ? releaseId.slice(0, 12) : releaseId;
207
253
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@postplus/cli",
3
- "version": "0.1.22",
3
+ "version": "0.1.24",
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.",