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.
@@ -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
- const description = await p.text({
43
- message: 'Description:',
44
- defaultValue: project.description,
45
- placeholder: project.description,
46
- });
47
- if (p.isCancel(description)) {
48
- p.cancel('Cancelled');
49
- process.exit(0);
50
- }
51
- const tagsInput = await p.text({
52
- message: 'Tags (comma-separated):',
53
- defaultValue: project.tags.join(', '),
54
- placeholder: project.tags.join(', '),
55
- });
56
- if (p.isCancel(tagsInput)) {
57
- p.cancel('Cancelled');
58
- process.exit(0);
59
- }
60
- const tags = tagsInput
61
- .split(',')
62
- .map((t) => t.trim().toLowerCase())
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: 'web-app', label: 'Web App' },
69
- { value: 'mobile-app', label: 'Mobile App' },
70
- { value: 'cli-tool', label: 'CLI Tool' },
71
- { value: 'api-backend', label: 'API/Backend' },
72
- { value: 'ai-ml', label: 'AI/ML' },
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(category)) {
71
+ if (p.isCancel(fields)) {
79
72
  p.cancel('Cancelled');
80
73
  process.exit(0);
81
74
  }
82
- // Product link (optional)
83
- const linkTypeInput = await p.select({
84
- message: 'Product link (optional):',
85
- initialValue: project.productUrlType || 'none',
86
- options: [
87
- { value: 'none', label: 'No link' },
88
- { value: 'git', label: 'Git Repository' },
89
- { value: 'url', label: 'Website / URL' },
90
- ],
91
- });
92
- if (p.isCancel(linkTypeInput)) {
93
- p.cancel('Cancelled');
94
- process.exit(0);
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
- let productUrl = '';
97
- let productUrlType = '';
98
- if (linkTypeInput !== 'none') {
99
- productUrlType = linkTypeInput;
100
- const productUrlInput = await p.text({
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(productUrlInput)) {
95
+ if (p.isCancel(v)) {
118
96
  p.cancel('Cancelled');
119
97
  process.exit(0);
120
98
  }
121
- productUrl = productUrlInput;
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!").'),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmpt-cli",
3
- "version": "1.14.7",
3
+ "version": "1.14.9",
4
4
  "description": "Record and share your AI-driven product development journey",
5
5
  "type": "module",
6
6
  "bin": {