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.
@@ -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
- p.log.info('Next steps:');
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
  }
@@ -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.`);
@@ -75,8 +75,12 @@ export function cmdHistory(path, options) {
75
75
  if (snapshot.git.dirty)
76
76
  header += ' (dirty)';
77
77
  }
78
- const files = snapshot.files.map((f) => ` - ${f}`).join('\n');
79
- p.note(files || ' (no files)', header);
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 note = step.saveNote ? ` — ${step.saveNote}` : '';
89
- p.log.success(`v${entry.version} saved${note}`);
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, {
@@ -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) {
@@ -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
+ `;
@@ -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 = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
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 = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
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].replace(/-/g, ':'),
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].replace(/-/g, ':'),
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)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmpt-cli",
3
- "version": "1.11.0",
3
+ "version": "1.11.1",
4
4
  "description": "Record and share your AI-driven product development journey",
5
5
  "type": "module",
6
6
  "bin": {