pmpt-cli 1.12.0 → 1.12.2
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 +49 -15
- package/dist/commands/clone.js +8 -10
- package/dist/commands/init.js +14 -11
- package/dist/commands/plan.js +9 -12
- package/dist/commands/publish.js +108 -74
- package/dist/commands/update.js +162 -0
- package/dist/index.js +6 -0
- package/dist/lib/config.js +2 -0
- package/dist/lib/git.js +7 -0
- package/dist/lib/plan.js +23 -12
- package/dist/lib/pmptFile.js +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,17 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
# pmpt
|
|
4
4
|
|
|
5
|
-
**
|
|
5
|
+
**Record and share your AI-driven product development journey.**
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/pmpt-cli)
|
|
8
8
|
[](https://github.com/pmptwiki/pmpt-cli/blob/main/LICENSE)
|
|
9
9
|
[](https://github.com/pmptwiki/pmpt-cli)
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Plan with 5 questions. Build with AI. Save every version. Share and reproduce.
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
[Quick Start](#quick-start) · [Commands](#commands) · [How It Works](#how-it-works) · [Explore Projects](#explore-projects)
|
|
13
|
+
[Quick Start](#quick-start) · [Commands](#commands) · [MCP Server](#mcp-server) · [Explore Projects](#explore-projects)
|
|
16
14
|
|
|
17
15
|
</div>
|
|
18
16
|
|
|
@@ -57,7 +55,7 @@ pmpt save
|
|
|
57
55
|
pmpt login && pmpt publish
|
|
58
56
|
|
|
59
57
|
# Bonus: Explore what others are building
|
|
60
|
-
pmpt
|
|
58
|
+
pmpt explore
|
|
61
59
|
```
|
|
62
60
|
|
|
63
61
|
---
|
|
@@ -113,17 +111,51 @@ The generated prompt is **automatically copied to your clipboard**. Just paste i
|
|
|
113
111
|
| Command | Description |
|
|
114
112
|
|---------|-------------|
|
|
115
113
|
| `pmpt login` | Authenticate via GitHub (one-time) |
|
|
116
|
-
| `pmpt publish` | Publish your project (requires
|
|
117
|
-
| `pmpt
|
|
114
|
+
| `pmpt publish` | Publish your project (requires quality score ≥ 40) |
|
|
115
|
+
| `pmpt update` | Quick re-publish: update content without changing metadata |
|
|
118
116
|
| `pmpt edit` | Edit published project metadata (description, tags, category) |
|
|
119
117
|
| `pmpt unpublish` | Remove a published project from pmptwiki |
|
|
120
118
|
| `pmpt clone <slug>` | Clone and reproduce someone's project |
|
|
121
|
-
| `pmpt
|
|
119
|
+
| `pmpt explore` | Open pmptwiki.com/explore in your browser |
|
|
120
|
+
|
|
121
|
+
> Quality score below 40? pmpt copies an **AI improvement prompt** to your clipboard — paste it into your AI tool to get help improving your project.
|
|
122
122
|
|
|
123
123
|
> See the full documentation at [pmptwiki.com/docs](https://pmptwiki.com/docs)
|
|
124
124
|
|
|
125
125
|
---
|
|
126
126
|
|
|
127
|
+
## MCP Server
|
|
128
|
+
|
|
129
|
+
pmpt includes a built-in [MCP](https://modelcontextprotocol.io) server so AI tools can interact with pmpt directly. This means Claude Code, Cursor, and other MCP-compatible tools can save snapshots, check status, and review history without you typing commands.
|
|
130
|
+
|
|
131
|
+
### Setup
|
|
132
|
+
|
|
133
|
+
Add to your `.mcp.json` (or IDE MCP config):
|
|
134
|
+
|
|
135
|
+
```json
|
|
136
|
+
{
|
|
137
|
+
"mcpServers": {
|
|
138
|
+
"pmpt": {
|
|
139
|
+
"command": "pmpt-mcp"
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Available Tools
|
|
146
|
+
|
|
147
|
+
| Tool | Description |
|
|
148
|
+
|------|-------------|
|
|
149
|
+
| `pmpt_save` | Save a snapshot after completing features, fixes, or milestones |
|
|
150
|
+
| `pmpt_status` | Check tracked files, snapshot count, and quality score |
|
|
151
|
+
| `pmpt_history` | View version history with git commit info |
|
|
152
|
+
| `pmpt_diff` | Compare two versions, or a version against working copy |
|
|
153
|
+
| `pmpt_quality` | Check quality score and publish readiness |
|
|
154
|
+
|
|
155
|
+
All tools accept an optional `projectPath` parameter (defaults to cwd).
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
127
159
|
## How It Works
|
|
128
160
|
|
|
129
161
|
```
|
|
@@ -158,7 +190,8 @@ your-project/
|
|
|
158
190
|
├── config.json # Project configuration
|
|
159
191
|
├── docs/ # Generated documents
|
|
160
192
|
│ ├── plan.md # Product plan (features checklist)
|
|
161
|
-
│
|
|
193
|
+
│ ├── pmpt.md # Progress tracking & decisions
|
|
194
|
+
│ └── pmpt.ai.md # AI-ready prompt (project context & instructions)
|
|
162
195
|
└── .history/ # Auto-saved version history
|
|
163
196
|
├── v1-2024-02-20/
|
|
164
197
|
├── v2-2024-02-21/
|
|
@@ -186,7 +219,8 @@ A single portable file containing your entire development journey:
|
|
|
186
219
|
},
|
|
187
220
|
"docs": {
|
|
188
221
|
"plan.md": "...",
|
|
189
|
-
"pmpt.md": "..."
|
|
222
|
+
"pmpt.md": "...",
|
|
223
|
+
"pmpt.ai.md": "..."
|
|
190
224
|
},
|
|
191
225
|
"history": [
|
|
192
226
|
{ "version": 1, "timestamp": "...", "files": {} },
|
|
@@ -210,11 +244,11 @@ Share it. Clone it. Reproduce it.
|
|
|
210
244
|
|
|
211
245
|
## Explore Projects
|
|
212
246
|
|
|
213
|
-
Don't know what to build?
|
|
247
|
+
Don't know what to build? See what others have created with AI.
|
|
214
248
|
|
|
215
249
|
```bash
|
|
216
|
-
#
|
|
217
|
-
pmpt
|
|
250
|
+
# Open the explore page
|
|
251
|
+
pmpt explore
|
|
218
252
|
|
|
219
253
|
# Found something interesting? Clone it and make it yours
|
|
220
254
|
pmpt clone budget-tracker-app
|
|
@@ -222,7 +256,7 @@ pmpt clone budget-tracker-app
|
|
|
222
256
|
|
|
223
257
|
**[Explore Projects on pmptwiki.com →](https://pmptwiki.com/explore)**
|
|
224
258
|
|
|
225
|
-
|
|
259
|
+
Clone any project to see how it was planned, what prompts were used, and how it evolved step by step. The clone output shows the product idea, tech stack, and full version history.
|
|
226
260
|
|
|
227
261
|
---
|
|
228
262
|
|
package/dist/commands/clone.js
CHANGED
|
@@ -190,22 +190,24 @@ export async function cmdClone(slug) {
|
|
|
190
190
|
// Copy AI prompt to clipboard
|
|
191
191
|
const aiContent = readFileSync(aiMdPath, 'utf-8');
|
|
192
192
|
const copied = copyToClipboard(aiContent);
|
|
193
|
+
p.log.info('Tips:');
|
|
194
|
+
p.log.message(' pmpt history — view version history');
|
|
195
|
+
p.log.message(' pmpt plan — view or edit AI prompt');
|
|
196
|
+
p.log.message(' pmpt save — save a new snapshot');
|
|
197
|
+
p.log.message('');
|
|
193
198
|
if (copied) {
|
|
194
|
-
p.log.message('');
|
|
195
|
-
p.log.success('AI prompt copied to clipboard!');
|
|
196
|
-
p.log.message('');
|
|
197
199
|
const banner = [
|
|
198
|
-
'',
|
|
199
200
|
'┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓',
|
|
200
201
|
'┃ ┃',
|
|
201
202
|
'┃ 📋 NEXT STEP ┃',
|
|
202
203
|
'┃ ┃',
|
|
203
|
-
'┃
|
|
204
|
+
'┃ AI prompt is already copied to clipboard! ┃',
|
|
205
|
+
'┃ Open your AI coding tool and paste it: ┃',
|
|
204
206
|
'┃ ┃',
|
|
205
207
|
'┃ ⌘ + V (Mac) ┃',
|
|
206
208
|
'┃ Ctrl + V (Windows/Linux) ┃',
|
|
207
209
|
'┃ ┃',
|
|
208
|
-
'┃ Your
|
|
210
|
+
'┃ Your project context is ready! 🚀 ┃',
|
|
209
211
|
'┃ ┃',
|
|
210
212
|
'┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛',
|
|
211
213
|
'',
|
|
@@ -216,9 +218,5 @@ export async function cmdClone(slug) {
|
|
|
216
218
|
p.log.warn('Could not copy to clipboard.');
|
|
217
219
|
p.log.info(`Read it at: ${aiMdPath}`);
|
|
218
220
|
}
|
|
219
|
-
p.log.info('Tips:');
|
|
220
|
-
p.log.message(' pmpt history — view version history');
|
|
221
|
-
p.log.message(' pmpt plan — view or edit AI prompt');
|
|
222
|
-
p.log.message(' pmpt save — save a new snapshot');
|
|
223
221
|
p.outro('Project cloned!');
|
|
224
222
|
}
|
package/dist/commands/init.js
CHANGED
|
@@ -2,7 +2,7 @@ import * as p from '@clack/prompts';
|
|
|
2
2
|
import { existsSync, readFileSync } from 'fs';
|
|
3
3
|
import { resolve } from 'path';
|
|
4
4
|
import { initializeProject, isInitialized } from '../lib/config.js';
|
|
5
|
-
import { isGitRepo, getGitInfo, formatGitInfo } from '../lib/git.js';
|
|
5
|
+
import { isGitRepo, getGitInfo, formatGitInfo, getCommitCount } from '../lib/git.js';
|
|
6
6
|
import { cmdPlan } from './plan.js';
|
|
7
7
|
import { scanProject, scanResultToAnswers } from '../lib/scanner.js';
|
|
8
8
|
import { savePlanDocuments, initPlanProgress, savePlanProgress } from '../lib/plan.js';
|
|
@@ -84,12 +84,17 @@ export async function cmdInit(path, options) {
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
|
+
// Detect project origin
|
|
88
|
+
const gitCommits = isGit ? getCommitCount(projectPath) : 0;
|
|
89
|
+
const origin = gitCommits >= 10 ? 'adopted' : 'new';
|
|
87
90
|
const s = p.spinner();
|
|
88
91
|
s.start('Initializing project...');
|
|
89
92
|
try {
|
|
90
93
|
const config = initializeProject(projectPath, {
|
|
91
94
|
repo: repoUrl,
|
|
92
95
|
trackGit: isGit,
|
|
96
|
+
origin,
|
|
97
|
+
gitCommitsAtInit: isGit ? gitCommits : undefined,
|
|
93
98
|
});
|
|
94
99
|
s.stop('Initialized');
|
|
95
100
|
// Build folder structure display
|
|
@@ -171,7 +176,7 @@ export async function cmdInit(path, options) {
|
|
|
171
176
|
const s2 = p.spinner();
|
|
172
177
|
s2.start('Scanning project and generating plan...');
|
|
173
178
|
const answers = scanResultToAnswers(scanResult, userDesc);
|
|
174
|
-
const { planPath, promptPath } = savePlanDocuments(projectPath, answers);
|
|
179
|
+
const { planPath, promptPath } = savePlanDocuments(projectPath, answers, origin);
|
|
175
180
|
const progress = initPlanProgress(projectPath);
|
|
176
181
|
progress.completed = true;
|
|
177
182
|
progress.answers = answers;
|
|
@@ -192,17 +197,19 @@ export async function cmdInit(path, options) {
|
|
|
192
197
|
// Copy to clipboard
|
|
193
198
|
const content = readFileSync(promptPath, 'utf-8');
|
|
194
199
|
const copied = copyToClipboard(content);
|
|
200
|
+
p.log.info('Tips:');
|
|
201
|
+
p.log.message(' pmpt plan — View or edit your AI prompt');
|
|
202
|
+
p.log.message(' pmpt save — Save a snapshot anytime');
|
|
203
|
+
p.log.message(' pmpt watch — Auto-save on file changes');
|
|
204
|
+
p.log.message('');
|
|
195
205
|
if (copied) {
|
|
196
|
-
p.log.message('');
|
|
197
|
-
p.log.success('AI prompt copied to clipboard!');
|
|
198
|
-
p.log.message('');
|
|
199
206
|
const banner = [
|
|
200
|
-
'',
|
|
201
207
|
'┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓',
|
|
202
208
|
'┃ ┃',
|
|
203
209
|
'┃ 📋 NEXT STEP ┃',
|
|
204
210
|
'┃ ┃',
|
|
205
|
-
'┃
|
|
211
|
+
'┃ AI prompt is already copied to clipboard! ┃',
|
|
212
|
+
'┃ Open your AI coding tool and paste it: ┃',
|
|
206
213
|
'┃ ┃',
|
|
207
214
|
'┃ ⌘ + V (Mac) ┃',
|
|
208
215
|
'┃ Ctrl + V (Windows/Linux) ┃',
|
|
@@ -218,10 +225,6 @@ export async function cmdInit(path, options) {
|
|
|
218
225
|
p.log.warn('Could not copy to clipboard.');
|
|
219
226
|
p.log.info(`Read it at: ${promptPath}`);
|
|
220
227
|
}
|
|
221
|
-
p.log.info('Tips:');
|
|
222
|
-
p.log.message(' pmpt plan — View or edit your AI prompt');
|
|
223
|
-
p.log.message(' pmpt save — Save a snapshot anytime');
|
|
224
|
-
p.log.message(' pmpt watch — Auto-save on file changes');
|
|
225
228
|
p.outro('Ready to go!');
|
|
226
229
|
}
|
|
227
230
|
else if (scanChoice === 'manual') {
|
package/dist/commands/plan.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as p from '@clack/prompts';
|
|
2
2
|
import { resolve } from 'path';
|
|
3
3
|
import { existsSync, readFileSync } from 'fs';
|
|
4
|
-
import { isInitialized } from '../lib/config.js';
|
|
4
|
+
import { isInitialized, loadConfig } 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';
|
|
@@ -88,16 +88,14 @@ export async function cmdPlan(path, options) {
|
|
|
88
88
|
if (action === 'copy') {
|
|
89
89
|
const copied = copyToClipboard(content);
|
|
90
90
|
if (copied) {
|
|
91
|
-
p.log.success('AI prompt copied to clipboard!');
|
|
92
91
|
p.log.message('');
|
|
93
|
-
// Eye-catching next step banner
|
|
94
92
|
const banner = [
|
|
95
|
-
'',
|
|
96
93
|
'┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓',
|
|
97
94
|
'┃ ┃',
|
|
98
95
|
'┃ 📋 NEXT STEP ┃',
|
|
99
96
|
'┃ ┃',
|
|
100
|
-
'┃
|
|
97
|
+
'┃ AI prompt is already copied to clipboard! ┃',
|
|
98
|
+
'┃ Open your AI coding tool and paste it: ┃',
|
|
101
99
|
'┃ ┃',
|
|
102
100
|
'┃ ⌘ + V (Mac) ┃',
|
|
103
101
|
'┃ Ctrl + V (Windows/Linux) ┃',
|
|
@@ -159,9 +157,10 @@ export async function cmdPlan(path, options) {
|
|
|
159
157
|
p.outro('');
|
|
160
158
|
process.exit(1);
|
|
161
159
|
}
|
|
160
|
+
const config = loadConfig(projectPath);
|
|
162
161
|
const s = p.spinner();
|
|
163
162
|
s.start('Generating documents from answers file...');
|
|
164
|
-
const { planPath, promptPath } = savePlanDocuments(projectPath, answers);
|
|
163
|
+
const { planPath, promptPath } = savePlanDocuments(projectPath, answers, config?.origin);
|
|
165
164
|
progress.completed = true;
|
|
166
165
|
progress.answers = answers;
|
|
167
166
|
savePlanProgress(projectPath, progress);
|
|
@@ -197,9 +196,10 @@ export async function cmdPlan(path, options) {
|
|
|
197
196
|
answers[question.key] = answer;
|
|
198
197
|
}
|
|
199
198
|
// Generate documents
|
|
199
|
+
const config = loadConfig(projectPath);
|
|
200
200
|
const s = p.spinner();
|
|
201
201
|
s.start('Generating your AI prompt...');
|
|
202
|
-
const { planPath, promptPath } = savePlanDocuments(projectPath, answers);
|
|
202
|
+
const { planPath, promptPath } = savePlanDocuments(projectPath, answers, config?.origin);
|
|
203
203
|
// Update progress
|
|
204
204
|
progress.completed = true;
|
|
205
205
|
progress.answers = answers;
|
|
@@ -233,16 +233,13 @@ export async function cmdPlan(path, options) {
|
|
|
233
233
|
const copied = copyToClipboard(content);
|
|
234
234
|
if (copied) {
|
|
235
235
|
p.log.message('');
|
|
236
|
-
p.log.success('AI prompt copied to clipboard!');
|
|
237
|
-
p.log.message('');
|
|
238
|
-
// Eye-catching next step banner
|
|
239
236
|
const banner = [
|
|
240
|
-
'',
|
|
241
237
|
'┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓',
|
|
242
238
|
'┃ ┃',
|
|
243
239
|
'┃ 📋 NEXT STEP ┃',
|
|
244
240
|
'┃ ┃',
|
|
245
|
-
'┃
|
|
241
|
+
'┃ AI prompt is already copied to clipboard! ┃',
|
|
242
|
+
'┃ Open your AI coding tool and paste it: ┃',
|
|
246
243
|
'┃ ┃',
|
|
247
244
|
'┃ ⌘ + V (Mac) ┃',
|
|
248
245
|
'┃ Ctrl + V (Windows/Linux) ┃',
|
package/dist/commands/publish.js
CHANGED
|
@@ -224,89 +224,120 @@ export async function cmdPublish(path, options) {
|
|
|
224
224
|
}
|
|
225
225
|
}
|
|
226
226
|
else {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
p.
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
if (p.isCancel(descriptionInput)) {
|
|
248
|
-
p.cancel('Cancelled');
|
|
249
|
-
process.exit(0);
|
|
250
|
-
}
|
|
251
|
-
description = descriptionInput;
|
|
252
|
-
const tagsInput = await p.text({
|
|
253
|
-
message: 'Tags (comma-separated):',
|
|
254
|
-
placeholder: 'react, saas, mvp',
|
|
255
|
-
defaultValue: existing?.tags?.join(', ') || '',
|
|
256
|
-
});
|
|
257
|
-
if (p.isCancel(tagsInput)) {
|
|
258
|
-
p.cancel('Cancelled');
|
|
259
|
-
process.exit(0);
|
|
227
|
+
// If previously published, offer to reuse settings
|
|
228
|
+
let reuseExisting = false;
|
|
229
|
+
if (existing && savedSlug) {
|
|
230
|
+
const categoryLabel = CATEGORY_OPTIONS.find((o) => o.value === existing.category)?.label ?? existing.category ?? '';
|
|
231
|
+
p.note([
|
|
232
|
+
`Slug: ${savedSlug}`,
|
|
233
|
+
`Description: ${existing.description || '(none)'}`,
|
|
234
|
+
existing.tags?.length ? `Tags: ${existing.tags.join(', ')}` : '',
|
|
235
|
+
categoryLabel ? `Category: ${categoryLabel}` : '',
|
|
236
|
+
existing.productUrl ? `Product: ${existing.productUrl}` : '',
|
|
237
|
+
].filter(Boolean).join('\n'), 'Previous Settings');
|
|
238
|
+
const reuse = await p.confirm({
|
|
239
|
+
message: 'Publish with same settings?',
|
|
240
|
+
initialValue: true,
|
|
241
|
+
});
|
|
242
|
+
if (p.isCancel(reuse)) {
|
|
243
|
+
p.cancel('Cancelled');
|
|
244
|
+
process.exit(0);
|
|
245
|
+
}
|
|
246
|
+
reuseExisting = !!reuse;
|
|
260
247
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
p.cancel('Cancelled');
|
|
269
|
-
process.exit(0);
|
|
248
|
+
if (reuseExisting && existing && savedSlug) {
|
|
249
|
+
slug = savedSlug;
|
|
250
|
+
description = existing.description || '';
|
|
251
|
+
tags = existing.tags || [];
|
|
252
|
+
category = existing.category || 'other';
|
|
253
|
+
productUrl = existing.productUrl || '';
|
|
254
|
+
productUrlType = existing.productUrlType || '';
|
|
270
255
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
options: [
|
|
277
|
-
{ value: 'none', label: 'No link' },
|
|
278
|
-
{ value: 'git', label: 'Git Repository' },
|
|
279
|
-
{ value: 'url', label: 'Website / URL' },
|
|
280
|
-
],
|
|
281
|
-
});
|
|
282
|
-
if (p.isCancel(linkTypeInput)) {
|
|
283
|
-
p.cancel('Cancelled');
|
|
284
|
-
process.exit(0);
|
|
285
|
-
}
|
|
286
|
-
if (linkTypeInput !== 'none') {
|
|
287
|
-
productUrlType = linkTypeInput;
|
|
288
|
-
const productUrlInput = await p.text({
|
|
289
|
-
message: 'Product URL:',
|
|
290
|
-
placeholder: linkTypeInput === 'git'
|
|
291
|
-
? `https://github.com/${auth.username}/${slug}`
|
|
292
|
-
: 'https://...',
|
|
293
|
-
defaultValue: existing?.productUrl || '',
|
|
256
|
+
else {
|
|
257
|
+
const slugInput = await p.text({
|
|
258
|
+
message: 'Project slug (used in URL):',
|
|
259
|
+
placeholder: defaultSlug,
|
|
260
|
+
defaultValue: savedSlug || '',
|
|
294
261
|
validate: (v) => {
|
|
295
|
-
if (
|
|
296
|
-
return '
|
|
297
|
-
try {
|
|
298
|
-
new URL(v);
|
|
299
|
-
}
|
|
300
|
-
catch {
|
|
301
|
-
return 'Invalid URL format.';
|
|
262
|
+
if (!/^[a-z0-9][a-z0-9-]{1,48}[a-z0-9]$/.test(v)) {
|
|
263
|
+
return '3-50 chars, lowercase letters, numbers, and hyphens only.';
|
|
302
264
|
}
|
|
303
265
|
},
|
|
304
266
|
});
|
|
305
|
-
if (p.isCancel(
|
|
267
|
+
if (p.isCancel(slugInput)) {
|
|
268
|
+
p.cancel('Cancelled');
|
|
269
|
+
process.exit(0);
|
|
270
|
+
}
|
|
271
|
+
slug = slugInput;
|
|
272
|
+
const descriptionInput = await p.text({
|
|
273
|
+
message: 'Project description (brief):',
|
|
274
|
+
placeholder: existing?.description || planProgress?.answers?.productIdea?.slice(0, 100) || '',
|
|
275
|
+
defaultValue: existing?.description || planProgress?.answers?.productIdea?.slice(0, 200) || '',
|
|
276
|
+
});
|
|
277
|
+
if (p.isCancel(descriptionInput)) {
|
|
278
|
+
p.cancel('Cancelled');
|
|
279
|
+
process.exit(0);
|
|
280
|
+
}
|
|
281
|
+
description = descriptionInput;
|
|
282
|
+
const tagsInput = await p.text({
|
|
283
|
+
message: 'Tags (comma-separated):',
|
|
284
|
+
placeholder: 'react, saas, mvp',
|
|
285
|
+
defaultValue: existing?.tags?.join(', ') || '',
|
|
286
|
+
});
|
|
287
|
+
if (p.isCancel(tagsInput)) {
|
|
306
288
|
p.cancel('Cancelled');
|
|
307
289
|
process.exit(0);
|
|
308
290
|
}
|
|
309
|
-
|
|
291
|
+
tags = normalizeTags(tagsInput);
|
|
292
|
+
const categoryInput = await p.select({
|
|
293
|
+
message: 'Project category:',
|
|
294
|
+
initialValue: existing?.category || 'other',
|
|
295
|
+
options: CATEGORY_OPTIONS,
|
|
296
|
+
});
|
|
297
|
+
if (p.isCancel(categoryInput)) {
|
|
298
|
+
p.cancel('Cancelled');
|
|
299
|
+
process.exit(0);
|
|
300
|
+
}
|
|
301
|
+
category = categoryInput;
|
|
302
|
+
// Product link (optional)
|
|
303
|
+
const linkTypeInput = await p.select({
|
|
304
|
+
message: 'Product link (optional):',
|
|
305
|
+
initialValue: existing?.productUrlType || 'none',
|
|
306
|
+
options: [
|
|
307
|
+
{ value: 'none', label: 'No link' },
|
|
308
|
+
{ value: 'git', label: 'Git Repository' },
|
|
309
|
+
{ value: 'url', label: 'Website / URL' },
|
|
310
|
+
],
|
|
311
|
+
});
|
|
312
|
+
if (p.isCancel(linkTypeInput)) {
|
|
313
|
+
p.cancel('Cancelled');
|
|
314
|
+
process.exit(0);
|
|
315
|
+
}
|
|
316
|
+
if (linkTypeInput !== 'none') {
|
|
317
|
+
productUrlType = linkTypeInput;
|
|
318
|
+
const productUrlInput = await p.text({
|
|
319
|
+
message: 'Product URL:',
|
|
320
|
+
placeholder: linkTypeInput === 'git'
|
|
321
|
+
? `https://github.com/${auth.username}/${slug}`
|
|
322
|
+
: 'https://...',
|
|
323
|
+
defaultValue: existing?.productUrl || '',
|
|
324
|
+
validate: (v) => {
|
|
325
|
+
if (!v.trim())
|
|
326
|
+
return 'URL is required when link type is selected.';
|
|
327
|
+
try {
|
|
328
|
+
new URL(v);
|
|
329
|
+
}
|
|
330
|
+
catch {
|
|
331
|
+
return 'Invalid URL format.';
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
});
|
|
335
|
+
if (p.isCancel(productUrlInput)) {
|
|
336
|
+
p.cancel('Cancelled');
|
|
337
|
+
process.exit(0);
|
|
338
|
+
}
|
|
339
|
+
productUrl = productUrlInput;
|
|
340
|
+
}
|
|
310
341
|
}
|
|
311
342
|
}
|
|
312
343
|
// Build .pmpt content (resolve from optimized snapshots)
|
|
@@ -323,6 +354,8 @@ export async function cmdPublish(path, options) {
|
|
|
323
354
|
description: description,
|
|
324
355
|
createdAt: config?.createdAt || new Date().toISOString(),
|
|
325
356
|
exportedAt: new Date().toISOString(),
|
|
357
|
+
origin: config?.origin,
|
|
358
|
+
gitCommitsAtInit: config?.gitCommitsAtInit,
|
|
326
359
|
};
|
|
327
360
|
const planAnswers = planProgress?.answers
|
|
328
361
|
? {
|
|
@@ -378,6 +411,7 @@ export async function cmdPublish(path, options) {
|
|
|
378
411
|
if (config) {
|
|
379
412
|
config.lastPublished = new Date().toISOString();
|
|
380
413
|
config.lastPublishedSlug = slug;
|
|
414
|
+
config.lastPublishedVersionCount = snapshots.length;
|
|
381
415
|
saveConfig(projectPath, config);
|
|
382
416
|
}
|
|
383
417
|
p.note([
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
import { resolve, basename } from 'path';
|
|
3
|
+
import { readFileSync, existsSync } from 'fs';
|
|
4
|
+
import { isInitialized, loadConfig, saveConfig, getDocsDir } from '../lib/config.js';
|
|
5
|
+
import { getAllSnapshots, resolveFullSnapshot } from '../lib/history.js';
|
|
6
|
+
import { getPlanProgress } from '../lib/plan.js';
|
|
7
|
+
import { createPmptFile } from '../lib/pmptFile.js';
|
|
8
|
+
import { loadAuth } from '../lib/auth.js';
|
|
9
|
+
import { publishProject, fetchProjects } from '../lib/api.js';
|
|
10
|
+
import { computeQuality } from '../lib/quality.js';
|
|
11
|
+
import glob from 'fast-glob';
|
|
12
|
+
import { join } from 'path';
|
|
13
|
+
function readDocsFolder(docsDir) {
|
|
14
|
+
const files = {};
|
|
15
|
+
if (!existsSync(docsDir))
|
|
16
|
+
return files;
|
|
17
|
+
const mdFiles = glob.sync('**/*.md', { cwd: docsDir });
|
|
18
|
+
for (const file of mdFiles) {
|
|
19
|
+
try {
|
|
20
|
+
files[file] = readFileSync(join(docsDir, file), 'utf-8');
|
|
21
|
+
}
|
|
22
|
+
catch { /* skip */ }
|
|
23
|
+
}
|
|
24
|
+
return files;
|
|
25
|
+
}
|
|
26
|
+
export async function cmdUpdate(path) {
|
|
27
|
+
const projectPath = path ? resolve(path) : process.cwd();
|
|
28
|
+
if (!isInitialized(projectPath)) {
|
|
29
|
+
p.log.error('Project not initialized. Run `pmpt init` first.');
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
const auth = loadAuth();
|
|
33
|
+
if (!auth?.token || !auth?.username) {
|
|
34
|
+
p.log.error('Login required. Run `pmpt login` first.');
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
p.intro('pmpt update');
|
|
38
|
+
const config = loadConfig(projectPath);
|
|
39
|
+
const savedSlug = config?.lastPublishedSlug;
|
|
40
|
+
if (!savedSlug) {
|
|
41
|
+
p.log.error('No previously published project found.');
|
|
42
|
+
p.log.info('Run `pmpt publish` first to publish your project.');
|
|
43
|
+
p.outro('');
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
// Find existing project on platform
|
|
47
|
+
let existing;
|
|
48
|
+
try {
|
|
49
|
+
const index = await fetchProjects();
|
|
50
|
+
existing = index.projects.find((proj) => proj.slug === savedSlug && proj.author === auth.username);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
p.log.error('Failed to fetch project info. Check your internet connection.');
|
|
54
|
+
p.outro('');
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
if (!existing) {
|
|
58
|
+
p.log.error(`Project "${savedSlug}" not found on platform.`);
|
|
59
|
+
p.log.info('Run `pmpt publish` to publish it first.');
|
|
60
|
+
p.outro('');
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
const snapshots = getAllSnapshots(projectPath);
|
|
64
|
+
const planProgress = getPlanProgress(projectPath);
|
|
65
|
+
if (snapshots.length === 0) {
|
|
66
|
+
p.log.warn('No snapshots found. Run `pmpt save` first.');
|
|
67
|
+
p.outro('');
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
// Check if already up to date
|
|
71
|
+
if (config?.lastPublishedVersionCount === snapshots.length) {
|
|
72
|
+
p.log.success('Already up to date — no new snapshots since last publish.');
|
|
73
|
+
p.log.info('Run `pmpt save` to create a new snapshot, then try again.');
|
|
74
|
+
p.outro('');
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
// Quality gate
|
|
78
|
+
const docsDir = getDocsDir(projectPath);
|
|
79
|
+
const aiMdPath = join(docsDir, 'pmpt.ai.md');
|
|
80
|
+
const aiMdContent = existsSync(aiMdPath) ? readFileSync(aiMdPath, 'utf-8').trim() : '';
|
|
81
|
+
const trackedFiles = glob.sync('**/*.md', { cwd: docsDir });
|
|
82
|
+
const hasGit = snapshots.some((s) => !!s.git);
|
|
83
|
+
const quality = computeQuality({
|
|
84
|
+
pmptAiMd: aiMdContent || null,
|
|
85
|
+
planAnswers: planProgress?.answers ?? null,
|
|
86
|
+
versionCount: snapshots.length,
|
|
87
|
+
docFiles: trackedFiles,
|
|
88
|
+
hasGit,
|
|
89
|
+
});
|
|
90
|
+
if (!quality.passesMinimum) {
|
|
91
|
+
p.log.warn(`Quality score ${quality.score}/100 is below minimum (40).`);
|
|
92
|
+
p.log.info('Run `pmpt publish` for detailed quality breakdown and improvement tips.');
|
|
93
|
+
p.outro('');
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
// Build .pmpt content
|
|
97
|
+
const projectName = planProgress?.answers?.projectName || basename(projectPath);
|
|
98
|
+
const history = snapshots.map((snapshot, i) => ({
|
|
99
|
+
version: snapshot.version,
|
|
100
|
+
timestamp: snapshot.timestamp,
|
|
101
|
+
files: resolveFullSnapshot(snapshots, i),
|
|
102
|
+
git: snapshot.git,
|
|
103
|
+
}));
|
|
104
|
+
const docs = readDocsFolder(docsDir);
|
|
105
|
+
const meta = {
|
|
106
|
+
projectName,
|
|
107
|
+
author: auth.username,
|
|
108
|
+
description: existing.description,
|
|
109
|
+
createdAt: config?.createdAt || new Date().toISOString(),
|
|
110
|
+
exportedAt: new Date().toISOString(),
|
|
111
|
+
origin: config?.origin,
|
|
112
|
+
gitCommitsAtInit: config?.gitCommitsAtInit,
|
|
113
|
+
};
|
|
114
|
+
const planAnswers = planProgress?.answers
|
|
115
|
+
? {
|
|
116
|
+
projectName: planProgress.answers.projectName,
|
|
117
|
+
productIdea: planProgress.answers.productIdea,
|
|
118
|
+
additionalContext: planProgress.answers.additionalContext,
|
|
119
|
+
coreFeatures: planProgress.answers.coreFeatures,
|
|
120
|
+
techStack: planProgress.answers.techStack,
|
|
121
|
+
}
|
|
122
|
+
: undefined;
|
|
123
|
+
const pmptContent = createPmptFile(meta, planAnswers, docs, history);
|
|
124
|
+
// Show summary
|
|
125
|
+
p.note([
|
|
126
|
+
`Slug: ${savedSlug}`,
|
|
127
|
+
`Versions: ${snapshots.length}`,
|
|
128
|
+
`Size: ${(pmptContent.length / 1024).toFixed(1)} KB`,
|
|
129
|
+
`Quality: ${quality.score}/100 (${quality.grade})`,
|
|
130
|
+
].join('\n'), 'Update Preview');
|
|
131
|
+
// Upload
|
|
132
|
+
const s = p.spinner();
|
|
133
|
+
s.start('Uploading...');
|
|
134
|
+
try {
|
|
135
|
+
const result = await publishProject(auth.token, {
|
|
136
|
+
slug: savedSlug,
|
|
137
|
+
pmptContent,
|
|
138
|
+
description: existing.description,
|
|
139
|
+
tags: existing.tags || [],
|
|
140
|
+
category: existing.category,
|
|
141
|
+
...(existing.productUrl && { productUrl: existing.productUrl, productUrlType: existing.productUrlType }),
|
|
142
|
+
});
|
|
143
|
+
s.stop('Updated!');
|
|
144
|
+
if (config) {
|
|
145
|
+
config.lastPublished = new Date().toISOString();
|
|
146
|
+
config.lastPublishedVersionCount = snapshots.length;
|
|
147
|
+
saveConfig(projectPath, config);
|
|
148
|
+
}
|
|
149
|
+
p.note([
|
|
150
|
+
`URL: ${result.url}`,
|
|
151
|
+
'',
|
|
152
|
+
'Content updated. Metadata unchanged.',
|
|
153
|
+
'To change metadata, use `pmpt edit`.',
|
|
154
|
+
].join('\n'), 'Updated!');
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
s.stop('Update failed');
|
|
158
|
+
p.log.error(err instanceof Error ? err.message : 'Failed to update.');
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
p.outro('');
|
|
162
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -34,6 +34,7 @@ import { cmdExport } from './commands/export.js';
|
|
|
34
34
|
import { cmdImport } from './commands/import.js';
|
|
35
35
|
import { cmdLogin } from './commands/login.js';
|
|
36
36
|
import { cmdPublish } from './commands/publish.js';
|
|
37
|
+
import { cmdUpdate } from './commands/update.js';
|
|
37
38
|
import { cmdEdit } from './commands/edit.js';
|
|
38
39
|
import { cmdUnpublish } from './commands/unpublish.js';
|
|
39
40
|
import { cmdClone } from './commands/clone.js';
|
|
@@ -63,6 +64,7 @@ Examples:
|
|
|
63
64
|
$ pmpt import <file.pmpt> Import from .pmpt file
|
|
64
65
|
$ pmpt login Authenticate with pmptwiki
|
|
65
66
|
$ pmpt publish Publish project to pmptwiki
|
|
67
|
+
$ pmpt update Quick re-publish (content only)
|
|
66
68
|
$ pmpt clone <slug> Clone a project from pmptwiki
|
|
67
69
|
$ pmpt explore Explore projects on pmptwiki.com
|
|
68
70
|
$ pmpt recover Recover damaged pmpt.md via AI
|
|
@@ -143,6 +145,10 @@ program
|
|
|
143
145
|
.option('--product-url-type <type>', 'Product link type: git or url')
|
|
144
146
|
.option('--yes', 'Skip confirmation prompt')
|
|
145
147
|
.action(cmdPublish);
|
|
148
|
+
program
|
|
149
|
+
.command('update [path]')
|
|
150
|
+
.description('Quick re-publish: update content without changing metadata')
|
|
151
|
+
.action(cmdUpdate);
|
|
146
152
|
program
|
|
147
153
|
.command('edit')
|
|
148
154
|
.description('Edit published project metadata (description, tags, category)')
|
package/dist/lib/config.js
CHANGED
|
@@ -35,6 +35,8 @@ export function initializeProject(projectPath, options) {
|
|
|
35
35
|
createdAt: new Date().toISOString(),
|
|
36
36
|
repo: options?.repo,
|
|
37
37
|
trackGit: options?.trackGit ?? true,
|
|
38
|
+
origin: options?.origin,
|
|
39
|
+
gitCommitsAtInit: options?.gitCommitsAtInit,
|
|
38
40
|
};
|
|
39
41
|
saveConfig(projectPath, config);
|
|
40
42
|
// Create README.md if it doesn't exist
|
package/dist/lib/git.js
CHANGED
|
@@ -84,6 +84,13 @@ export function isCommitMatch(path, expectedCommit) {
|
|
|
84
84
|
currentFull.startsWith(expectedCommit) ||
|
|
85
85
|
expectedCommit.startsWith(currentShort));
|
|
86
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* Count total commits in the repository
|
|
89
|
+
*/
|
|
90
|
+
export function getCommitCount(path) {
|
|
91
|
+
const count = git(path, 'rev-list --count HEAD');
|
|
92
|
+
return count ? parseInt(count, 10) : 0;
|
|
93
|
+
}
|
|
87
94
|
/**
|
|
88
95
|
* Convert git info to human-readable string
|
|
89
96
|
*/
|
package/dist/lib/plan.js
CHANGED
|
@@ -42,7 +42,7 @@ export const PLAN_QUESTIONS = [
|
|
|
42
42
|
},
|
|
43
43
|
];
|
|
44
44
|
// Generate AI instruction file (pmpt.ai.md) — always English, AI-facing
|
|
45
|
-
export function generateAIPrompt(answers) {
|
|
45
|
+
export function generateAIPrompt(answers, origin) {
|
|
46
46
|
// Parse features (support comma, semicolon, or newline separators)
|
|
47
47
|
const features = answers.coreFeatures
|
|
48
48
|
.split(/[,;\n]/)
|
|
@@ -56,10 +56,28 @@ export function generateAIPrompt(answers) {
|
|
|
56
56
|
const techSection = answers.techStack
|
|
57
57
|
? `\n## Tech Stack Preferences\n${answers.techStack}\n`
|
|
58
58
|
: '';
|
|
59
|
+
const isAdopted = origin === 'adopted';
|
|
60
|
+
const workflowSteps = isAdopted
|
|
61
|
+
? `This is an **existing project** — you are joining mid-development.
|
|
62
|
+
|
|
63
|
+
1. First, explore the existing codebase and understand the current architecture.
|
|
64
|
+
2. Review what's already implemented vs what still needs to be done.
|
|
65
|
+
3. Suggest improvements or next steps based on the current state.
|
|
66
|
+
4. Continue development from where it left off.
|
|
67
|
+
|
|
68
|
+
Do NOT start from scratch. Build on the existing code.`
|
|
69
|
+
: `Please help me build this product based on the requirements above.
|
|
70
|
+
|
|
71
|
+
1. First, review the requirements and ask if anything is unclear.
|
|
72
|
+
2. Propose a technical architecture.
|
|
73
|
+
3. Outline the implementation steps.
|
|
74
|
+
4. Start coding from the first step.
|
|
75
|
+
|
|
76
|
+
I'll confirm progress at each step before moving to the next.`;
|
|
59
77
|
return `<!-- This file is for AI tools only. Do not edit manually. -->
|
|
60
78
|
<!-- Paste this into Claude Code, Codex, Cursor, or any AI coding tool. -->
|
|
61
79
|
|
|
62
|
-
# ${answers.projectName} — Product Development Request
|
|
80
|
+
# ${answers.projectName} — ${isAdopted ? 'Continue Development' : 'Product Development Request'}
|
|
63
81
|
|
|
64
82
|
## What I Want to Build
|
|
65
83
|
${answers.productIdea}
|
|
@@ -69,14 +87,7 @@ ${features}
|
|
|
69
87
|
${techSection}
|
|
70
88
|
---
|
|
71
89
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
1. First, review the requirements and ask if anything is unclear.
|
|
75
|
-
2. Propose a technical architecture.
|
|
76
|
-
3. Outline the implementation steps.
|
|
77
|
-
4. Start coding from the first step.
|
|
78
|
-
|
|
79
|
-
I'll confirm progress at each step before moving to the next.
|
|
90
|
+
${workflowSteps}
|
|
80
91
|
|
|
81
92
|
## Documentation Rule
|
|
82
93
|
|
|
@@ -186,7 +197,7 @@ export function initPlanProgress(projectPath) {
|
|
|
186
197
|
savePlanProgress(projectPath, progress);
|
|
187
198
|
return progress;
|
|
188
199
|
}
|
|
189
|
-
export function savePlanDocuments(projectPath, answers) {
|
|
200
|
+
export function savePlanDocuments(projectPath, answers, origin) {
|
|
190
201
|
const pmptDir = getPmptDir(projectPath);
|
|
191
202
|
mkdirSync(pmptDir, { recursive: true });
|
|
192
203
|
// Save plan to pmpt folder
|
|
@@ -199,7 +210,7 @@ export function savePlanDocuments(projectPath, answers) {
|
|
|
199
210
|
writeFileSync(pmptMdPath, pmptMdContent, 'utf-8');
|
|
200
211
|
// Save AI instruction file
|
|
201
212
|
const promptPath = join(pmptDir, 'pmpt.ai.md');
|
|
202
|
-
const promptContent = generateAIPrompt(answers);
|
|
213
|
+
const promptContent = generateAIPrompt(answers, origin);
|
|
203
214
|
writeFileSync(promptPath, promptContent, 'utf-8');
|
|
204
215
|
// Create initial snapshot
|
|
205
216
|
createFullSnapshot(projectPath);
|
package/dist/lib/pmptFile.js
CHANGED
|
@@ -65,6 +65,8 @@ const MetaSchema = z.object({
|
|
|
65
65
|
description: z.string().optional(),
|
|
66
66
|
createdAt: z.string(),
|
|
67
67
|
exportedAt: z.string(),
|
|
68
|
+
origin: z.enum(['new', 'adopted']).optional(),
|
|
69
|
+
gitCommitsAtInit: z.number().optional(),
|
|
68
70
|
});
|
|
69
71
|
// Full .pmpt file schema
|
|
70
72
|
export const PmptFileSchema = z.object({
|