pmpt-cli 1.9.0 → 1.10.0
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/dist/commands/diff.js +11 -4
- package/dist/commands/internal-seed.js +104 -0
- package/dist/commands/plan.js +62 -10
- package/dist/commands/publish.js +145 -62
- package/dist/index.js +14 -0
- package/package.json +1 -1
package/dist/commands/diff.js
CHANGED
|
@@ -74,16 +74,23 @@ function printSummary(diffs) {
|
|
|
74
74
|
p.log.info(`${pc.green(`+${additions}`)} additions, ${pc.red(`-${deletions}`)} deletions`);
|
|
75
75
|
}
|
|
76
76
|
export function cmdDiff(v1, v2, pathOrOptions, maybeOptions) {
|
|
77
|
-
// Commander passes args
|
|
78
|
-
//
|
|
79
|
-
// pmpt diff v1 v2 → (v1, v2, options)
|
|
80
|
-
// pmpt diff v1 v2 /path → (v1, v2, path, options)
|
|
77
|
+
// Commander passes args in order: pmpt diff v1 [v2] [path] [options]
|
|
78
|
+
// Smart parsing: if v2 looks like a path (not a version pattern), treat it as path.
|
|
81
79
|
let v2Str;
|
|
82
80
|
let path;
|
|
83
81
|
let options = {};
|
|
82
|
+
const isVersion = (s) => /^v?\d+$/.test(s);
|
|
84
83
|
if (typeof v2 === 'object') {
|
|
84
|
+
// pmpt diff v1 --file x → (v1, options)
|
|
85
85
|
options = v2;
|
|
86
86
|
}
|
|
87
|
+
else if (v2 !== undefined && !isVersion(v2)) {
|
|
88
|
+
// pmpt diff v1 /some/path → v2 is actually a path
|
|
89
|
+
path = v2;
|
|
90
|
+
if (typeof pathOrOptions === 'object') {
|
|
91
|
+
options = pathOrOptions;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
87
94
|
else {
|
|
88
95
|
v2Str = v2;
|
|
89
96
|
if (typeof pathOrOptions === 'object') {
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from 'fs';
|
|
3
|
+
import { dirname, join, resolve } from 'path';
|
|
4
|
+
import { getDocsDir, initializeProject, isInitialized } from '../lib/config.js';
|
|
5
|
+
import { createFullSnapshot } from '../lib/history.js';
|
|
6
|
+
import { cmdPlan } from './plan.js';
|
|
7
|
+
import { cmdPublish } from './publish.js';
|
|
8
|
+
function assertInternalEnabled() {
|
|
9
|
+
if (process.env.PMPT_INTERNAL !== '1') {
|
|
10
|
+
p.log.error('internal-seed is disabled.');
|
|
11
|
+
p.log.info('Set PMPT_INTERNAL=1 to enable internal automation commands.');
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function readJsonFile(filePath) {
|
|
16
|
+
if (!existsSync(filePath)) {
|
|
17
|
+
throw new Error(`File not found: ${filePath}`);
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
return JSON.parse(readFileSync(filePath, 'utf-8'));
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
throw new Error(`Invalid JSON: ${filePath}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function writeDocFile(docsDir, fileName, content) {
|
|
27
|
+
const outPath = resolve(docsDir, fileName);
|
|
28
|
+
const docsRoot = resolve(docsDir);
|
|
29
|
+
if (!outPath.startsWith(docsRoot + '/') && outPath !== docsRoot) {
|
|
30
|
+
throw new Error(`Unsafe docs path: ${fileName}`);
|
|
31
|
+
}
|
|
32
|
+
mkdirSync(dirname(outPath), { recursive: true });
|
|
33
|
+
writeFileSync(outPath, content, 'utf-8');
|
|
34
|
+
}
|
|
35
|
+
function normalizeTags(tags) {
|
|
36
|
+
if (!tags)
|
|
37
|
+
return undefined;
|
|
38
|
+
if (Array.isArray(tags))
|
|
39
|
+
return tags.join(',');
|
|
40
|
+
return tags;
|
|
41
|
+
}
|
|
42
|
+
export async function cmdInternalSeed(options) {
|
|
43
|
+
assertInternalEnabled();
|
|
44
|
+
if (!options?.spec) {
|
|
45
|
+
p.log.error('Missing required option: --spec <file>');
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
const specPath = resolve(options.spec);
|
|
49
|
+
const specDir = dirname(specPath);
|
|
50
|
+
const spec = readJsonFile(specPath);
|
|
51
|
+
const projectPath = spec.projectPath ? resolve(spec.projectPath) : process.cwd();
|
|
52
|
+
p.intro('pmpt internal-seed');
|
|
53
|
+
if (!isInitialized(projectPath)) {
|
|
54
|
+
initializeProject(projectPath, { trackGit: true });
|
|
55
|
+
p.log.info(`Initialized: ${projectPath}`);
|
|
56
|
+
}
|
|
57
|
+
let answersFileForPlan;
|
|
58
|
+
if (spec.answers) {
|
|
59
|
+
const tempAnswersPath = join(projectPath, '.pmpt', '.internal-seed-answers.json');
|
|
60
|
+
mkdirSync(dirname(tempAnswersPath), { recursive: true });
|
|
61
|
+
writeFileSync(tempAnswersPath, JSON.stringify(spec.answers, null, 2), 'utf-8');
|
|
62
|
+
answersFileForPlan = tempAnswersPath;
|
|
63
|
+
}
|
|
64
|
+
else if (spec.answersFile) {
|
|
65
|
+
answersFileForPlan = resolve(specDir, spec.answersFile);
|
|
66
|
+
}
|
|
67
|
+
if (answersFileForPlan) {
|
|
68
|
+
await cmdPlan(projectPath, {
|
|
69
|
+
reset: spec.resetPlan ?? true,
|
|
70
|
+
answersFile: answersFileForPlan,
|
|
71
|
+
});
|
|
72
|
+
// Clean up temp answers file
|
|
73
|
+
const tempAnswersPath = join(projectPath, '.pmpt', '.internal-seed-answers.json');
|
|
74
|
+
if (spec.answers && existsSync(tempAnswersPath)) {
|
|
75
|
+
unlinkSync(tempAnswersPath);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const docsDir = getDocsDir(projectPath);
|
|
79
|
+
for (const step of spec.versions ?? []) {
|
|
80
|
+
for (const [fileName, content] of Object.entries(step.files ?? {})) {
|
|
81
|
+
writeDocFile(docsDir, fileName, content);
|
|
82
|
+
}
|
|
83
|
+
for (const [fileName, fromPath] of Object.entries(step.filesFrom ?? {})) {
|
|
84
|
+
const content = readFileSync(resolve(specDir, fromPath), 'utf-8');
|
|
85
|
+
writeDocFile(docsDir, fileName, content);
|
|
86
|
+
}
|
|
87
|
+
const entry = createFullSnapshot(projectPath);
|
|
88
|
+
const note = step.saveNote ? ` — ${step.saveNote}` : '';
|
|
89
|
+
p.log.success(`v${entry.version} saved${note}`);
|
|
90
|
+
}
|
|
91
|
+
if (spec.publish?.enabled) {
|
|
92
|
+
await cmdPublish(projectPath, {
|
|
93
|
+
force: spec.publish.force ?? false,
|
|
94
|
+
nonInteractive: true,
|
|
95
|
+
yes: spec.publish.yes ?? true,
|
|
96
|
+
metaFile: spec.publish.metaFile ? resolve(specDir, spec.publish.metaFile) : undefined,
|
|
97
|
+
slug: spec.publish.slug,
|
|
98
|
+
description: spec.publish.description,
|
|
99
|
+
tags: normalizeTags(spec.publish.tags),
|
|
100
|
+
category: spec.publish.category,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
p.outro('internal-seed completed');
|
|
104
|
+
}
|
package/dist/commands/plan.js
CHANGED
|
@@ -1,10 +1,30 @@
|
|
|
1
1
|
import * as p from '@clack/prompts';
|
|
2
2
|
import { resolve } from 'path';
|
|
3
|
-
import { readFileSync } from 'fs';
|
|
3
|
+
import { existsSync, readFileSync } from 'fs';
|
|
4
4
|
import { isInitialized } from '../lib/config.js';
|
|
5
5
|
import { copyToClipboard } from '../lib/clipboard.js';
|
|
6
6
|
import { cmdWatch } from './watch.js';
|
|
7
7
|
import { PLAN_QUESTIONS, getPlanProgress, initPlanProgress, savePlanProgress, savePlanDocuments, } from '../lib/plan.js';
|
|
8
|
+
function loadAnswersFromFile(projectPath, inputPath) {
|
|
9
|
+
const filePath = resolve(projectPath, inputPath);
|
|
10
|
+
if (!existsSync(filePath)) {
|
|
11
|
+
throw new Error(`Answers file not found: ${filePath}`);
|
|
12
|
+
}
|
|
13
|
+
const raw = JSON.parse(readFileSync(filePath, 'utf-8'));
|
|
14
|
+
const requiredKeys = ['projectName', 'productIdea', 'coreFeatures'];
|
|
15
|
+
for (const key of requiredKeys) {
|
|
16
|
+
if (!raw[key] || String(raw[key]).trim().length === 0) {
|
|
17
|
+
throw new Error(`Missing required field in answers file: ${key}`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
projectName: String(raw.projectName ?? '').trim(),
|
|
22
|
+
productIdea: String(raw.productIdea ?? '').trim(),
|
|
23
|
+
additionalContext: String(raw.additionalContext ?? '').trim(),
|
|
24
|
+
coreFeatures: String(raw.coreFeatures ?? '').trim(),
|
|
25
|
+
techStack: String(raw.techStack ?? '').trim(),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
8
28
|
export async function cmdPlan(path, options) {
|
|
9
29
|
const projectPath = path ? resolve(path) : process.cwd();
|
|
10
30
|
// Check initialization
|
|
@@ -17,20 +37,25 @@ export async function cmdPlan(path, options) {
|
|
|
17
37
|
}
|
|
18
38
|
// Reset option
|
|
19
39
|
if (options?.reset) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
40
|
+
if (options.answersFile) {
|
|
41
|
+
initPlanProgress(projectPath);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
const confirm = await p.confirm({
|
|
45
|
+
message: 'Restart plan from scratch?',
|
|
46
|
+
initialValue: false,
|
|
47
|
+
});
|
|
48
|
+
if (p.isCancel(confirm) || !confirm) {
|
|
49
|
+
p.cancel('Cancelled');
|
|
50
|
+
process.exit(0);
|
|
51
|
+
}
|
|
52
|
+
initPlanProgress(projectPath);
|
|
27
53
|
}
|
|
28
|
-
initPlanProgress(projectPath);
|
|
29
54
|
}
|
|
30
55
|
// Check progress
|
|
31
56
|
let progress = getPlanProgress(projectPath);
|
|
32
57
|
// If already completed
|
|
33
|
-
if (progress?.completed) {
|
|
58
|
+
if (progress?.completed && !options?.answersFile) {
|
|
34
59
|
p.intro('pmpt plan');
|
|
35
60
|
p.log.success('Plan already completed.');
|
|
36
61
|
const action = await p.select({
|
|
@@ -122,6 +147,33 @@ export async function cmdPlan(path, options) {
|
|
|
122
147
|
if (!progress) {
|
|
123
148
|
progress = initPlanProgress(projectPath);
|
|
124
149
|
}
|
|
150
|
+
// Non-interactive mode for agents/automation
|
|
151
|
+
if (options?.answersFile) {
|
|
152
|
+
p.log.info('Plan: non-interactive mode');
|
|
153
|
+
let answers;
|
|
154
|
+
try {
|
|
155
|
+
answers = loadAnswersFromFile(projectPath, options.answersFile);
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
p.log.error(err instanceof Error ? err.message : 'Invalid answers file.');
|
|
159
|
+
p.outro('');
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
const s = p.spinner();
|
|
163
|
+
s.start('Generating documents from answers file...');
|
|
164
|
+
const { planPath, promptPath } = savePlanDocuments(projectPath, answers);
|
|
165
|
+
progress.completed = true;
|
|
166
|
+
progress.answers = answers;
|
|
167
|
+
savePlanProgress(projectPath, progress);
|
|
168
|
+
s.stop('Done!');
|
|
169
|
+
const pmptMdPath = promptPath.replace('pmpt.ai.md', 'pmpt.md');
|
|
170
|
+
p.note([
|
|
171
|
+
`plan.md: ${planPath}`,
|
|
172
|
+
`pmpt.md: ${pmptMdPath}`,
|
|
173
|
+
`pmpt.ai.md: ${promptPath}`,
|
|
174
|
+
].join('\n'), 'Generated');
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
125
177
|
p.intro('pmpt plan — Your Product Journey Starts Here!');
|
|
126
178
|
p.log.info(`Answer ${PLAN_QUESTIONS.length} quick questions to generate your AI prompt.`);
|
|
127
179
|
p.log.message('You can answer in any language you prefer.');
|
package/dist/commands/publish.js
CHANGED
|
@@ -11,6 +11,43 @@ import { computeQuality } from '../lib/quality.js';
|
|
|
11
11
|
import pc from 'picocolors';
|
|
12
12
|
import glob from 'fast-glob';
|
|
13
13
|
import { join } from 'path';
|
|
14
|
+
const CATEGORY_OPTIONS = [
|
|
15
|
+
{ value: 'web-app', label: 'Web App' },
|
|
16
|
+
{ value: 'mobile-app', label: 'Mobile App' },
|
|
17
|
+
{ value: 'cli-tool', label: 'CLI Tool' },
|
|
18
|
+
{ value: 'api-backend', label: 'API/Backend' },
|
|
19
|
+
{ value: 'ai-ml', label: 'AI/ML' },
|
|
20
|
+
{ value: 'game', label: 'Game' },
|
|
21
|
+
{ value: 'library', label: 'Library' },
|
|
22
|
+
{ value: 'other', label: 'Other' },
|
|
23
|
+
];
|
|
24
|
+
const VALID_CATEGORIES = new Set(CATEGORY_OPTIONS.map((o) => o.value));
|
|
25
|
+
function normalizeTags(value) {
|
|
26
|
+
if (Array.isArray(value)) {
|
|
27
|
+
return value
|
|
28
|
+
.map((v) => String(v).trim().toLowerCase())
|
|
29
|
+
.filter(Boolean);
|
|
30
|
+
}
|
|
31
|
+
if (typeof value === 'string') {
|
|
32
|
+
return value
|
|
33
|
+
.split(',')
|
|
34
|
+
.map((t) => t.trim().toLowerCase())
|
|
35
|
+
.filter(Boolean);
|
|
36
|
+
}
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
function loadMetaFile(projectPath, filePath) {
|
|
40
|
+
const resolved = resolve(projectPath, filePath);
|
|
41
|
+
if (!existsSync(resolved)) {
|
|
42
|
+
throw new Error(`Meta file not found: ${resolved}`);
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
return JSON.parse(readFileSync(resolved, 'utf-8'));
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
throw new Error(`Invalid JSON in meta file: ${resolved}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
14
51
|
function readDocsFolder(docsDir) {
|
|
15
52
|
const files = {};
|
|
16
53
|
if (!existsSync(docsDir))
|
|
@@ -35,7 +72,12 @@ export async function cmdPublish(path, options) {
|
|
|
35
72
|
p.log.error('Login required. Run `pmpt login` first.');
|
|
36
73
|
process.exit(1);
|
|
37
74
|
}
|
|
38
|
-
|
|
75
|
+
if (options?.nonInteractive) {
|
|
76
|
+
p.log.info('Publish: non-interactive mode');
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
p.intro('pmpt publish');
|
|
80
|
+
}
|
|
39
81
|
const config = loadConfig(projectPath);
|
|
40
82
|
const snapshots = getAllSnapshots(projectPath);
|
|
41
83
|
const planProgress = getPlanProgress(projectPath);
|
|
@@ -102,59 +144,92 @@ export async function cmdPublish(path, options) {
|
|
|
102
144
|
const defaultSlug = savedSlug
|
|
103
145
|
|| projectName.toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-');
|
|
104
146
|
// Collect publish info
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
147
|
+
let slug;
|
|
148
|
+
let description;
|
|
149
|
+
let tags;
|
|
150
|
+
let category;
|
|
151
|
+
if (options?.nonInteractive) {
|
|
152
|
+
let metaFromFile = {};
|
|
153
|
+
if (options.metaFile) {
|
|
154
|
+
try {
|
|
155
|
+
metaFromFile = loadMetaFile(projectPath, options.metaFile);
|
|
112
156
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
157
|
+
catch (err) {
|
|
158
|
+
p.log.error(err instanceof Error ? err.message : 'Failed to load meta file.');
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
slug = String(options.slug
|
|
163
|
+
?? metaFromFile.slug
|
|
164
|
+
?? savedSlug
|
|
165
|
+
?? defaultSlug).trim();
|
|
166
|
+
description = String(options.description
|
|
167
|
+
?? metaFromFile.description
|
|
168
|
+
?? existing?.description
|
|
169
|
+
?? planProgress?.answers?.productIdea?.slice(0, 200)
|
|
170
|
+
?? '').trim();
|
|
171
|
+
tags = normalizeTags(options.tags ?? metaFromFile.tags ?? existing?.tags ?? []);
|
|
172
|
+
category = String(options.category ?? metaFromFile.category ?? existing?.category ?? 'other').trim();
|
|
173
|
+
if (!/^[a-z0-9][a-z0-9-]{1,48}[a-z0-9]$/.test(slug)) {
|
|
174
|
+
p.log.error('Invalid slug. Use 3-50 chars, lowercase letters, numbers, and hyphens only.');
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
177
|
+
if (!description) {
|
|
178
|
+
p.log.error('Description is required in non-interactive mode.');
|
|
179
|
+
process.exit(1);
|
|
180
|
+
}
|
|
181
|
+
if (!VALID_CATEGORIES.has(category)) {
|
|
182
|
+
p.log.error(`Invalid category: ${category}`);
|
|
183
|
+
p.log.info(`Allowed: ${[...VALID_CATEGORIES].join(', ')}`);
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
const slugInput = await p.text({
|
|
189
|
+
message: 'Project slug (used in URL):',
|
|
190
|
+
placeholder: defaultSlug,
|
|
191
|
+
defaultValue: savedSlug || '',
|
|
192
|
+
validate: (v) => {
|
|
193
|
+
if (!/^[a-z0-9][a-z0-9-]{1,48}[a-z0-9]$/.test(v)) {
|
|
194
|
+
return '3-50 chars, lowercase letters, numbers, and hyphens only.';
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
if (p.isCancel(slugInput)) {
|
|
199
|
+
p.cancel('Cancelled');
|
|
200
|
+
process.exit(0);
|
|
201
|
+
}
|
|
202
|
+
slug = slugInput;
|
|
203
|
+
const descriptionInput = await p.text({
|
|
204
|
+
message: 'Project description (brief):',
|
|
205
|
+
placeholder: existing?.description || planProgress?.answers?.productIdea?.slice(0, 100) || '',
|
|
206
|
+
defaultValue: existing?.description || planProgress?.answers?.productIdea?.slice(0, 200) || '',
|
|
207
|
+
});
|
|
208
|
+
if (p.isCancel(descriptionInput)) {
|
|
209
|
+
p.cancel('Cancelled');
|
|
210
|
+
process.exit(0);
|
|
211
|
+
}
|
|
212
|
+
description = descriptionInput;
|
|
213
|
+
const tagsInput = await p.text({
|
|
214
|
+
message: 'Tags (comma-separated):',
|
|
215
|
+
placeholder: 'react, saas, mvp',
|
|
216
|
+
defaultValue: existing?.tags?.join(', ') || '',
|
|
217
|
+
});
|
|
218
|
+
if (p.isCancel(tagsInput)) {
|
|
219
|
+
p.cancel('Cancelled');
|
|
220
|
+
process.exit(0);
|
|
221
|
+
}
|
|
222
|
+
tags = normalizeTags(tagsInput);
|
|
223
|
+
const categoryInput = await p.select({
|
|
224
|
+
message: 'Project category:',
|
|
225
|
+
initialValue: existing?.category || 'other',
|
|
226
|
+
options: CATEGORY_OPTIONS,
|
|
227
|
+
});
|
|
228
|
+
if (p.isCancel(categoryInput)) {
|
|
229
|
+
p.cancel('Cancelled');
|
|
230
|
+
process.exit(0);
|
|
231
|
+
}
|
|
232
|
+
category = categoryInput;
|
|
158
233
|
}
|
|
159
234
|
// Build .pmpt content (resolve from optimized snapshots)
|
|
160
235
|
const history = snapshots.map((snapshot, i) => ({
|
|
@@ -191,13 +266,21 @@ export async function cmdPublish(path, options) {
|
|
|
191
266
|
`Category: ${category}`,
|
|
192
267
|
tags.length ? `Tags: ${tags.join(', ')}` : '',
|
|
193
268
|
].filter(Boolean).join('\n'), 'Publish Preview');
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
269
|
+
if (options?.nonInteractive) {
|
|
270
|
+
if (!options.yes) {
|
|
271
|
+
p.log.error('Non-interactive mode requires --yes to confirm publish.');
|
|
272
|
+
process.exit(1);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
else if (!options?.yes) {
|
|
276
|
+
const confirm = await p.confirm({
|
|
277
|
+
message: 'Publish this project?',
|
|
278
|
+
initialValue: true,
|
|
279
|
+
});
|
|
280
|
+
if (p.isCancel(confirm) || !confirm) {
|
|
281
|
+
p.cancel('Cancelled');
|
|
282
|
+
process.exit(0);
|
|
283
|
+
}
|
|
201
284
|
}
|
|
202
285
|
// Upload
|
|
203
286
|
const s = p.spinner();
|
|
@@ -206,9 +289,9 @@ export async function cmdPublish(path, options) {
|
|
|
206
289
|
const result = await publishProject(auth.token, {
|
|
207
290
|
slug: slug,
|
|
208
291
|
pmptContent,
|
|
209
|
-
description
|
|
292
|
+
description,
|
|
210
293
|
tags,
|
|
211
|
-
category
|
|
294
|
+
category,
|
|
212
295
|
});
|
|
213
296
|
s.stop('Published!');
|
|
214
297
|
// Update config
|
package/dist/index.js
CHANGED
|
@@ -40,6 +40,7 @@ import { cmdClone } from './commands/clone.js';
|
|
|
40
40
|
import { cmdBrowse } from './commands/browse.js';
|
|
41
41
|
import { cmdRecover } from './commands/recover.js';
|
|
42
42
|
import { cmdDiff } from './commands/diff.js';
|
|
43
|
+
import { cmdInternalSeed } from './commands/internal-seed.js';
|
|
43
44
|
import { createRequire } from 'module';
|
|
44
45
|
const require = createRequire(import.meta.url);
|
|
45
46
|
const { version } = require('../package.json');
|
|
@@ -114,6 +115,7 @@ program
|
|
|
114
115
|
.command('plan [path]')
|
|
115
116
|
.description('Quick product planning with 5 questions — auto-generate AI prompt')
|
|
116
117
|
.option('--reset', 'Restart plan from scratch')
|
|
118
|
+
.option('--answers-file <file>', 'Load plan answers from JSON file (non-interactive)')
|
|
117
119
|
.action(cmdPlan);
|
|
118
120
|
program
|
|
119
121
|
.command('logout')
|
|
@@ -132,6 +134,13 @@ program
|
|
|
132
134
|
.command('publish [path]')
|
|
133
135
|
.description('Publish project to pmptwiki platform')
|
|
134
136
|
.option('--force', 'Publish even if quality score is below minimum')
|
|
137
|
+
.option('--non-interactive', 'Run without interactive prompts')
|
|
138
|
+
.option('--meta-file <file>', 'JSON file with slug, description, tags, category')
|
|
139
|
+
.option('--slug <slug>', 'Project slug')
|
|
140
|
+
.option('--description <text>', 'Project description')
|
|
141
|
+
.option('--tags <csv>', 'Comma-separated tags')
|
|
142
|
+
.option('--category <id>', 'Project category')
|
|
143
|
+
.option('--yes', 'Skip confirmation prompt')
|
|
135
144
|
.action(cmdPublish);
|
|
136
145
|
program
|
|
137
146
|
.command('edit')
|
|
@@ -153,4 +162,9 @@ program
|
|
|
153
162
|
.command('recover [path]')
|
|
154
163
|
.description('Generate a recovery prompt to regenerate pmpt.md via AI')
|
|
155
164
|
.action(cmdRecover);
|
|
165
|
+
// Internal automation command (hidden from help)
|
|
166
|
+
program
|
|
167
|
+
.command('internal-seed', { hidden: true })
|
|
168
|
+
.requiredOption('--spec <file>', 'Seed spec JSON file')
|
|
169
|
+
.action(cmdInternalSeed);
|
|
156
170
|
program.parse();
|