pmpt-cli 1.14.7 → 1.14.9
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/edit.js +124 -76
- package/dist/mcp.js +129 -1
- package/package.json +1 -1
package/dist/commands/edit.js
CHANGED
|
@@ -39,97 +39,145 @@ export async function cmdEdit() {
|
|
|
39
39
|
process.exit(0);
|
|
40
40
|
}
|
|
41
41
|
const project = myProjects.find((proj) => proj.slug === slug);
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
.filter(Boolean);
|
|
64
|
-
const category = await p.select({
|
|
65
|
-
message: 'Category:',
|
|
66
|
-
initialValue: project.category || 'other',
|
|
42
|
+
// Show current settings
|
|
43
|
+
const categoryLabel = [
|
|
44
|
+
{ value: 'web-app', label: 'Web App' },
|
|
45
|
+
{ value: 'mobile-app', label: 'Mobile App' },
|
|
46
|
+
{ value: 'cli-tool', label: 'CLI Tool' },
|
|
47
|
+
{ value: 'api-backend', label: 'API/Backend' },
|
|
48
|
+
{ value: 'ai-ml', label: 'AI/ML' },
|
|
49
|
+
{ value: 'game', label: 'Game' },
|
|
50
|
+
{ value: 'library', label: 'Library' },
|
|
51
|
+
{ value: 'other', label: 'Other' },
|
|
52
|
+
].find((o) => o.value === project.category)?.label ?? project.category ?? 'Other';
|
|
53
|
+
p.note([
|
|
54
|
+
`Description: ${project.description || '(none)'}`,
|
|
55
|
+
`Tags: ${project.tags?.length ? project.tags.join(', ') : '(none)'}`,
|
|
56
|
+
`Category: ${categoryLabel}`,
|
|
57
|
+
project.productUrl ? `Product: ${project.productUrl}` : 'Product: (none)',
|
|
58
|
+
`Visibility: ${project.unlisted ? 'Unlisted' : 'Listed'}`,
|
|
59
|
+
].join('\n'), 'Current Settings');
|
|
60
|
+
// Pick fields to edit
|
|
61
|
+
const fields = await p.multiselect({
|
|
62
|
+
message: 'What do you want to edit?',
|
|
67
63
|
options: [
|
|
68
|
-
{ value: '
|
|
69
|
-
{ value: '
|
|
70
|
-
{ value: '
|
|
71
|
-
{ value: '
|
|
72
|
-
{ value: '
|
|
73
|
-
{ value: 'game', label: 'Game' },
|
|
74
|
-
{ value: 'library', label: 'Library' },
|
|
75
|
-
{ value: 'other', label: 'Other' },
|
|
64
|
+
{ value: 'description', label: 'Description' },
|
|
65
|
+
{ value: 'tags', label: 'Tags' },
|
|
66
|
+
{ value: 'category', label: 'Category' },
|
|
67
|
+
{ value: 'productUrl', label: 'Product Link' },
|
|
68
|
+
{ value: 'unlisted', label: 'Visibility (listed/unlisted)' },
|
|
76
69
|
],
|
|
77
70
|
});
|
|
78
|
-
if (p.isCancel(
|
|
71
|
+
if (p.isCancel(fields)) {
|
|
79
72
|
p.cancel('Cancelled');
|
|
80
73
|
process.exit(0);
|
|
81
74
|
}
|
|
82
|
-
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
75
|
+
const updates = {};
|
|
76
|
+
const selected = new Set(fields);
|
|
77
|
+
if (selected.has('description')) {
|
|
78
|
+
const v = await p.text({
|
|
79
|
+
message: 'Description:',
|
|
80
|
+
defaultValue: project.description,
|
|
81
|
+
placeholder: project.description,
|
|
82
|
+
});
|
|
83
|
+
if (p.isCancel(v)) {
|
|
84
|
+
p.cancel('Cancelled');
|
|
85
|
+
process.exit(0);
|
|
86
|
+
}
|
|
87
|
+
updates.description = v;
|
|
95
88
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
message: 'Product URL:',
|
|
102
|
-
placeholder: linkTypeInput === 'git'
|
|
103
|
-
? `https://github.com/${auth.username}/${slug}`
|
|
104
|
-
: 'https://...',
|
|
105
|
-
defaultValue: project.productUrl || '',
|
|
106
|
-
validate: (v) => {
|
|
107
|
-
if (!v.trim())
|
|
108
|
-
return 'URL is required when link type is selected.';
|
|
109
|
-
try {
|
|
110
|
-
new URL(v);
|
|
111
|
-
}
|
|
112
|
-
catch {
|
|
113
|
-
return 'Invalid URL format.';
|
|
114
|
-
}
|
|
115
|
-
},
|
|
89
|
+
if (selected.has('tags')) {
|
|
90
|
+
const v = await p.text({
|
|
91
|
+
message: 'Tags (comma-separated):',
|
|
92
|
+
defaultValue: project.tags.join(', '),
|
|
93
|
+
placeholder: project.tags.join(', '),
|
|
116
94
|
});
|
|
117
|
-
if (p.isCancel(
|
|
95
|
+
if (p.isCancel(v)) {
|
|
118
96
|
p.cancel('Cancelled');
|
|
119
97
|
process.exit(0);
|
|
120
98
|
}
|
|
121
|
-
|
|
99
|
+
updates.tags = v.split(',').map((t) => t.trim().toLowerCase()).filter(Boolean);
|
|
100
|
+
}
|
|
101
|
+
if (selected.has('category')) {
|
|
102
|
+
const v = await p.select({
|
|
103
|
+
message: 'Category:',
|
|
104
|
+
initialValue: project.category || 'other',
|
|
105
|
+
options: [
|
|
106
|
+
{ value: 'web-app', label: 'Web App' },
|
|
107
|
+
{ value: 'mobile-app', label: 'Mobile App' },
|
|
108
|
+
{ value: 'cli-tool', label: 'CLI Tool' },
|
|
109
|
+
{ value: 'api-backend', label: 'API/Backend' },
|
|
110
|
+
{ value: 'ai-ml', label: 'AI/ML' },
|
|
111
|
+
{ value: 'game', label: 'Game' },
|
|
112
|
+
{ value: 'library', label: 'Library' },
|
|
113
|
+
{ value: 'other', label: 'Other' },
|
|
114
|
+
],
|
|
115
|
+
});
|
|
116
|
+
if (p.isCancel(v)) {
|
|
117
|
+
p.cancel('Cancelled');
|
|
118
|
+
process.exit(0);
|
|
119
|
+
}
|
|
120
|
+
updates.category = v;
|
|
121
|
+
}
|
|
122
|
+
if (selected.has('productUrl')) {
|
|
123
|
+
const linkType = await p.select({
|
|
124
|
+
message: 'Product link:',
|
|
125
|
+
initialValue: project.productUrlType || 'none',
|
|
126
|
+
options: [
|
|
127
|
+
{ value: 'none', label: 'No link' },
|
|
128
|
+
{ value: 'git', label: 'Git Repository' },
|
|
129
|
+
{ value: 'url', label: 'Website / URL' },
|
|
130
|
+
],
|
|
131
|
+
});
|
|
132
|
+
if (p.isCancel(linkType)) {
|
|
133
|
+
p.cancel('Cancelled');
|
|
134
|
+
process.exit(0);
|
|
135
|
+
}
|
|
136
|
+
if (linkType === 'none') {
|
|
137
|
+
updates.productUrl = '';
|
|
138
|
+
updates.productUrlType = '';
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
updates.productUrlType = linkType;
|
|
142
|
+
const urlInput = await p.text({
|
|
143
|
+
message: 'Product URL:',
|
|
144
|
+
placeholder: linkType === 'git'
|
|
145
|
+
? `https://github.com/${auth.username}/${slug}`
|
|
146
|
+
: 'https://...',
|
|
147
|
+
defaultValue: project.productUrl || '',
|
|
148
|
+
validate: (v) => {
|
|
149
|
+
if (!v.trim())
|
|
150
|
+
return 'URL is required when link type is selected.';
|
|
151
|
+
try {
|
|
152
|
+
new URL(v);
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
return 'Invalid URL format.';
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
if (p.isCancel(urlInput)) {
|
|
160
|
+
p.cancel('Cancelled');
|
|
161
|
+
process.exit(0);
|
|
162
|
+
}
|
|
163
|
+
updates.productUrl = urlInput;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (selected.has('unlisted')) {
|
|
167
|
+
const v = await p.confirm({
|
|
168
|
+
message: 'Unlisted? (hidden from explore, accessible via direct URL)',
|
|
169
|
+
initialValue: project.unlisted ?? false,
|
|
170
|
+
});
|
|
171
|
+
if (p.isCancel(v)) {
|
|
172
|
+
p.cancel('Cancelled');
|
|
173
|
+
process.exit(0);
|
|
174
|
+
}
|
|
175
|
+
updates.unlisted = !!v;
|
|
122
176
|
}
|
|
123
177
|
const s2 = p.spinner();
|
|
124
178
|
s2.start('Updating...');
|
|
125
179
|
try {
|
|
126
|
-
await editProject(auth.token, slug,
|
|
127
|
-
description: description,
|
|
128
|
-
tags,
|
|
129
|
-
category: category,
|
|
130
|
-
productUrl,
|
|
131
|
-
productUrlType,
|
|
132
|
-
});
|
|
180
|
+
await editProject(auth.token, slug, updates);
|
|
133
181
|
s2.stop('Updated!');
|
|
134
182
|
p.log.success(`Project "${slug}" has been updated.`);
|
|
135
183
|
}
|
package/dist/mcp.js
CHANGED
|
@@ -15,7 +15,7 @@ import { createRequire } from 'module';
|
|
|
15
15
|
import { isInitialized, loadConfig, saveConfig, getDocsDir, getHistoryDir } from './lib/config.js';
|
|
16
16
|
import { createFullSnapshot, getAllSnapshots, getTrackedFiles, resolveFullSnapshot } from './lib/history.js';
|
|
17
17
|
import { computeQuality } from './lib/quality.js';
|
|
18
|
-
import { getPlanProgress, savePlanProgress, savePlanDocuments, PLAN_QUESTIONS } from './lib/plan.js';
|
|
18
|
+
import { getPlanProgress, savePlanProgress, savePlanDocuments, PLAN_QUESTIONS, generatePlanDocument, generateAIPrompt } from './lib/plan.js';
|
|
19
19
|
import { isGitRepo } from './lib/git.js';
|
|
20
20
|
import { diffSnapshots } from './lib/diff.js';
|
|
21
21
|
import { loadAuth } from './lib/auth.js';
|
|
@@ -689,6 +689,134 @@ server.tool('pmpt_publish', 'Publish the project to pmptwiki.com. Non-interactiv
|
|
|
689
689
|
return { content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }], isError: true };
|
|
690
690
|
}
|
|
691
691
|
});
|
|
692
|
+
server.tool('pmpt_edit_plan', 'Edit existing plan fields (productIdea, coreFeatures, techStack, additionalContext, projectName). Use this to update plan content — e.g., translate productIdea to English, add new features, change tech stack. Only provided fields are updated; others stay unchanged. Regenerates plan.md and pmpt.ai.md automatically. pmpt.md progress/log sections are preserved — only the plan-derived sections (Product Idea, Features, Tech Stack, Additional Context) are updated.', {
|
|
693
|
+
projectPath: z.string().optional().describe('Project root path. Defaults to cwd.'),
|
|
694
|
+
projectName: z.string().optional().describe('New project name.'),
|
|
695
|
+
productIdea: z.string().optional().describe('New product idea description.'),
|
|
696
|
+
coreFeatures: z.string().optional().describe('New core features (comma or semicolon separated).'),
|
|
697
|
+
techStack: z.string().optional().describe('New tech stack preference.'),
|
|
698
|
+
additionalContext: z.string().optional().describe('New additional context.'),
|
|
699
|
+
}, async ({ projectPath, projectName, productIdea, coreFeatures, techStack, additionalContext }) => {
|
|
700
|
+
try {
|
|
701
|
+
const pp = resolveProjectPath(projectPath);
|
|
702
|
+
assertInitialized(pp);
|
|
703
|
+
const progress = getPlanProgress(pp);
|
|
704
|
+
if (!progress?.completed || !progress.answers) {
|
|
705
|
+
return { content: [{ type: 'text', text: 'No plan found. Run pmpt_plan first to create a plan.' }], isError: true };
|
|
706
|
+
}
|
|
707
|
+
// Track what changed
|
|
708
|
+
const changes = [];
|
|
709
|
+
const answers = { ...progress.answers };
|
|
710
|
+
if (projectName !== undefined) {
|
|
711
|
+
answers.projectName = projectName;
|
|
712
|
+
changes.push(`projectName → "${projectName}"`);
|
|
713
|
+
}
|
|
714
|
+
if (productIdea !== undefined) {
|
|
715
|
+
answers.productIdea = productIdea;
|
|
716
|
+
changes.push(`productIdea updated`);
|
|
717
|
+
}
|
|
718
|
+
if (coreFeatures !== undefined) {
|
|
719
|
+
answers.coreFeatures = coreFeatures;
|
|
720
|
+
changes.push(`coreFeatures updated`);
|
|
721
|
+
}
|
|
722
|
+
if (techStack !== undefined) {
|
|
723
|
+
answers.techStack = techStack;
|
|
724
|
+
changes.push(`techStack → "${techStack}"`);
|
|
725
|
+
}
|
|
726
|
+
if (additionalContext !== undefined) {
|
|
727
|
+
answers.additionalContext = additionalContext;
|
|
728
|
+
changes.push(`additionalContext updated`);
|
|
729
|
+
}
|
|
730
|
+
if (changes.length === 0) {
|
|
731
|
+
return { content: [{ type: 'text', text: 'No fields provided. Pass at least one field to update.' }] };
|
|
732
|
+
}
|
|
733
|
+
// Update plan-progress.json
|
|
734
|
+
savePlanProgress(pp, { ...progress, answers });
|
|
735
|
+
// Regenerate plan.md and pmpt.ai.md
|
|
736
|
+
const docsDir = getDocsDir(pp);
|
|
737
|
+
const planPath = join(docsDir, 'plan.md');
|
|
738
|
+
writeFileSync(planPath, generatePlanDocument(answers), 'utf-8');
|
|
739
|
+
const aiMdPath = join(docsDir, 'pmpt.ai.md');
|
|
740
|
+
writeFileSync(aiMdPath, generateAIPrompt(answers), 'utf-8');
|
|
741
|
+
// Update pmpt.md plan-derived sections (preserve progress, log, decisions)
|
|
742
|
+
const pmptMdPath = join(docsDir, 'pmpt.md');
|
|
743
|
+
if (existsSync(pmptMdPath)) {
|
|
744
|
+
let content = readFileSync(pmptMdPath, 'utf-8');
|
|
745
|
+
// Update title (first # heading)
|
|
746
|
+
content = content.replace(/^# .+$/m, `# ${answers.projectName}`);
|
|
747
|
+
// Update Product Idea section
|
|
748
|
+
const ideaRegex = /## Product Idea\n[\s\S]*?(?=\n## )/;
|
|
749
|
+
if (ideaRegex.test(content)) {
|
|
750
|
+
content = content.replace(ideaRegex, `## Product Idea\n${answers.productIdea}\n`);
|
|
751
|
+
}
|
|
752
|
+
// Update Features section (only unchecked items get replaced; checked items are preserved)
|
|
753
|
+
const featuresRegex = /## Features\n[\s\S]*?(?=\n## )/;
|
|
754
|
+
if (featuresRegex.test(content)) {
|
|
755
|
+
const existingMatch = content.match(featuresRegex);
|
|
756
|
+
if (existingMatch) {
|
|
757
|
+
// Extract already-checked features
|
|
758
|
+
const checked = existingMatch[0]
|
|
759
|
+
.split('\n')
|
|
760
|
+
.filter(line => line.startsWith('- [x]'))
|
|
761
|
+
.join('\n');
|
|
762
|
+
const newFeatures = answers.coreFeatures
|
|
763
|
+
.split(/[,;\n]/)
|
|
764
|
+
.map((f) => f.trim())
|
|
765
|
+
.filter((f) => f)
|
|
766
|
+
.map((f) => `- [ ] ${f}`)
|
|
767
|
+
.join('\n');
|
|
768
|
+
const featuresSection = checked
|
|
769
|
+
? `## Features\n${checked}\n${newFeatures}\n`
|
|
770
|
+
: `## Features\n${newFeatures}\n`;
|
|
771
|
+
content = content.replace(featuresRegex, featuresSection);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
// Update Tech Stack section
|
|
775
|
+
if (answers.techStack) {
|
|
776
|
+
const techRegex = /## Tech Stack\n[\s\S]*?(?=\n## )/;
|
|
777
|
+
if (techRegex.test(content)) {
|
|
778
|
+
content = content.replace(techRegex, `## Tech Stack\n${answers.techStack}\n`);
|
|
779
|
+
}
|
|
780
|
+
else {
|
|
781
|
+
// Insert before Progress section
|
|
782
|
+
const progressIdx = content.indexOf('\n## Progress');
|
|
783
|
+
if (progressIdx !== -1) {
|
|
784
|
+
content = content.slice(0, progressIdx) + `\n## Tech Stack\n${answers.techStack}\n` + content.slice(progressIdx);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
// Update Additional Context section
|
|
789
|
+
if (answers.additionalContext) {
|
|
790
|
+
const ctxRegex = /## Additional Context\n[\s\S]*?(?=\n## )/;
|
|
791
|
+
if (ctxRegex.test(content)) {
|
|
792
|
+
content = content.replace(ctxRegex, `## Additional Context\n${answers.additionalContext}\n`);
|
|
793
|
+
}
|
|
794
|
+
else {
|
|
795
|
+
const ideaEnd = content.indexOf('\n## ', content.indexOf('## Product Idea') + 1);
|
|
796
|
+
if (ideaEnd !== -1) {
|
|
797
|
+
content = content.slice(0, ideaEnd) + `\n## Additional Context\n${answers.additionalContext}\n` + content.slice(ideaEnd);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
writeFileSync(pmptMdPath, content, 'utf-8');
|
|
802
|
+
}
|
|
803
|
+
return {
|
|
804
|
+
content: [{
|
|
805
|
+
type: 'text',
|
|
806
|
+
text: [
|
|
807
|
+
`Plan updated:`,
|
|
808
|
+
...changes.map(c => ` - ${c}`),
|
|
809
|
+
'',
|
|
810
|
+
'Regenerated: plan.md, pmpt.ai.md, pmpt.md (progress preserved)',
|
|
811
|
+
'Run pmpt_save to snapshot this change.',
|
|
812
|
+
].join('\n'),
|
|
813
|
+
}],
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
catch (error) {
|
|
817
|
+
return { content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }], isError: true };
|
|
818
|
+
}
|
|
819
|
+
});
|
|
692
820
|
server.tool('pmpt_graduate', 'Graduate a project on pmptwiki — archives it with a Hall of Fame badge. The project can no longer be updated. Non-interactive. User must have run `pmpt login` once before.', {
|
|
693
821
|
slug: z.string().describe('Project slug to graduate.'),
|
|
694
822
|
note: z.string().optional().describe('Graduation note (e.g., "Reached 1000 users!").'),
|