@postplus/cli 0.1.12 → 0.1.13

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
@@ -23,7 +23,7 @@ The agent then routes the work, collects evidence, makes the judgment explicit,
23
23
  PostPlus has three public surfaces that work together:
24
24
 
25
25
  - `https://postplus.io/`: the hosted product surface for account access, subscription state, and cloud-backed capabilities.
26
- - `https://github.com/RealProductStudio/postplus-skills`: the public skill repository that installs local marketing workflows into agent tools.
26
+ - `https://github.com/PostPlusAI/postplus-skills`: the public skill repository that installs local marketing workflows into agent tools.
27
27
  - `https://github.com/PostPlusAI/postplus-cli`: the local command-line tool that signs you in, checks local readiness, and connects released skills to PostPlus account state.
28
28
 
29
29
  ## Install
@@ -39,8 +39,7 @@ npx -y skills add PostPlusAI/postplus-skills --full-depth --skill '*' --agent cl
39
39
  Useful checks:
40
40
 
41
41
  ```bash
42
- postplus auth status
43
- postplus doctor
42
+ postplus status
44
43
  npx -y skills add PostPlusAI/postplus-skills --list --full-depth
45
44
  ```
46
45
 
@@ -393,4 +392,3 @@ The best first prompt includes:
393
392
  - the target platform if you already know it
394
393
  - any reference links, files, accounts, or assets
395
394
  - the artifact you want at the end
396
-
@@ -0,0 +1,60 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { mkdtemp, open, readFile, rm } from 'node:fs/promises';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ export async function runCommand(command, args, options = {}) {
6
+ const tempDir = await mkdtemp(join(tmpdir(), 'postplus-cli-command-'));
7
+ const stdoutPath = join(tempDir, 'stdout.txt');
8
+ const stdoutFile = await open(stdoutPath, 'w');
9
+ try {
10
+ const result = await new Promise((resolve, reject) => {
11
+ const child = spawn(command, args, {
12
+ stdio: ['ignore', stdoutFile.fd, 'pipe'],
13
+ });
14
+ const stderr = [];
15
+ const timer = setTimeout(() => {
16
+ child.kill('SIGTERM');
17
+ reject(new Error(`Command timed out: ${command} ${args.join(' ')}`));
18
+ }, options.timeoutMs ?? 60_000);
19
+ child.stderr?.on('data', (chunk) => {
20
+ stderr.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
21
+ });
22
+ child.on('error', (error) => {
23
+ clearTimeout(timer);
24
+ reject(error);
25
+ });
26
+ child.on('exit', (code) => {
27
+ clearTimeout(timer);
28
+ const stderrText = Buffer.concat(stderr).toString('utf8');
29
+ if (code === 0) {
30
+ resolve({
31
+ stderr: stderrText,
32
+ stdout: '',
33
+ });
34
+ return;
35
+ }
36
+ reject(new Error(`Command failed (${code ?? 'unknown'}): ${command} ${args.join(' ')}${stderrText ? `\n${stderrText}` : ''}`));
37
+ });
38
+ });
39
+ await stdoutFile.close();
40
+ return {
41
+ ...result,
42
+ stdout: await readFile(stdoutPath, 'utf8'),
43
+ };
44
+ }
45
+ finally {
46
+ await stdoutFile.close().catch(() => { });
47
+ await rm(tempDir, { force: true, recursive: true });
48
+ }
49
+ }
50
+ export async function runInteractiveCommand(command, args) {
51
+ return await new Promise((resolve, reject) => {
52
+ const child = spawn(command, args, {
53
+ stdio: 'inherit',
54
+ });
55
+ child.on('error', reject);
56
+ child.on('exit', (code) => {
57
+ resolve(code ?? 1);
58
+ });
59
+ });
60
+ }
package/build/index.js CHANGED
@@ -6,8 +6,9 @@ import { clearAuthState, formatAuthStatusReport, generateAuthStatusReport, } fro
6
6
  import { formatDoctorReport, generateDoctorReport, } from './doctor.js';
7
7
  import { assertConfigFilePermissions } from './local-state.js';
8
8
  import { POSTPLUS_SKILLS_INSTALL_COMMAND, loadPublicSkillCatalog, } from './skill-catalog.js';
9
+ import { runPostPlusSkillUninstall, runPostPlusSkillUpdate, } from './skill-management.js';
9
10
  import { formatStatusReport, generateStatusReport } from './status.js';
10
- const REMOVED_SKILL_COMMAND_MESSAGE = `PostPlus CLI no longer installs skills directly. Run \`${POSTPLUS_SKILLS_INSTALL_COMMAND}\`.`;
11
+ import { refreshUpdateCheckBaseline } from './update-check.js';
11
12
  function printAuthHelp() {
12
13
  process.stdout.write(`PostPlus CLI — auth commands
13
14
 
@@ -36,6 +37,8 @@ Usage:
36
37
  postplus auth validate [--json]
37
38
  postplus auth logout [--json]
38
39
  postplus doctor [--json]
40
+ postplus update
41
+ postplus uninstall
39
42
  postplus list [--json]
40
43
  postplus status [--json]
41
44
  postplus help
@@ -93,6 +96,16 @@ async function runList(json) {
93
96
  process.stdout.write(`${lines.join('\n')}\n`);
94
97
  return 0;
95
98
  }
99
+ async function runSkillUpdateCommand() {
100
+ const exitCode = await runPostPlusSkillUpdate();
101
+ if (exitCode === 0) {
102
+ await refreshUpdateCheckBaseline().catch(() => { });
103
+ }
104
+ return exitCode;
105
+ }
106
+ async function runSkillUninstallCommand() {
107
+ return runPostPlusSkillUninstall();
108
+ }
96
109
  function writeJson(value) {
97
110
  process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
98
111
  }
@@ -177,10 +190,14 @@ async function main() {
177
190
  process.exitCode = await runDoctor(json);
178
191
  return;
179
192
  case 'install':
193
+ process.stderr.write(`PostPlus CLI does not install skills directly. Run \`${POSTPLUS_SKILLS_INSTALL_COMMAND}\`.\n`);
194
+ process.exitCode = 1;
195
+ return;
180
196
  case 'update':
197
+ process.exitCode = await runSkillUpdateCommand();
198
+ return;
181
199
  case 'uninstall':
182
- process.stderr.write(`${REMOVED_SKILL_COMMAND_MESSAGE}\n`);
183
- process.exitCode = 1;
200
+ process.exitCode = await runSkillUninstallCommand();
184
201
  return;
185
202
  case 'list':
186
203
  process.exitCode = await runList(json);
@@ -0,0 +1,155 @@
1
+ import { POSTPLUS_SKILLS_INSTALL_COMMAND, POSTPLUS_SKILLS_REPO, loadPublicSkillCatalog, } from './skill-catalog.js';
2
+ import { runCommand, runInteractiveCommand } from './command-runner.js';
3
+ const SKILLS_AGENTS = ['claude-code', 'codex', 'cursor'];
4
+ const NPX_SKILLS = ['-y', 'skills'];
5
+ export async function runPostPlusSkillUpdate() {
6
+ const catalog = await loadPublicSkillCatalog();
7
+ const skillNames = catalog.skills.map((skill) => skill.skillId);
8
+ if (skillNames.length === 0) {
9
+ throw new Error('PostPlus public skill catalog has no released skills.');
10
+ }
11
+ return runInteractiveCommand('npx', buildPostPlusSkillUpdateArgs(skillNames));
12
+ }
13
+ export async function runPostPlusSkillUninstall() {
14
+ const catalog = await loadPublicSkillCatalog();
15
+ const skillNames = catalog.skills.map((skill) => skill.skillId);
16
+ if (skillNames.length === 0) {
17
+ throw new Error('PostPlus public skill catalog has no released skills.');
18
+ }
19
+ return runInteractiveCommand('npx', buildPostPlusSkillUninstallArgs(skillNames));
20
+ }
21
+ export async function generateSkillInstallStatusReport(dependencies = {
22
+ runCommand,
23
+ }) {
24
+ const catalog = await loadPublicSkillCatalog();
25
+ const requiredSkills = new Set(catalog.skills.map((skill) => skill.skillId));
26
+ try {
27
+ const installed = await listInstalledSkills(dependencies);
28
+ const postPlusInstalled = installed.filter((skill) => requiredSkills.has(skill.name));
29
+ const installedNames = new Set(postPlusInstalled.map((skill) => skill.name));
30
+ const missingSkills = [...requiredSkills].filter((skill) => !installedNames.has(skill));
31
+ const scopes = [
32
+ ...new Set(postPlusInstalled
33
+ .map((skill) => skill.scope)
34
+ .filter((scope) => scope.trim().length > 0)),
35
+ ].sort();
36
+ return {
37
+ ok: missingSkills.length === 0,
38
+ error: null,
39
+ installCommand: POSTPLUS_SKILLS_INSTALL_COMMAND,
40
+ installedCount: installedNames.size,
41
+ missingSkills,
42
+ requiredCount: requiredSkills.size,
43
+ scopes,
44
+ source: POSTPLUS_SKILLS_REPO,
45
+ updateCommand: formatPostPlusSkillUpdateCommand(),
46
+ uninstallCommand: formatPostPlusSkillUninstallCommand(),
47
+ };
48
+ }
49
+ catch (error) {
50
+ return {
51
+ ok: false,
52
+ error: error instanceof Error
53
+ ? error.message
54
+ : 'Failed to inspect installed PostPlus skills.',
55
+ installCommand: POSTPLUS_SKILLS_INSTALL_COMMAND,
56
+ installedCount: 0,
57
+ missingSkills: [...requiredSkills],
58
+ requiredCount: requiredSkills.size,
59
+ scopes: [],
60
+ source: POSTPLUS_SKILLS_REPO,
61
+ updateCommand: formatPostPlusSkillUpdateCommand(),
62
+ uninstallCommand: formatPostPlusSkillUninstallCommand(),
63
+ };
64
+ }
65
+ }
66
+ export function formatSkillInstallStatusReport(report) {
67
+ const lines = ['PostPlus skills status', ''];
68
+ if (report.error) {
69
+ lines.push(`[FAIL] Skill installer: ${report.error}`);
70
+ }
71
+ else if (report.ok) {
72
+ lines.push(`[PASS] Installed released skills: ${report.installedCount}/${report.requiredCount}`);
73
+ }
74
+ else {
75
+ lines.push(`[FAIL] Installed released skills: ${report.installedCount}/${report.requiredCount}`);
76
+ }
77
+ lines.push(` Source: ${report.source}`);
78
+ lines.push(` Scope: ${report.scopes.length > 0 ? report.scopes.join(', ') : 'none detected'}`);
79
+ if (report.missingSkills.length > 0) {
80
+ lines.push(` Missing: ${formatSkillList(report.missingSkills, 8)}`, ` Fix: ${report.installCommand}`);
81
+ }
82
+ else {
83
+ lines.push(` Update: ${report.updateCommand}`);
84
+ }
85
+ return lines.join('\n');
86
+ }
87
+ export function buildPostPlusSkillUpdateArgs(skillNames) {
88
+ return [...NPX_SKILLS, 'update', ...skillNames, '--yes'];
89
+ }
90
+ export function buildPostPlusSkillUninstallArgs(skillNames) {
91
+ return [
92
+ ...NPX_SKILLS,
93
+ 'remove',
94
+ ...skillNames,
95
+ '--agent',
96
+ ...SKILLS_AGENTS,
97
+ '--yes',
98
+ ];
99
+ }
100
+ export function formatPostPlusSkillUpdateCommand() {
101
+ return 'postplus update';
102
+ }
103
+ export function formatPostPlusSkillUninstallCommand() {
104
+ return 'postplus uninstall';
105
+ }
106
+ async function listInstalledSkills(dependencies) {
107
+ const [project, global] = await Promise.all([
108
+ listInstalledSkillsForScope(dependencies, []),
109
+ listInstalledSkillsForScope(dependencies, ['--global']),
110
+ ]);
111
+ const byKey = new Map();
112
+ for (const skill of [...project, ...global]) {
113
+ byKey.set(`${skill.scope}:${skill.name}:${skill.path}`, skill);
114
+ }
115
+ return [...byKey.values()];
116
+ }
117
+ async function listInstalledSkillsForScope(dependencies, scopeArgs) {
118
+ const result = await dependencies.runCommand('npx', [...NPX_SKILLS, 'list', '--json', ...scopeArgs], {
119
+ timeoutMs: 60_000,
120
+ });
121
+ const parsed = JSON.parse(result.stdout);
122
+ if (!Array.isArray(parsed)) {
123
+ throw new Error('`skills list --json` returned an invalid payload.');
124
+ }
125
+ return parsed.map(normalizeInstalledSkillEntry);
126
+ }
127
+ function normalizeInstalledSkillEntry(value) {
128
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
129
+ throw new Error('`skills list --json` returned an invalid skill entry.');
130
+ }
131
+ const record = value;
132
+ const name = typeof record.name === 'string' ? record.name.trim() : '';
133
+ const skillPath = typeof record.path === 'string' ? record.path.trim() : '';
134
+ const scope = typeof record.scope === 'string' ? record.scope.trim() : '';
135
+ const agents = Array.isArray(record.agents)
136
+ ? record.agents
137
+ .filter((agent) => typeof agent === 'string')
138
+ .map((agent) => agent.trim())
139
+ .filter(Boolean)
140
+ : [];
141
+ if (!name || !skillPath || !scope) {
142
+ throw new Error('`skills list --json` returned an incomplete skill entry.');
143
+ }
144
+ return {
145
+ agents,
146
+ name,
147
+ path: skillPath,
148
+ scope,
149
+ };
150
+ }
151
+ function formatSkillList(skills, limit) {
152
+ const visible = skills.slice(0, limit);
153
+ const rest = skills.length - visible.length;
154
+ return rest > 0 ? `${visible.join(', ')} (+${rest} more)` : visible.join(', ');
155
+ }
package/build/status.js CHANGED
@@ -1,14 +1,27 @@
1
1
  import { formatAuthStatusReport, generateAuthStatusReport, } from './auth.js';
2
2
  import { formatDoctorReport, generateDoctorReport, } from './doctor.js';
3
+ import { formatSkillInstallStatusReport, generateSkillInstallStatusReport, } from './skill-management.js';
4
+ import { formatUpdateStatusReport, generateUpdateStatusReport, } from './update-check.js';
3
5
  export async function generateStatusReport() {
4
- const [doctor, auth] = await Promise.all([
5
- generateDoctorReport(),
6
- generateAuthStatusReport(),
6
+ return generateStatusReportWithDependencies();
7
+ }
8
+ export async function generateStatusReportWithDependencies(dependencies = {}) {
9
+ const generateAuthStatus = dependencies.generateAuthStatus ?? generateAuthStatusReport;
10
+ const generateDoctor = dependencies.generateDoctor ?? generateDoctorReport;
11
+ const generateSkillStatus = dependencies.generateSkillStatus ?? generateSkillInstallStatusReport;
12
+ const generateUpdateStatus = dependencies.generateUpdateStatus ?? generateUpdateStatusReport;
13
+ const [doctor, auth, skills, updates] = await Promise.all([
14
+ generateDoctor(),
15
+ generateAuthStatus(),
16
+ generateSkillStatus(),
17
+ generateUpdateStatus(),
7
18
  ]);
8
19
  return {
9
- ok: doctor.ok && auth.ok,
20
+ ok: doctor.ok && auth.ok && skills.ok && updates.ok,
10
21
  doctor,
11
22
  auth,
23
+ skills,
24
+ updates,
12
25
  };
13
26
  }
14
27
  export function formatStatusReport(report) {
@@ -20,5 +33,9 @@ export function formatStatusReport(report) {
20
33
  formatDoctorReport(report.doctor),
21
34
  '',
22
35
  formatAuthStatusReport(report.auth),
36
+ '',
37
+ formatSkillInstallStatusReport(report.skills),
38
+ '',
39
+ formatUpdateStatusReport(report.updates),
23
40
  ].join('\n');
24
41
  }
@@ -0,0 +1,229 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
3
+ import { dirname, join } from 'node:path';
4
+ import { getPostPlusConfigDir } from './local-state.js';
5
+ import { POSTPLUS_SKILLS_REPO } from './skill-catalog.js';
6
+ const UPDATE_CHECK_TTL_MS = 24 * 60 * 60 * 1000;
7
+ const UPDATE_CHECK_CACHE_FILE = 'update-check.json';
8
+ const NPM_PACKAGE_NAME = '@postplus/cli';
9
+ 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
+ export async function generateUpdateStatusReport(input = {}, dependencies = {
12
+ fetchFn: fetch,
13
+ }) {
14
+ const currentVersion = await readCurrentCliVersion();
15
+ const cache = await readUpdateCheckCache();
16
+ if (cache &&
17
+ !input.force &&
18
+ cache.cli.currentVersion === currentVersion &&
19
+ Date.now() - Date.parse(cache.checkedAt) < UPDATE_CHECK_TTL_MS) {
20
+ return buildUpdateReport({
21
+ cache,
22
+ currentVersion,
23
+ previousSkillRevision: cache.skills.latestRevision,
24
+ source: 'cache',
25
+ });
26
+ }
27
+ try {
28
+ const [latestCliVersion, latestSkillRevision] = await Promise.all([
29
+ fetchLatestCliVersion(dependencies.fetchFn),
30
+ fetchLatestSkillRevision(dependencies.fetchFn),
31
+ ]);
32
+ const nextCache = {
33
+ checkedAt: new Date().toISOString(),
34
+ cli: {
35
+ currentVersion,
36
+ latestVersion: latestCliVersion,
37
+ },
38
+ skills: {
39
+ latestRevision: latestSkillRevision,
40
+ },
41
+ };
42
+ await writeUpdateCheckCache(nextCache);
43
+ return buildUpdateReport({
44
+ cache: nextCache,
45
+ currentVersion,
46
+ previousSkillRevision: input.resetSkillBaseline
47
+ ? latestSkillRevision
48
+ : cache?.skills.latestRevision ?? latestSkillRevision,
49
+ source: 'remote',
50
+ });
51
+ }
52
+ catch (error) {
53
+ const warning = error instanceof Error ? error.message : 'Update check failed.';
54
+ if (cache) {
55
+ return {
56
+ ...buildUpdateReport({
57
+ cache,
58
+ currentVersion,
59
+ previousSkillRevision: cache.skills.latestRevision,
60
+ source: 'cache',
61
+ }),
62
+ warning,
63
+ };
64
+ }
65
+ return {
66
+ checkedAt: null,
67
+ ok: true,
68
+ source: 'unavailable',
69
+ cli: {
70
+ currentVersion,
71
+ latestVersion: null,
72
+ updateAvailable: false,
73
+ updateCommand: 'npm install -g @postplus/cli',
74
+ },
75
+ skills: {
76
+ currentRevision: null,
77
+ latestRevision: null,
78
+ updateAvailable: false,
79
+ updateCommand: 'postplus update',
80
+ },
81
+ warning,
82
+ };
83
+ }
84
+ }
85
+ export async function refreshUpdateCheckBaseline() {
86
+ await generateUpdateStatusReport({
87
+ force: true,
88
+ resetSkillBaseline: true,
89
+ });
90
+ }
91
+ export function formatUpdateStatusReport(report) {
92
+ const lines = ['PostPlus update status', ''];
93
+ const cliMarker = report.cli.updateAvailable ? '[WARN]' : '[PASS]';
94
+ lines.push(`${cliMarker} CLI: ${report.cli.currentVersion}${report.cli.latestVersion ? ` (latest ${report.cli.latestVersion})` : ''}`);
95
+ if (report.cli.updateAvailable) {
96
+ lines.push(` Update: ${report.cli.updateCommand}`);
97
+ }
98
+ const skillMarker = report.skills.updateAvailable ? '[WARN]' : '[PASS]';
99
+ lines.push(`${skillMarker} Skills: ${report.skills.latestRevision
100
+ ? `release ${shortRevision(report.skills.latestRevision)}`
101
+ : 'release unknown'}`);
102
+ if (report.skills.updateAvailable) {
103
+ lines.push(` Update: ${report.skills.updateCommand}`);
104
+ }
105
+ lines.push(` Checked: ${report.checkedAt ?? 'not checked'} (${report.source})`);
106
+ if (report.warning) {
107
+ lines.push(` Warning: ${report.warning}`);
108
+ }
109
+ return lines.join('\n');
110
+ }
111
+ function buildUpdateReport(input) {
112
+ return {
113
+ checkedAt: input.cache.checkedAt,
114
+ ok: true,
115
+ source: input.source,
116
+ cli: {
117
+ currentVersion: input.currentVersion,
118
+ latestVersion: input.cache.cli.latestVersion,
119
+ updateAvailable: compareVersions(input.cache.cli.latestVersion, input.currentVersion) > 0,
120
+ updateCommand: 'npm install -g @postplus/cli',
121
+ },
122
+ skills: {
123
+ currentRevision: input.previousSkillRevision,
124
+ latestRevision: input.cache.skills.latestRevision,
125
+ updateAvailable: input.cache.skills.latestRevision !== input.previousSkillRevision,
126
+ updateCommand: 'postplus update',
127
+ },
128
+ warning: null,
129
+ };
130
+ }
131
+ async function readCurrentCliVersion() {
132
+ const packageJsonPath = new URL('../package.json', import.meta.url);
133
+ const raw = await readFile(packageJsonPath, 'utf8');
134
+ const parsed = JSON.parse(raw);
135
+ if (typeof parsed.version !== 'string' || !parsed.version.trim()) {
136
+ throw new Error('Could not read the current PostPlus CLI version.');
137
+ }
138
+ return parsed.version.trim();
139
+ }
140
+ async function fetchLatestCliVersion(fetchFn) {
141
+ const response = await fetchFn(NPM_LATEST_URL, {
142
+ headers: {
143
+ accept: 'application/json',
144
+ 'user-agent': `postplus-cli-update-check/${await readCurrentCliVersion()}`,
145
+ },
146
+ signal: AbortSignal.timeout(15_000),
147
+ });
148
+ if (!response.ok) {
149
+ throw new Error(`Failed to check latest PostPlus CLI version (${response.status}).`);
150
+ }
151
+ const payload = (await response.json());
152
+ if (typeof payload.version !== 'string' || !payload.version.trim()) {
153
+ throw new Error('NPM returned an invalid PostPlus CLI version payload.');
154
+ }
155
+ return payload.version.trim();
156
+ }
157
+ 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}).`);
167
+ }
168
+ const payload = (await response.json());
169
+ if (typeof payload.sha === 'string' && payload.sha.trim()) {
170
+ return payload.sha.trim();
171
+ }
172
+ return createHash('sha256')
173
+ .update(JSON.stringify(payload))
174
+ .digest('hex');
175
+ }
176
+ async function readUpdateCheckCache() {
177
+ try {
178
+ const raw = await readFile(getUpdateCheckCachePath(), 'utf8');
179
+ const parsed = JSON.parse(raw);
180
+ if (typeof parsed.checkedAt !== 'string' ||
181
+ typeof parsed.cli?.currentVersion !== 'string' ||
182
+ typeof parsed.cli?.latestVersion !== 'string' ||
183
+ typeof parsed.skills?.latestRevision !== 'string') {
184
+ return null;
185
+ }
186
+ return parsed;
187
+ }
188
+ catch (error) {
189
+ const nodeError = error;
190
+ if (nodeError.code === 'ENOENT') {
191
+ return null;
192
+ }
193
+ throw error;
194
+ }
195
+ }
196
+ async function writeUpdateCheckCache(cache) {
197
+ const cachePath = getUpdateCheckCachePath();
198
+ await mkdir(dirname(cachePath), { recursive: true });
199
+ await writeFile(cachePath, `${JSON.stringify(cache, null, 2)}\n`, 'utf8');
200
+ }
201
+ function getUpdateCheckCachePath() {
202
+ return join(getPostPlusConfigDir(), UPDATE_CHECK_CACHE_FILE);
203
+ }
204
+ function compareVersions(a, b) {
205
+ const left = parseVersion(a);
206
+ const right = parseVersion(b);
207
+ const length = Math.max(left.length, right.length);
208
+ for (let index = 0; index < length; index += 1) {
209
+ const leftPart = left[index] ?? 0;
210
+ const rightPart = right[index] ?? 0;
211
+ if (leftPart > rightPart) {
212
+ return 1;
213
+ }
214
+ if (leftPart < rightPart) {
215
+ return -1;
216
+ }
217
+ }
218
+ return 0;
219
+ }
220
+ function parseVersion(value) {
221
+ return value
222
+ .replace(/^[^\d]*/, '')
223
+ .split(/[.-]/)
224
+ .map((part) => Number.parseInt(part, 10))
225
+ .map((part) => (Number.isFinite(part) ? part : 0));
226
+ }
227
+ function shortRevision(revision) {
228
+ return revision.length > 12 ? revision.slice(0, 12) : revision;
229
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@postplus/cli",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
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.",
@@ -10,12 +10,15 @@
10
10
  "build/auth-login.js",
11
11
  "build/auth-validate.js",
12
12
  "build/auth.js",
13
+ "build/command-runner.js",
13
14
  "build/doctor.js",
14
15
  "build/hosted-release.js",
15
16
  "build/index.js",
16
17
  "build/local-state.js",
17
18
  "build/skill-catalog.js",
19
+ "build/skill-management.js",
18
20
  "build/status.js",
21
+ "build/update-check.js",
19
22
  "LICENSE",
20
23
  "NOTICE",
21
24
  "README.md"