@postplus/cli 0.1.29 → 0.1.31
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 +24 -2
- package/build/doctor.js +16 -1
- package/build/index.js +46 -11
- package/build/skill-catalog.js +6 -2
- package/build/skill-management.js +27 -17
- package/build/studio.js +322 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -33,17 +33,39 @@ Requires Node.js and npm.
|
|
|
33
33
|
```bash
|
|
34
34
|
npm install -g @postplus/cli@latest
|
|
35
35
|
postplus auth login
|
|
36
|
-
npx -y skills add PostPlusAI/postplus-skills --global --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 openclaw hermes-agent --yes
|
|
37
37
|
postplus skills verify
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
+
If you explicitly do not want global skills, run the install from the target
|
|
41
|
+
project directory and omit `--global`:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npx -y skills add PostPlusAI/postplus-skills --full-depth --skill '*' --agent claude-code codex cursor github-copilot windsurf trae trae-cn openclaw hermes-agent --yes
|
|
45
|
+
```
|
|
46
|
+
|
|
40
47
|
Useful checks:
|
|
41
48
|
|
|
42
49
|
```bash
|
|
43
50
|
postplus status
|
|
44
|
-
npx -y skills add PostPlusAI/postplus-skills --
|
|
51
|
+
npx -y skills add PostPlusAI/postplus-skills --global --list
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Local Studio
|
|
55
|
+
|
|
56
|
+
For heavier skills that benefit from a visual workspace, use the CLI-managed
|
|
57
|
+
local Studio:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
postplus studio init
|
|
61
|
+
postplus studio open
|
|
62
|
+
postplus studio status
|
|
45
63
|
```
|
|
46
64
|
|
|
65
|
+
Studio creates a visible `PostPlus Studio/` folder in the current working
|
|
66
|
+
directory. Assets, workflow files, activity, and provenance live inside that
|
|
67
|
+
folder; hidden runtime cache and logs stay under `PostPlus Studio/.postplus/`.
|
|
68
|
+
|
|
47
69
|
## The Vision
|
|
48
70
|
|
|
49
71
|
PostPlus is built for a world where one marketer, founder, operator, or agency strategist can work with an AI agent as if they had a larger marketing team around them.
|
package/build/doctor.js
CHANGED
|
@@ -166,6 +166,11 @@ async function checkHostedCapabilities(input, skillScope) {
|
|
|
166
166
|
.map((value) => readCapabilityFailureLabel(value, skillScope))
|
|
167
167
|
.filter((value) => value !== null);
|
|
168
168
|
if (skillScope && hasHostedRequirements(skillScope.skill.requirements)) {
|
|
169
|
+
const subscription = readSubscriptionStatusField(payload).label;
|
|
170
|
+
if (requiresSocialPublishingPlan(skillScope.skill.requirements) &&
|
|
171
|
+
subscription === 'none') {
|
|
172
|
+
failedLabels.push(`PostPlus Plus or Pro plan required; current subscription ${subscription}`);
|
|
173
|
+
}
|
|
169
174
|
const missingRequirements = collectMissingHostedRequirementLabels(relevantCapabilities, skillScope.skill.requirements);
|
|
170
175
|
failedLabels.push(...missingRequirements);
|
|
171
176
|
}
|
|
@@ -261,7 +266,7 @@ function capabilityMatchesRequirements(capability, requirements) {
|
|
|
261
266
|
if (prefix &&
|
|
262
267
|
suffix &&
|
|
263
268
|
hostedCapabilities.has(prefix) &&
|
|
264
|
-
requirementKeys.has(suffix)) {
|
|
269
|
+
(isWholeFamilyHostedCapability(prefix) || requirementKeys.has(suffix))) {
|
|
265
270
|
return true;
|
|
266
271
|
}
|
|
267
272
|
return requirementKeys.has(identifier) || requirementKeys.has(suffix);
|
|
@@ -310,6 +315,12 @@ function collectHostedRequirementKeys(requirements) {
|
|
|
310
315
|
...requirements.sourceKeys,
|
|
311
316
|
]);
|
|
312
317
|
}
|
|
318
|
+
function isWholeFamilyHostedCapability(prefix) {
|
|
319
|
+
return prefix === 'social-publishing';
|
|
320
|
+
}
|
|
321
|
+
function requiresSocialPublishingPlan(requirements) {
|
|
322
|
+
return requirements.hostedCapabilities.includes('social-publishing');
|
|
323
|
+
}
|
|
313
324
|
function hasHostedRequirements(requirements) {
|
|
314
325
|
return (requirements.accountConnections.length > 0 ||
|
|
315
326
|
requirements.collectionKeys.length > 0 ||
|
|
@@ -337,6 +348,10 @@ function identifierMatchesKey(identifier, key) {
|
|
|
337
348
|
if (identifier === key) {
|
|
338
349
|
return true;
|
|
339
350
|
}
|
|
351
|
+
if (key === 'social-publishing-workspace' &&
|
|
352
|
+
identifierMatchesCapability(identifier, 'social-publishing')) {
|
|
353
|
+
return true;
|
|
354
|
+
}
|
|
340
355
|
const [, suffix] = splitCapabilityIdentifier(identifier);
|
|
341
356
|
return suffix === key;
|
|
342
357
|
}
|
package/build/index.js
CHANGED
|
@@ -8,10 +8,11 @@ import { readCurrentCliVersion } from './client-compatibility.js';
|
|
|
8
8
|
import { formatDoctorReport, generateDoctorReport } from './doctor.js';
|
|
9
9
|
import { assertConfigFilePermissions } from './local-state.js';
|
|
10
10
|
import { readLargeCreditQuoteConfirmationChallenge, resolveLargeCreditQuoteConfirmation, } from './quote-confirmation.js';
|
|
11
|
-
import { POSTPLUS_SKILLS_INSTALL_COMMAND, loadPublicSkillCatalog, } from './skill-catalog.js';
|
|
11
|
+
import { POSTPLUS_SKILLS_CURRENT_DIRECTORY_INSTALL_COMMAND, POSTPLUS_SKILLS_INSTALL_COMMAND, loadPublicSkillCatalog, formatPostPlusSkillsInstallCommand, } from './skill-catalog.js';
|
|
12
12
|
import { formatSkillBaselineVerifyReport, runPostPlusSkillUninstall, runPostPlusSkillUpdate, runPostPlusSkillVerify, } from './skill-management.js';
|
|
13
13
|
import { formatStatusReport, generateStatusReport } from './status.js';
|
|
14
14
|
import { refreshUpdateCheckCache, runCliSelfUpdateIfOutdated, } from './update-check.js';
|
|
15
|
+
import { runStudioCommand } from './studio.js';
|
|
15
16
|
function printAuthHelp() {
|
|
16
17
|
process.stdout.write(`PostPlus CLI — auth commands
|
|
17
18
|
|
|
@@ -42,15 +43,19 @@ Usage:
|
|
|
42
43
|
postplus doctor [--skill <skill-id>] [--json]
|
|
43
44
|
postplus quote confirm --json --challenge-file <path>
|
|
44
45
|
postplus skills verify [--json]
|
|
45
|
-
postplus
|
|
46
|
-
postplus
|
|
46
|
+
postplus studio init|open|status
|
|
47
|
+
postplus update [--current-directory]
|
|
48
|
+
postplus uninstall [--current-directory]
|
|
47
49
|
postplus list [--json]
|
|
48
50
|
postplus status [--skill <skill-id>] [--json]
|
|
49
51
|
postplus version
|
|
50
52
|
postplus help
|
|
51
53
|
|
|
52
54
|
Skills:
|
|
53
|
-
|
|
55
|
+
Global:
|
|
56
|
+
${POSTPLUS_SKILLS_INSTALL_COMMAND}
|
|
57
|
+
Current directory:
|
|
58
|
+
${POSTPLUS_SKILLS_CURRENT_DIRECTORY_INSTALL_COMMAND}
|
|
54
59
|
|
|
55
60
|
After first install, run:
|
|
56
61
|
postplus skills verify
|
|
@@ -96,7 +101,8 @@ async function runList(json) {
|
|
|
96
101
|
'PostPlus skills',
|
|
97
102
|
'',
|
|
98
103
|
`Source: ${catalog.source}`,
|
|
99
|
-
`Install: ${catalog.installCommand}`,
|
|
104
|
+
`Install (global): ${catalog.installCommand}`,
|
|
105
|
+
`Install (current directory): ${formatPostPlusSkillsInstallCommand(catalog.source, 'current-directory')}`,
|
|
100
106
|
'',
|
|
101
107
|
];
|
|
102
108
|
for (const entry of catalog.skills) {
|
|
@@ -109,19 +115,25 @@ async function runVersion() {
|
|
|
109
115
|
process.stdout.write(`${await readCurrentCliVersion()}\n`);
|
|
110
116
|
return 0;
|
|
111
117
|
}
|
|
112
|
-
async function runSkillUpdateCommand() {
|
|
118
|
+
async function runSkillUpdateCommand(rest) {
|
|
119
|
+
const options = parseSkillMutationOptions(rest, 'update');
|
|
113
120
|
const cliSelfUpdate = await runCliSelfUpdateIfOutdated();
|
|
114
121
|
if (cliSelfUpdate.updateAvailable) {
|
|
115
122
|
return cliSelfUpdate.exitCode ?? 1;
|
|
116
123
|
}
|
|
117
|
-
const exitCode = await runPostPlusSkillUpdate(
|
|
124
|
+
const exitCode = await runPostPlusSkillUpdate(undefined, {
|
|
125
|
+
scope: options.scope,
|
|
126
|
+
});
|
|
118
127
|
if (exitCode === 0) {
|
|
119
128
|
await refreshUpdateCheckCache().catch(() => { });
|
|
120
129
|
}
|
|
121
130
|
return exitCode;
|
|
122
131
|
}
|
|
123
|
-
async function runSkillUninstallCommand() {
|
|
124
|
-
|
|
132
|
+
async function runSkillUninstallCommand(rest) {
|
|
133
|
+
const options = parseSkillMutationOptions(rest, 'uninstall');
|
|
134
|
+
return runPostPlusSkillUninstall(undefined, {
|
|
135
|
+
scope: options.scope,
|
|
136
|
+
});
|
|
125
137
|
}
|
|
126
138
|
async function runSkillsCommand(rest) {
|
|
127
139
|
const [subcommand] = rest;
|
|
@@ -153,6 +165,12 @@ Usage:
|
|
|
153
165
|
|
|
154
166
|
Options:
|
|
155
167
|
--json Output results as JSON
|
|
168
|
+
|
|
169
|
+
Install scope:
|
|
170
|
+
postplus update Update global PostPlus skills
|
|
171
|
+
postplus update --current-directory Update PostPlus skills in the current directory
|
|
172
|
+
postplus uninstall Remove global PostPlus skills
|
|
173
|
+
postplus uninstall --current-directory Remove PostPlus skills from the current directory
|
|
156
174
|
`);
|
|
157
175
|
return 0;
|
|
158
176
|
default:
|
|
@@ -233,6 +251,17 @@ function parseDiagnosticOptions(args) {
|
|
|
233
251
|
}
|
|
234
252
|
return options;
|
|
235
253
|
}
|
|
254
|
+
function parseSkillMutationOptions(args, commandName) {
|
|
255
|
+
let scope = 'global';
|
|
256
|
+
for (const arg of args) {
|
|
257
|
+
if (arg === '--current-directory') {
|
|
258
|
+
scope = 'current-directory';
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
throw new Error(`Unknown option for ${commandName}: ${arg}`);
|
|
262
|
+
}
|
|
263
|
+
return { scope };
|
|
264
|
+
}
|
|
236
265
|
async function runAuthLogout(json) {
|
|
237
266
|
const report = await clearAuthState();
|
|
238
267
|
if (json) {
|
|
@@ -309,6 +338,9 @@ async function main() {
|
|
|
309
338
|
else if (helpTopic === 'skills') {
|
|
310
339
|
await runSkillsCommand(['help']);
|
|
311
340
|
}
|
|
341
|
+
else if (helpTopic === 'studio') {
|
|
342
|
+
await runStudioCommand(['help']);
|
|
343
|
+
}
|
|
312
344
|
else {
|
|
313
345
|
printHelp();
|
|
314
346
|
}
|
|
@@ -324,15 +356,18 @@ async function main() {
|
|
|
324
356
|
case 'skills':
|
|
325
357
|
process.exitCode = await runSkillsCommand(rest);
|
|
326
358
|
return;
|
|
359
|
+
case 'studio':
|
|
360
|
+
process.exitCode = await runStudioCommand(rest);
|
|
361
|
+
return;
|
|
327
362
|
case 'install':
|
|
328
363
|
process.stderr.write(`PostPlus CLI does not install skills directly. Run \`${POSTPLUS_SKILLS_INSTALL_COMMAND}\`.\n`);
|
|
329
364
|
process.exitCode = 1;
|
|
330
365
|
return;
|
|
331
366
|
case 'update':
|
|
332
|
-
process.exitCode = await runSkillUpdateCommand();
|
|
367
|
+
process.exitCode = await runSkillUpdateCommand(rest);
|
|
333
368
|
return;
|
|
334
369
|
case 'uninstall':
|
|
335
|
-
process.exitCode = await runSkillUninstallCommand();
|
|
370
|
+
process.exitCode = await runSkillUninstallCommand(rest);
|
|
336
371
|
return;
|
|
337
372
|
case 'list':
|
|
338
373
|
process.exitCode = await runList(json);
|
package/build/skill-catalog.js
CHANGED
|
@@ -9,9 +9,12 @@ export const POSTPLUS_SKILLS_AGENT_TARGETS = [
|
|
|
9
9
|
'windsurf',
|
|
10
10
|
'trae',
|
|
11
11
|
'trae-cn',
|
|
12
|
+
'openclaw',
|
|
13
|
+
'hermes-agent',
|
|
12
14
|
];
|
|
13
15
|
const POSTPLUS_SKILLS_AGENT_ARGS = POSTPLUS_SKILLS_AGENT_TARGETS.join(' ');
|
|
14
16
|
export const POSTPLUS_SKILLS_INSTALL_COMMAND = formatPostPlusSkillsInstallCommand();
|
|
17
|
+
export const POSTPLUS_SKILLS_CURRENT_DIRECTORY_INSTALL_COMMAND = formatPostPlusSkillsInstallCommand(POSTPLUS_SKILLS_REPO, 'current-directory');
|
|
15
18
|
export const POSTPLUS_SKILLS_LIST_COMMAND = formatPostPlusSkillsListCommand();
|
|
16
19
|
const POSTPLUS_SKILLS_INDEX_URL = 'https://raw.githubusercontent.com/PostPlusAI/postplus-skills/main/skills/INDEX.md';
|
|
17
20
|
const POSTPLUS_SKILLS_CATALOG_URL = 'https://raw.githubusercontent.com/PostPlusAI/postplus-skills/main/skills/catalog.json';
|
|
@@ -54,8 +57,9 @@ export function resolvePostPlusSkillsSource(env = process.env) {
|
|
|
54
57
|
export function resolvePostPlusSkillsCatalogUrl(env = process.env) {
|
|
55
58
|
return env[POSTPLUS_SKILLS_CATALOG_URL_ENV]?.trim() || POSTPLUS_SKILLS_CATALOG_URL;
|
|
56
59
|
}
|
|
57
|
-
export function formatPostPlusSkillsInstallCommand(source = POSTPLUS_SKILLS_REPO) {
|
|
58
|
-
|
|
60
|
+
export function formatPostPlusSkillsInstallCommand(source = POSTPLUS_SKILLS_REPO, scope = 'global') {
|
|
61
|
+
const scopeArgs = scope === 'global' ? ' --global' : '';
|
|
62
|
+
return `npx -y skills add ${source}${scopeArgs} --full-depth --skill '*' --agent ${POSTPLUS_SKILLS_AGENT_ARGS} --yes`;
|
|
59
63
|
}
|
|
60
64
|
export function formatPostPlusSkillsListCommand(source = POSTPLUS_SKILLS_REPO) {
|
|
61
65
|
return `npx -y skills add ${source} --list --full-depth`;
|
|
@@ -3,9 +3,12 @@ import { runCommand, runInteractiveCommand } from './command-runner.js';
|
|
|
3
3
|
import { clearManagedSkillBaseline, readManagedSkillBaseline, writeManagedSkillBaseline, } from './local-state.js';
|
|
4
4
|
import { POSTPLUS_SKILLS_AGENT_TARGETS, formatPostPlusSkillsInstallCommand, resolvePostPlusSkillsSource, loadPublicSkillCatalog, } from './skill-catalog.js';
|
|
5
5
|
const NPX_SKILLS = ['-y', 'skills'];
|
|
6
|
+
const DEFAULT_SKILL_MUTATION_OPTIONS = {
|
|
7
|
+
scope: 'global',
|
|
8
|
+
};
|
|
6
9
|
export async function runPostPlusSkillUpdate(dependencies = {
|
|
7
10
|
runInteractiveCommand,
|
|
8
|
-
}) {
|
|
11
|
+
}, options = DEFAULT_SKILL_MUTATION_OPTIONS) {
|
|
9
12
|
const catalog = await loadPublicSkillCatalog();
|
|
10
13
|
const skillNames = catalog.skills.map((skill) => skill.skillId);
|
|
11
14
|
const baseline = await readManagedSkillBaseline();
|
|
@@ -13,12 +16,12 @@ export async function runPostPlusSkillUpdate(dependencies = {
|
|
|
13
16
|
if (skillNames.length === 0) {
|
|
14
17
|
throw new Error('PostPlus public skill catalog has no released skills.');
|
|
15
18
|
}
|
|
16
|
-
const updateExitCode = await dependencies.runInteractiveCommand('npx', buildPostPlusSkillUpdateArgs(skillNames));
|
|
19
|
+
const updateExitCode = await dependencies.runInteractiveCommand('npx', buildPostPlusSkillUpdateArgs(skillNames, options.scope));
|
|
17
20
|
if (updateExitCode !== 0) {
|
|
18
21
|
return updateExitCode;
|
|
19
22
|
}
|
|
20
23
|
if (retiredSkillNames.length > 0) {
|
|
21
|
-
const removeExitCode = await dependencies.runInteractiveCommand('npx', buildPostPlusSkillUninstallArgs(retiredSkillNames));
|
|
24
|
+
const removeExitCode = await dependencies.runInteractiveCommand('npx', buildPostPlusSkillUninstallArgs(retiredSkillNames, options.scope));
|
|
22
25
|
if (removeExitCode !== 0) {
|
|
23
26
|
return removeExitCode;
|
|
24
27
|
}
|
|
@@ -32,7 +35,7 @@ export async function runPostPlusSkillUpdate(dependencies = {
|
|
|
32
35
|
}
|
|
33
36
|
export async function runPostPlusSkillUninstall(dependencies = {
|
|
34
37
|
runInteractiveCommand,
|
|
35
|
-
}) {
|
|
38
|
+
}, options = DEFAULT_SKILL_MUTATION_OPTIONS) {
|
|
36
39
|
const catalog = await loadPublicSkillCatalog();
|
|
37
40
|
const skillNames = catalog.skills.map((skill) => skill.skillId);
|
|
38
41
|
const baseline = await readManagedSkillBaseline();
|
|
@@ -40,7 +43,7 @@ export async function runPostPlusSkillUninstall(dependencies = {
|
|
|
40
43
|
if (allKnownSkillNames.length === 0) {
|
|
41
44
|
throw new Error('PostPlus public skill catalog has no released skills.');
|
|
42
45
|
}
|
|
43
|
-
const exitCode = await dependencies.runInteractiveCommand('npx', buildPostPlusSkillUninstallArgs(allKnownSkillNames));
|
|
46
|
+
const exitCode = await dependencies.runInteractiveCommand('npx', buildPostPlusSkillUninstallArgs(allKnownSkillNames, options.scope));
|
|
44
47
|
if (exitCode === 0) {
|
|
45
48
|
await clearManagedSkillBaseline();
|
|
46
49
|
}
|
|
@@ -150,13 +153,13 @@ export function formatSkillInstallStatusReport(report) {
|
|
|
150
153
|
lines.push(` Managed baseline: ${report.managedSkillsReleaseId ?? 'none'}`);
|
|
151
154
|
lines.push(` Scope: ${report.scopes.length > 0 ? report.scopes.join(', ') : 'none detected'}`);
|
|
152
155
|
if (report.retiredManagedSkills.length > 0) {
|
|
153
|
-
lines.push(` Retired managed skills: ${formatSkillList(report.retiredManagedSkills, 8)}`, ` Cleanup: ${report.updateCommand}`);
|
|
156
|
+
lines.push(` Retired managed skills: ${formatSkillList(report.retiredManagedSkills, 8)}`, ` Cleanup (global): ${report.updateCommand}`, ` Cleanup (current directory): ${formatPostPlusSkillUpdateCommand('current-directory')}`);
|
|
154
157
|
}
|
|
155
158
|
if (report.missingSkills.length > 0) {
|
|
156
|
-
lines.push(` Missing: ${formatSkillList(report.missingSkills, 8)}`, ` Fix: ${report.installCommand}`);
|
|
159
|
+
lines.push(` Missing: ${formatSkillList(report.missingSkills, 8)}`, ` Fix (global): ${report.installCommand}`, ` Fix (current directory): ${formatPostPlusSkillsInstallCommand(report.source, 'current-directory')}`);
|
|
157
160
|
}
|
|
158
161
|
else {
|
|
159
|
-
lines.push(` Update: ${report.updateCommand}`);
|
|
162
|
+
lines.push(` Update (global): ${report.updateCommand}`, ` Update (current directory): ${formatPostPlusSkillUpdateCommand('current-directory')}`);
|
|
160
163
|
}
|
|
161
164
|
return lines.join('\n');
|
|
162
165
|
}
|
|
@@ -181,11 +184,11 @@ export function formatSkillBaselineVerifyReport(report) {
|
|
|
181
184
|
lines.push(' Verified baseline: unchanged');
|
|
182
185
|
}
|
|
183
186
|
if (report.missingSkills.length > 0) {
|
|
184
|
-
lines.push(` Missing: ${formatSkillList(report.missingSkills, 8)}`, ` Fix: ${report.installCommand}`);
|
|
187
|
+
lines.push(` Missing: ${formatSkillList(report.missingSkills, 8)}`, ` Fix (global): ${report.installCommand}`, ` Fix (current directory): ${formatPostPlusSkillsInstallCommand(report.source, 'current-directory')}`);
|
|
185
188
|
}
|
|
186
189
|
return lines.join('\n');
|
|
187
190
|
}
|
|
188
|
-
export function buildPostPlusSkillUpdateArgs(skillNames) {
|
|
191
|
+
export function buildPostPlusSkillUpdateArgs(skillNames, scope = 'global') {
|
|
189
192
|
if (skillNames.length === 0) {
|
|
190
193
|
throw new Error('PostPlus public skill catalog has no released skills.');
|
|
191
194
|
}
|
|
@@ -194,7 +197,7 @@ export function buildPostPlusSkillUpdateArgs(skillNames) {
|
|
|
194
197
|
...NPX_SKILLS,
|
|
195
198
|
'add',
|
|
196
199
|
skillsSource,
|
|
197
|
-
|
|
200
|
+
...buildSkillScopeArgs(scope),
|
|
198
201
|
'--full-depth',
|
|
199
202
|
'--skill',
|
|
200
203
|
'*',
|
|
@@ -203,22 +206,29 @@ export function buildPostPlusSkillUpdateArgs(skillNames) {
|
|
|
203
206
|
'--yes',
|
|
204
207
|
];
|
|
205
208
|
}
|
|
206
|
-
export function buildPostPlusSkillUninstallArgs(skillNames) {
|
|
209
|
+
export function buildPostPlusSkillUninstallArgs(skillNames, scope = 'global') {
|
|
207
210
|
return [
|
|
208
211
|
...NPX_SKILLS,
|
|
209
212
|
'remove',
|
|
210
213
|
...skillNames,
|
|
211
|
-
|
|
214
|
+
...buildSkillScopeArgs(scope),
|
|
212
215
|
'--agent',
|
|
213
216
|
...POSTPLUS_SKILLS_AGENT_TARGETS,
|
|
214
217
|
'--yes',
|
|
215
218
|
];
|
|
216
219
|
}
|
|
217
|
-
export function formatPostPlusSkillUpdateCommand() {
|
|
218
|
-
return
|
|
220
|
+
export function formatPostPlusSkillUpdateCommand(scope = 'global') {
|
|
221
|
+
return scope === 'global'
|
|
222
|
+
? 'postplus update'
|
|
223
|
+
: 'postplus update --current-directory';
|
|
224
|
+
}
|
|
225
|
+
export function formatPostPlusSkillUninstallCommand(scope = 'global') {
|
|
226
|
+
return scope === 'global'
|
|
227
|
+
? 'postplus uninstall'
|
|
228
|
+
: 'postplus uninstall --current-directory';
|
|
219
229
|
}
|
|
220
|
-
|
|
221
|
-
return '
|
|
230
|
+
function buildSkillScopeArgs(scope) {
|
|
231
|
+
return scope === 'global' ? ['--global'] : [];
|
|
222
232
|
}
|
|
223
233
|
function mergeSkillNames(left, right) {
|
|
224
234
|
return [...new Set([...left, ...right])].sort((a, b) => a.localeCompare(b));
|
package/build/studio.js
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
2
|
+
import { access, mkdir, writeFile, } from 'node:fs/promises';
|
|
3
|
+
import { constants as fsConstants } from 'node:fs';
|
|
4
|
+
import { platform } from 'node:os';
|
|
5
|
+
import { basename, dirname, join, resolve, } from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
export const POSTPLUS_STUDIO_DIRECTORY_NAME = 'PostPlus Studio';
|
|
8
|
+
const DEFAULT_STUDIO_ID = 'postplus-studio';
|
|
9
|
+
export async function runStudioCommand(args) {
|
|
10
|
+
const [subcommand, ...rest] = args;
|
|
11
|
+
if (!subcommand || ['help', '--help', '-h'].includes(subcommand)) {
|
|
12
|
+
printStudioHelp();
|
|
13
|
+
return 0;
|
|
14
|
+
}
|
|
15
|
+
const options = parseStudioOptions(rest);
|
|
16
|
+
switch (subcommand) {
|
|
17
|
+
case 'init': {
|
|
18
|
+
const result = await initializeStudio(options.workdir);
|
|
19
|
+
writeOutput(options.json, result, formatStudioInitReport(result));
|
|
20
|
+
return 0;
|
|
21
|
+
}
|
|
22
|
+
case 'status': {
|
|
23
|
+
const report = await getStudioStatus(options.workdir);
|
|
24
|
+
writeOutput(options.json, report, formatStudioStatusReport(report));
|
|
25
|
+
return report.ok ? 0 : 1;
|
|
26
|
+
}
|
|
27
|
+
case 'open': {
|
|
28
|
+
const result = await openStudio(options);
|
|
29
|
+
writeOutput(options.json, result, formatStudioOpenReport(result));
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
32
|
+
default:
|
|
33
|
+
process.stderr.write(`Unknown studio command: ${subcommand}\n\n`);
|
|
34
|
+
printStudioHelp();
|
|
35
|
+
return 1;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function printStudioHelp() {
|
|
39
|
+
process.stdout.write(`PostPlus CLI — studio commands
|
|
40
|
+
|
|
41
|
+
Usage:
|
|
42
|
+
postplus studio init [--workdir <dir>] [--json]
|
|
43
|
+
postplus studio open [--workdir <dir>] [--port 3978] [--no-browser] [--json]
|
|
44
|
+
postplus studio status [--workdir <dir>] [--json]
|
|
45
|
+
|
|
46
|
+
Studio creates a visible "PostPlus Studio" folder inside the selected working directory.
|
|
47
|
+
`);
|
|
48
|
+
}
|
|
49
|
+
function parseStudioOptions(args) {
|
|
50
|
+
const options = {
|
|
51
|
+
browser: true,
|
|
52
|
+
json: false,
|
|
53
|
+
port: 3978,
|
|
54
|
+
workdir: process.cwd(),
|
|
55
|
+
};
|
|
56
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
57
|
+
const arg = args[index];
|
|
58
|
+
if (arg === '--json') {
|
|
59
|
+
options.json = true;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (arg === '--no-browser') {
|
|
63
|
+
options.browser = false;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (arg === '--workdir') {
|
|
67
|
+
const value = args[index + 1];
|
|
68
|
+
if (!value || value.startsWith('--')) {
|
|
69
|
+
throw new Error('Missing value for --workdir.');
|
|
70
|
+
}
|
|
71
|
+
options.workdir = resolve(value);
|
|
72
|
+
index += 1;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (arg === '--port') {
|
|
76
|
+
const value = args[index + 1];
|
|
77
|
+
if (!value || value.startsWith('--')) {
|
|
78
|
+
throw new Error('Missing value for --port.');
|
|
79
|
+
}
|
|
80
|
+
options.port = Number(value);
|
|
81
|
+
if (!Number.isInteger(options.port) || options.port <= 0) {
|
|
82
|
+
throw new Error('--port must be a positive integer.');
|
|
83
|
+
}
|
|
84
|
+
index += 1;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
throw new Error(`Unknown option for studio command: ${arg}`);
|
|
88
|
+
}
|
|
89
|
+
return options;
|
|
90
|
+
}
|
|
91
|
+
export function resolveStudioRoot(workdir) {
|
|
92
|
+
const root = resolve(workdir);
|
|
93
|
+
return basename(root) === POSTPLUS_STUDIO_DIRECTORY_NAME
|
|
94
|
+
? root
|
|
95
|
+
: join(root, POSTPLUS_STUDIO_DIRECTORY_NAME);
|
|
96
|
+
}
|
|
97
|
+
async function initializeStudio(workdir) {
|
|
98
|
+
const studioRoot = resolveStudioRoot(workdir);
|
|
99
|
+
const createdAt = new Date().toISOString();
|
|
100
|
+
await mkdir(studioRoot, { recursive: true });
|
|
101
|
+
for (const dir of [
|
|
102
|
+
'workflows',
|
|
103
|
+
'assets/texts',
|
|
104
|
+
'assets/images',
|
|
105
|
+
'assets/audio',
|
|
106
|
+
'assets/videos',
|
|
107
|
+
'assets/html',
|
|
108
|
+
'assets/references',
|
|
109
|
+
'data',
|
|
110
|
+
'exports',
|
|
111
|
+
'.postplus/locks',
|
|
112
|
+
'.postplus/cache',
|
|
113
|
+
'.postplus/temp',
|
|
114
|
+
'.postplus/runs',
|
|
115
|
+
'.postplus/provider-responses',
|
|
116
|
+
'.postplus/quote-confirmations',
|
|
117
|
+
'.postplus/logs',
|
|
118
|
+
]) {
|
|
119
|
+
await mkdir(join(studioRoot, dir), { recursive: true });
|
|
120
|
+
}
|
|
121
|
+
await writeJsonIfMissing(join(studioRoot, 'studio.json'), {
|
|
122
|
+
schemaVersion: 1,
|
|
123
|
+
studio_id: DEFAULT_STUDIO_ID,
|
|
124
|
+
name: 'PostPlus Studio',
|
|
125
|
+
root_name: POSTPLUS_STUDIO_DIRECTORY_NAME,
|
|
126
|
+
created_at: createdAt,
|
|
127
|
+
updated_at: createdAt,
|
|
128
|
+
});
|
|
129
|
+
await writeJsonIfMissing(join(studioRoot, 'project.json'), {
|
|
130
|
+
project_id: DEFAULT_STUDIO_ID,
|
|
131
|
+
name: 'PostPlus Studio',
|
|
132
|
+
goal: 'Run PostPlus workflows in a local visual Studio workspace.',
|
|
133
|
+
status: 'active',
|
|
134
|
+
created_at: createdAt,
|
|
135
|
+
updated_at: createdAt,
|
|
136
|
+
});
|
|
137
|
+
await writeJsonIfMissing(join(studioRoot, 'manifest.json'), { assets: [] });
|
|
138
|
+
await writeJsonIfMissing(join(studioRoot, 'pipeline.json'), {
|
|
139
|
+
pipeline_id: 'ad-video-pipeline',
|
|
140
|
+
steps: [
|
|
141
|
+
{
|
|
142
|
+
id: 'brief',
|
|
143
|
+
name: 'Brief',
|
|
144
|
+
status: 'pending',
|
|
145
|
+
updated_at: createdAt,
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
id: 'script',
|
|
149
|
+
name: 'Script',
|
|
150
|
+
status: 'pending',
|
|
151
|
+
updated_at: createdAt,
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
id: 'storyboard',
|
|
155
|
+
name: 'Storyboard',
|
|
156
|
+
status: 'pending',
|
|
157
|
+
updated_at: createdAt,
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
});
|
|
161
|
+
await writeJsonIfMissing(join(studioRoot, 'context.json'), {
|
|
162
|
+
active_project: DEFAULT_STUDIO_ID,
|
|
163
|
+
active_pipeline: 'ad-video-pipeline',
|
|
164
|
+
active_step: 'brief',
|
|
165
|
+
selected_asset_id: null,
|
|
166
|
+
selected_block_id: null,
|
|
167
|
+
selected_version: null,
|
|
168
|
+
visible_panel: 'dashboard',
|
|
169
|
+
updated_at: createdAt,
|
|
170
|
+
});
|
|
171
|
+
await writeTextIfMissing(join(studioRoot, 'provenance.jsonl'), '');
|
|
172
|
+
await writeTextIfMissing(join(studioRoot, 'activity.jsonl'), '');
|
|
173
|
+
return {
|
|
174
|
+
ok: true,
|
|
175
|
+
studioRoot,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
async function getStudioStatus(workdir) {
|
|
179
|
+
const studioRoot = resolveStudioRoot(workdir);
|
|
180
|
+
const exists = await pathExists(studioRoot);
|
|
181
|
+
const files = {
|
|
182
|
+
manifest: await pathExists(join(studioRoot, 'manifest.json')),
|
|
183
|
+
pipeline: await pathExists(join(studioRoot, 'pipeline.json')),
|
|
184
|
+
studio: await pathExists(join(studioRoot, 'studio.json')),
|
|
185
|
+
};
|
|
186
|
+
return {
|
|
187
|
+
exists,
|
|
188
|
+
files,
|
|
189
|
+
ok: exists && files.studio && files.manifest && files.pipeline,
|
|
190
|
+
studioRoot,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
async function openStudio(options) {
|
|
194
|
+
const { studioRoot } = await initializeStudio(options.workdir);
|
|
195
|
+
const runtimeRoot = await resolveStudioRuntimeRoot();
|
|
196
|
+
const launcher = join(runtimeRoot, 'skills/00-core/postplus-workspace-dashboard/scripts/launch_workspace_dashboard.mjs');
|
|
197
|
+
const result = spawnSync(process.execPath, [
|
|
198
|
+
launcher,
|
|
199
|
+
'--studio-root',
|
|
200
|
+
studioRoot,
|
|
201
|
+
'--host',
|
|
202
|
+
'127.0.0.1',
|
|
203
|
+
'--port',
|
|
204
|
+
String(options.port),
|
|
205
|
+
'--skip-build',
|
|
206
|
+
], {
|
|
207
|
+
cwd: runtimeRoot,
|
|
208
|
+
encoding: 'utf8',
|
|
209
|
+
});
|
|
210
|
+
if (result.status !== 0) {
|
|
211
|
+
throw new Error(result.stderr || result.stdout || 'Failed to open Studio.');
|
|
212
|
+
}
|
|
213
|
+
const parsed = JSON.parse(result.stdout);
|
|
214
|
+
if (options.browser) {
|
|
215
|
+
openSystemBrowser(parsed.url);
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
ok: true,
|
|
219
|
+
runtimeRoot,
|
|
220
|
+
studioRoot,
|
|
221
|
+
...parsed,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
async function resolveStudioRuntimeRoot() {
|
|
225
|
+
const envRoot = process.env.POSTPLUS_STUDIO_RUNTIME_ROOT?.trim();
|
|
226
|
+
if (envRoot) {
|
|
227
|
+
return assertStudioRuntimeRoot(resolve(envRoot));
|
|
228
|
+
}
|
|
229
|
+
const candidates = [
|
|
230
|
+
process.cwd(),
|
|
231
|
+
dirname(fileURLToPath(import.meta.url)),
|
|
232
|
+
...ancestorDirs(process.cwd()),
|
|
233
|
+
];
|
|
234
|
+
for (const base of candidates) {
|
|
235
|
+
for (const candidate of [
|
|
236
|
+
base,
|
|
237
|
+
join(base, 'packages/vibe_marketing'),
|
|
238
|
+
join(base, '../packages/vibe_marketing'),
|
|
239
|
+
join(base, '../../packages/vibe_marketing'),
|
|
240
|
+
]) {
|
|
241
|
+
if (await isStudioRuntimeRoot(resolve(candidate))) {
|
|
242
|
+
return resolve(candidate);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
throw new Error('PostPlus Studio runtime was not found. Set POSTPLUS_STUDIO_RUNTIME_ROOT to the vibe_marketing authoring repo.');
|
|
247
|
+
}
|
|
248
|
+
function ancestorDirs(start) {
|
|
249
|
+
const dirs = [];
|
|
250
|
+
let current = resolve(start);
|
|
251
|
+
while (dirname(current) !== current) {
|
|
252
|
+
current = dirname(current);
|
|
253
|
+
dirs.push(current);
|
|
254
|
+
}
|
|
255
|
+
return dirs;
|
|
256
|
+
}
|
|
257
|
+
async function assertStudioRuntimeRoot(root) {
|
|
258
|
+
if (!(await isStudioRuntimeRoot(root))) {
|
|
259
|
+
throw new Error(`Invalid PostPlus Studio runtime root: ${root}`);
|
|
260
|
+
}
|
|
261
|
+
return root;
|
|
262
|
+
}
|
|
263
|
+
async function isStudioRuntimeRoot(root) {
|
|
264
|
+
return pathExists(join(root, 'skills/00-core/postplus-workspace-dashboard/scripts/launch_workspace_dashboard.mjs'));
|
|
265
|
+
}
|
|
266
|
+
async function pathExists(path) {
|
|
267
|
+
try {
|
|
268
|
+
await access(path, fsConstants.F_OK);
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
async function writeJsonIfMissing(path, value) {
|
|
276
|
+
if (await pathExists(path)) {
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
await writeJson(path, value);
|
|
280
|
+
}
|
|
281
|
+
async function writeTextIfMissing(path, value) {
|
|
282
|
+
if (await pathExists(path)) {
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
await mkdir(dirname(path), { recursive: true });
|
|
286
|
+
await writeFile(path, value, 'utf8');
|
|
287
|
+
}
|
|
288
|
+
async function writeJson(path, value) {
|
|
289
|
+
await mkdir(dirname(path), { recursive: true });
|
|
290
|
+
await writeFile(path, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
|
|
291
|
+
}
|
|
292
|
+
function openSystemBrowser(url) {
|
|
293
|
+
const command = platform() === 'darwin' ? 'open' : platform() === 'win32' ? 'cmd' : 'xdg-open';
|
|
294
|
+
const args = platform() === 'win32' ? ['/c', 'start', '', url] : [url];
|
|
295
|
+
const child = spawn(command, args, {
|
|
296
|
+
detached: true,
|
|
297
|
+
stdio: 'ignore',
|
|
298
|
+
});
|
|
299
|
+
child.unref();
|
|
300
|
+
}
|
|
301
|
+
function writeOutput(json, value, text) {
|
|
302
|
+
process.stdout.write(json ? `${JSON.stringify(value, null, 2)}\n` : text);
|
|
303
|
+
}
|
|
304
|
+
function formatStudioInitReport(result) {
|
|
305
|
+
return `PostPlus Studio initialized\n\nStudio root: ${result.studioRoot}\n`;
|
|
306
|
+
}
|
|
307
|
+
function formatStudioOpenReport(result) {
|
|
308
|
+
return `PostPlus Studio is running\n\nStudio root: ${result.studioRoot}\nURL: ${result.url}\n`;
|
|
309
|
+
}
|
|
310
|
+
function formatStudioStatusReport(report) {
|
|
311
|
+
return [
|
|
312
|
+
'PostPlus Studio status',
|
|
313
|
+
'',
|
|
314
|
+
`Studio root: ${report.studioRoot}`,
|
|
315
|
+
`Exists: ${report.exists ? 'yes' : 'no'}`,
|
|
316
|
+
`studio.json: ${report.files.studio ? 'yes' : 'no'}`,
|
|
317
|
+
`manifest.json: ${report.files.manifest ? 'yes' : 'no'}`,
|
|
318
|
+
`pipeline.json: ${report.files.pipeline ? 'yes' : 'no'}`,
|
|
319
|
+
`Status: ${report.ok ? 'ready' : 'not ready'}`,
|
|
320
|
+
'',
|
|
321
|
+
].join('\n');
|
|
322
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@postplus/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.31",
|
|
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.",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"build/skill-catalog.js",
|
|
23
23
|
"build/skill-management.js",
|
|
24
24
|
"build/status.js",
|
|
25
|
+
"build/studio.js",
|
|
25
26
|
"build/subscription-status.js",
|
|
26
27
|
"build/update-check.js",
|
|
27
28
|
"LICENSE",
|