pmpt-cli 1.11.0 → 1.11.1
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/clone.js +45 -2
- package/dist/commands/edit.js +43 -0
- package/dist/commands/hist.js +6 -2
- package/dist/commands/internal-seed.js +3 -3
- package/dist/commands/save.js +6 -0
- package/dist/lib/config.js +57 -0
- package/dist/lib/history.js +22 -5
- package/dist/lib/plan.js +6 -0
- package/package.json +1 -1
package/dist/commands/clone.js
CHANGED
|
@@ -4,6 +4,7 @@ import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync } from
|
|
|
4
4
|
import { isInitialized, getConfigDir, getHistoryDir, getDocsDir, initializeProject } from '../lib/config.js';
|
|
5
5
|
import { validatePmptFile, isSafeFilename } from '../lib/pmptFile.js';
|
|
6
6
|
import { fetchPmptFile, trackClone } from '../lib/api.js';
|
|
7
|
+
import { copyToClipboard } from '../lib/clipboard.js';
|
|
7
8
|
/**
|
|
8
9
|
* Restore history from .pmpt data (shared with import command)
|
|
9
10
|
*/
|
|
@@ -147,6 +148,19 @@ export async function cmdClone(slug) {
|
|
|
147
148
|
'',
|
|
148
149
|
'---',
|
|
149
150
|
'',
|
|
151
|
+
`## Documentation Rule`,
|
|
152
|
+
'',
|
|
153
|
+
`**Important:** When you make progress, update \`.pmpt/docs/pmpt.md\` (the human-facing project document) at these moments:`,
|
|
154
|
+
`- When architecture or tech decisions are finalized`,
|
|
155
|
+
`- When a feature is implemented (mark as done)`,
|
|
156
|
+
`- When a development phase is completed`,
|
|
157
|
+
`- When requirements change or new decisions are made`,
|
|
158
|
+
'',
|
|
159
|
+
`Keep the Progress and Snapshot Log sections in pmpt.md up to date.`,
|
|
160
|
+
`After significant milestones, run \`pmpt save\` to create a snapshot.`,
|
|
161
|
+
'',
|
|
162
|
+
'---',
|
|
163
|
+
'',
|
|
150
164
|
originalAiMd,
|
|
151
165
|
].join('\n');
|
|
152
166
|
writeFileSync(aiMdPath, cloneGuide, 'utf-8');
|
|
@@ -170,9 +184,38 @@ export async function cmdClone(slug) {
|
|
|
170
184
|
`Versions: ${versionCount}`,
|
|
171
185
|
`Location: ${pmptDir}`,
|
|
172
186
|
].join('\n'), 'Clone Summary');
|
|
173
|
-
|
|
187
|
+
// Copy AI prompt to clipboard
|
|
188
|
+
const aiContent = readFileSync(aiMdPath, 'utf-8');
|
|
189
|
+
const copied = copyToClipboard(aiContent);
|
|
190
|
+
if (copied) {
|
|
191
|
+
p.log.message('');
|
|
192
|
+
p.log.success('AI prompt copied to clipboard!');
|
|
193
|
+
p.log.message('');
|
|
194
|
+
const banner = [
|
|
195
|
+
'',
|
|
196
|
+
'┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓',
|
|
197
|
+
'┃ ┃',
|
|
198
|
+
'┃ 📋 NEXT STEP ┃',
|
|
199
|
+
'┃ ┃',
|
|
200
|
+
'┃ Open your AI coding tool and press: ┃',
|
|
201
|
+
'┃ ┃',
|
|
202
|
+
'┃ ⌘ + V (Mac) ┃',
|
|
203
|
+
'┃ Ctrl + V (Windows/Linux) ┃',
|
|
204
|
+
'┃ ┃',
|
|
205
|
+
'┃ Your cloned project context is ready! 🚀 ┃',
|
|
206
|
+
'┃ ┃',
|
|
207
|
+
'┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛',
|
|
208
|
+
'',
|
|
209
|
+
];
|
|
210
|
+
console.log(banner.join('\n'));
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
p.log.warn('Could not copy to clipboard.');
|
|
214
|
+
p.log.info(`Read it at: ${aiMdPath}`);
|
|
215
|
+
}
|
|
216
|
+
p.log.info('Tips:');
|
|
174
217
|
p.log.message(' pmpt history — view version history');
|
|
175
|
-
p.log.message(' pmpt plan — view AI prompt');
|
|
218
|
+
p.log.message(' pmpt plan — view or edit AI prompt');
|
|
176
219
|
p.log.message(' pmpt save — save a new snapshot');
|
|
177
220
|
p.outro('Project cloned!');
|
|
178
221
|
}
|
package/dist/commands/edit.js
CHANGED
|
@@ -79,6 +79,47 @@ export async function cmdEdit() {
|
|
|
79
79
|
p.cancel('Cancelled');
|
|
80
80
|
process.exit(0);
|
|
81
81
|
}
|
|
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);
|
|
95
|
+
}
|
|
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
|
+
},
|
|
116
|
+
});
|
|
117
|
+
if (p.isCancel(productUrlInput)) {
|
|
118
|
+
p.cancel('Cancelled');
|
|
119
|
+
process.exit(0);
|
|
120
|
+
}
|
|
121
|
+
productUrl = productUrlInput;
|
|
122
|
+
}
|
|
82
123
|
const s2 = p.spinner();
|
|
83
124
|
s2.start('Updating...');
|
|
84
125
|
try {
|
|
@@ -86,6 +127,8 @@ export async function cmdEdit() {
|
|
|
86
127
|
description: description,
|
|
87
128
|
tags,
|
|
88
129
|
category: category,
|
|
130
|
+
productUrl,
|
|
131
|
+
productUrlType,
|
|
89
132
|
});
|
|
90
133
|
s2.stop('Updated!');
|
|
91
134
|
p.log.success(`Project "${slug}" has been updated.`);
|
package/dist/commands/hist.js
CHANGED
|
@@ -75,8 +75,12 @@ export function cmdHistory(path, options) {
|
|
|
75
75
|
if (snapshot.git.dirty)
|
|
76
76
|
header += ' (dirty)';
|
|
77
77
|
}
|
|
78
|
-
const
|
|
79
|
-
|
|
78
|
+
const lines = [];
|
|
79
|
+
if (snapshot.note) {
|
|
80
|
+
lines.push(` ${snapshot.note}`, '');
|
|
81
|
+
}
|
|
82
|
+
lines.push(...snapshot.files.map((f) => ` - ${f}`));
|
|
83
|
+
p.note(lines.join('\n') || ' (no files)', header);
|
|
80
84
|
}
|
|
81
85
|
if (options?.compact && hiddenVersions.length > 0) {
|
|
82
86
|
p.log.info(`Hidden versions (minor changes): ${hiddenVersions.map(v => `v${v}`).join(', ')}`);
|
|
@@ -84,9 +84,9 @@ export async function cmdInternalSeed(options) {
|
|
|
84
84
|
const content = readFileSync(resolve(specDir, fromPath), 'utf-8');
|
|
85
85
|
writeDocFile(docsDir, fileName, content);
|
|
86
86
|
}
|
|
87
|
-
const entry = createFullSnapshot(projectPath);
|
|
88
|
-
const
|
|
89
|
-
p.log.success(`v${entry.version} saved${
|
|
87
|
+
const entry = createFullSnapshot(projectPath, { note: step.saveNote });
|
|
88
|
+
const noteStr = entry.note ? ` — ${entry.note}` : '';
|
|
89
|
+
p.log.success(`v${entry.version} saved${noteStr}`);
|
|
90
90
|
}
|
|
91
91
|
if (spec.publish?.enabled) {
|
|
92
92
|
await cmdPublish(projectPath, {
|
package/dist/commands/save.js
CHANGED
|
@@ -38,6 +38,12 @@ export async function cmdSave(fileOrPath) {
|
|
|
38
38
|
msg += ` (${changedCount} changed, ${unchangedCount} skipped)`;
|
|
39
39
|
}
|
|
40
40
|
p.log.success(msg);
|
|
41
|
+
// Warn if pmpt.md was not updated since last save
|
|
42
|
+
if (entry.version > 1 && entry.changedFiles && !entry.changedFiles.includes('pmpt.md')) {
|
|
43
|
+
p.log.message('');
|
|
44
|
+
p.log.warn('pmpt.md has not been updated since the last save.');
|
|
45
|
+
p.log.message(' Tip: Mark completed features and update the Snapshot Log before saving.');
|
|
46
|
+
}
|
|
41
47
|
p.log.message('');
|
|
42
48
|
p.log.info('Files included:');
|
|
43
49
|
for (const file of entry.files) {
|
package/dist/lib/config.js
CHANGED
|
@@ -37,6 +37,11 @@ export function initializeProject(projectPath, options) {
|
|
|
37
37
|
trackGit: options?.trackGit ?? true,
|
|
38
38
|
};
|
|
39
39
|
saveConfig(projectPath, config);
|
|
40
|
+
// Create README.md if it doesn't exist
|
|
41
|
+
const readmePath = join(configDir, 'README.md');
|
|
42
|
+
if (!existsSync(readmePath)) {
|
|
43
|
+
writeFileSync(readmePath, PMPT_README, 'utf-8');
|
|
44
|
+
}
|
|
40
45
|
return config;
|
|
41
46
|
}
|
|
42
47
|
export function loadConfig(projectPath) {
|
|
@@ -54,3 +59,55 @@ export function saveConfig(projectPath, config) {
|
|
|
54
59
|
const configPath = join(getConfigDir(projectPath), CONFIG_FILE);
|
|
55
60
|
writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
56
61
|
}
|
|
62
|
+
const PMPT_README = `# .pmpt — Your Project's Development Journal
|
|
63
|
+
|
|
64
|
+
This folder is managed by [pmpt](https://pmptwiki.com). It records your product development journey with AI.
|
|
65
|
+
|
|
66
|
+
## What's Inside
|
|
67
|
+
|
|
68
|
+
\`\`\`
|
|
69
|
+
.pmpt/
|
|
70
|
+
├── config.json ← Project settings (auto-generated)
|
|
71
|
+
├── docs/
|
|
72
|
+
│ ├── pmpt.md ← Human-facing project document (YOU update this)
|
|
73
|
+
│ ├── pmpt.ai.md ← AI-facing prompt (paste into your AI tool)
|
|
74
|
+
│ └── plan.md ← Original plan from pmpt plan
|
|
75
|
+
└── .history/ ← Version snapshots (auto-managed)
|
|
76
|
+
\`\`\`
|
|
77
|
+
|
|
78
|
+
## Quick Reference
|
|
79
|
+
|
|
80
|
+
| Command | What it does |
|
|
81
|
+
|---------|-------------|
|
|
82
|
+
| \`pmpt plan\` | Create or view your AI prompt |
|
|
83
|
+
| \`pmpt save\` | Save a snapshot of current docs |
|
|
84
|
+
| \`pmpt history\` | View version history |
|
|
85
|
+
| \`pmpt diff\` | Compare versions side by side |
|
|
86
|
+
| \`pmpt publish\` | Share your journey on pmptwiki.com |
|
|
87
|
+
|
|
88
|
+
## How to Get the Most Out of pmpt
|
|
89
|
+
|
|
90
|
+
1. **Paste \`pmpt.ai.md\` into your AI tool** to start building
|
|
91
|
+
2. **Update \`pmpt.md\` as you go** — mark features done, log decisions
|
|
92
|
+
3. **Run \`pmpt save\` at milestones** — after setup, after each feature, after big changes
|
|
93
|
+
4. **Publish when ready** — others can clone your journey and learn from it
|
|
94
|
+
|
|
95
|
+
## When Things Go Wrong
|
|
96
|
+
|
|
97
|
+
| Problem | Solution |
|
|
98
|
+
|---------|----------|
|
|
99
|
+
| Lost your AI prompt | \`pmpt plan\` to regenerate or view it |
|
|
100
|
+
| Messed up docs | \`pmpt history\` → \`pmpt diff\` to find the good version |
|
|
101
|
+
| Need to start over | \`pmpt recover\` rebuilds context from history |
|
|
102
|
+
| Accidentally deleted .pmpt | Re-clone from pmptwiki.com if published |
|
|
103
|
+
|
|
104
|
+
## One Request
|
|
105
|
+
|
|
106
|
+
Please keep \`pmpt.md\` updated as you build. It's the human-readable record of your journey — what you tried, what worked, what you decided. When you publish, this is what others will learn from.
|
|
107
|
+
|
|
108
|
+
Your snapshots tell a story. Make it a good one.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
*Learn more at [pmptwiki.com](https://pmptwiki.com)*
|
|
113
|
+
`;
|
package/dist/lib/history.js
CHANGED
|
@@ -3,18 +3,31 @@ import { basename, join, relative } from 'path';
|
|
|
3
3
|
import { getHistoryDir, getDocsDir, loadConfig } from './config.js';
|
|
4
4
|
import { getGitInfo, isGitRepo } from './git.js';
|
|
5
5
|
import glob from 'fast-glob';
|
|
6
|
+
/** Generate compact timestamp for snapshot dir names: 20260225T163000 */
|
|
7
|
+
function compactTimestamp() {
|
|
8
|
+
return new Date().toISOString().replace(/[-:\.]/g, '').slice(0, 15);
|
|
9
|
+
}
|
|
10
|
+
/** Parse snapshot dir timestamp (compact or legacy) to ISO string */
|
|
11
|
+
function parseTimestamp(raw) {
|
|
12
|
+
// Compact: 20260225T163000
|
|
13
|
+
if (/^\d{8}T\d{6}$/.test(raw)) {
|
|
14
|
+
return `${raw.slice(0, 4)}-${raw.slice(4, 6)}-${raw.slice(6, 8)}T${raw.slice(9, 11)}:${raw.slice(11, 13)}:${raw.slice(13, 15)}`;
|
|
15
|
+
}
|
|
16
|
+
// Legacy: 2026-02-25T16-30-00
|
|
17
|
+
return raw.replace(/T(.+)$/, (_, time) => 'T' + time.replace(/-/g, ':'));
|
|
18
|
+
}
|
|
6
19
|
/**
|
|
7
20
|
* Save .pmpt/docs MD files as snapshot
|
|
8
21
|
* Copy only changed files to optimize storage
|
|
9
22
|
*/
|
|
10
|
-
export function createFullSnapshot(projectPath) {
|
|
23
|
+
export function createFullSnapshot(projectPath, options) {
|
|
11
24
|
const historyDir = getHistoryDir(projectPath);
|
|
12
25
|
const docsDir = getDocsDir(projectPath);
|
|
13
26
|
mkdirSync(historyDir, { recursive: true });
|
|
14
27
|
// Find next version number
|
|
15
28
|
const existing = getAllSnapshots(projectPath);
|
|
16
29
|
const version = existing.length + 1;
|
|
17
|
-
const timestamp =
|
|
30
|
+
const timestamp = compactTimestamp();
|
|
18
31
|
const snapshotName = `v${version}-${timestamp}`;
|
|
19
32
|
const snapshotDir = join(historyDir, snapshotName);
|
|
20
33
|
mkdirSync(snapshotDir, { recursive: true });
|
|
@@ -63,12 +76,14 @@ export function createFullSnapshot(projectPath) {
|
|
|
63
76
|
}
|
|
64
77
|
}
|
|
65
78
|
// Save metadata
|
|
79
|
+
const note = options?.note;
|
|
66
80
|
const metaPath = join(snapshotDir, '.meta.json');
|
|
67
81
|
writeFileSync(metaPath, JSON.stringify({
|
|
68
82
|
version,
|
|
69
83
|
timestamp,
|
|
70
84
|
files,
|
|
71
85
|
changedFiles,
|
|
86
|
+
...(note ? { note } : {}),
|
|
72
87
|
git: gitData,
|
|
73
88
|
}, null, 2), 'utf-8');
|
|
74
89
|
return {
|
|
@@ -77,6 +92,7 @@ export function createFullSnapshot(projectPath) {
|
|
|
77
92
|
snapshotDir,
|
|
78
93
|
files,
|
|
79
94
|
changedFiles,
|
|
95
|
+
note,
|
|
80
96
|
git: gitData,
|
|
81
97
|
};
|
|
82
98
|
}
|
|
@@ -98,7 +114,7 @@ export function createSnapshot(projectPath, filePath) {
|
|
|
98
114
|
}
|
|
99
115
|
function createSingleFileSnapshot(projectPath, filePath, relPath) {
|
|
100
116
|
const historyDir = getHistoryDir(projectPath);
|
|
101
|
-
const timestamp =
|
|
117
|
+
const timestamp = compactTimestamp();
|
|
102
118
|
// Check existing version count for this file
|
|
103
119
|
const existing = getFileHistory(projectPath, relPath);
|
|
104
120
|
const version = existing.length + 1;
|
|
@@ -168,10 +184,11 @@ export function getAllSnapshots(projectPath) {
|
|
|
168
184
|
}
|
|
169
185
|
entries.push({
|
|
170
186
|
version: parseInt(match[1], 10),
|
|
171
|
-
timestamp: match[2]
|
|
187
|
+
timestamp: parseTimestamp(match[2]),
|
|
172
188
|
snapshotDir,
|
|
173
189
|
files: meta.files || [],
|
|
174
190
|
changedFiles: meta.changedFiles,
|
|
191
|
+
note: meta.note,
|
|
175
192
|
git: meta.git,
|
|
176
193
|
});
|
|
177
194
|
}
|
|
@@ -210,7 +227,7 @@ export function getFileHistory(projectPath, relPath) {
|
|
|
210
227
|
}
|
|
211
228
|
entries.push({
|
|
212
229
|
version: parseInt(match[1], 10),
|
|
213
|
-
timestamp: match[2]
|
|
230
|
+
timestamp: parseTimestamp(match[2]),
|
|
214
231
|
filePath: relPath,
|
|
215
232
|
historyPath: filePath,
|
|
216
233
|
git: gitData,
|
package/dist/lib/plan.js
CHANGED
|
@@ -88,6 +88,12 @@ I'll confirm progress at each step before moving to the next.
|
|
|
88
88
|
|
|
89
89
|
Keep the Progress and Snapshot Log sections in pmpt.md up to date.
|
|
90
90
|
After significant milestones, run \`pmpt save\` to create a snapshot.
|
|
91
|
+
|
|
92
|
+
### Per-Feature Checklist
|
|
93
|
+
After completing each feature above:
|
|
94
|
+
1. Mark the feature done in \`.pmpt/docs/pmpt.md\` (change \`- [ ]\` to \`- [x]\`)
|
|
95
|
+
2. Add a brief note to the Snapshot Log section
|
|
96
|
+
3. Run \`pmpt save\` in terminal
|
|
91
97
|
`;
|
|
92
98
|
}
|
|
93
99
|
// Generate human-facing project document (pmpt.md)
|