pmpt-cli 1.14.14 → 1.14.17

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.
@@ -1,6 +1,6 @@
1
1
  import * as p from '@clack/prompts';
2
2
  import { join, dirname, resolve, sep } from 'path';
3
- import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync } from 'fs';
3
+ import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
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';
@@ -110,7 +110,8 @@ export async function cmdClone(slug) {
110
110
  const pmptDir = getConfigDir(projectPath);
111
111
  const historyDir = getHistoryDir(projectPath);
112
112
  const docsDir = getDocsDir(projectPath);
113
- restoreHistory(historyDir, pmptData.history);
113
+ // History is not restored — user's journey starts fresh from v1.
114
+ // The version summary is embedded in pmpt.ai.md for AI reference.
114
115
  if (pmptData.docs) {
115
116
  restoreDocs(docsDir, pmptData.docs);
116
117
  }
@@ -175,16 +176,13 @@ export async function cmdClone(slug) {
175
176
  answers: pmptData.plan,
176
177
  }, null, 2), 'utf-8');
177
178
  }
178
- let versionCount = 0;
179
- if (existsSync(historyDir)) {
180
- versionCount = readdirSync(historyDir).filter((d) => d.startsWith('v')).length;
181
- }
182
179
  importSpinner.stop('Restore complete!');
183
180
  // Track clone event (fire-and-forget)
184
181
  trackClone(slug);
185
182
  p.note([
186
183
  `Project: ${pmptData.meta.projectName}`,
187
- `Versions: ${versionCount}`,
184
+ `Cloned from: @${pmptData.meta.author || 'unknown'} (${pmptData.history.length} versions)`,
185
+ `Your history: starts fresh from v1`,
188
186
  `Location: ${pmptDir}`,
189
187
  ].join('\n'), 'Clone Summary');
190
188
  // Copy AI prompt to clipboard
@@ -0,0 +1,180 @@
1
+ import * as p from '@clack/prompts';
2
+ import { join, basename } from 'path';
3
+ import { existsSync, readFileSync, writeFileSync } from 'fs';
4
+ import { isInitialized, getDocsDir, getPmptDir } from '../lib/config.js';
5
+ import { getPlanProgress } from '../lib/plan.js';
6
+ import { copyToClipboard } from '../lib/clipboard.js';
7
+ export async function cmdRemix() {
8
+ const projectPath = process.cwd();
9
+ if (!isInitialized(projectPath)) {
10
+ p.log.error('Project not initialized. Run `pmpt init` or `pmpt clone <slug>` first.');
11
+ process.exit(1);
12
+ }
13
+ p.intro('pmpt remix');
14
+ const docsDir = getDocsDir(projectPath);
15
+ const aiMdPath = join(docsDir, 'pmpt.ai.md');
16
+ const planPath = join(getPmptDir(projectPath), 'plan-progress.json');
17
+ // Read original project info
18
+ const originalAiMd = existsSync(aiMdPath) ? readFileSync(aiMdPath, 'utf-8') : '';
19
+ const planProgress = getPlanProgress(projectPath);
20
+ const originalName = planProgress?.answers?.projectName || basename(projectPath);
21
+ const originalIdea = planProgress?.answers?.productIdea || '';
22
+ const originalTech = planProgress?.answers?.techStack || '';
23
+ p.note([
24
+ `Original: ${originalName}`,
25
+ originalIdea ? `Idea: ${originalIdea.slice(0, 100)}${originalIdea.length > 100 ? '...' : ''}` : '',
26
+ originalTech ? `Tech: ${originalTech}` : '',
27
+ ].filter(Boolean).join('\n'), 'Remixing From');
28
+ // Ask remix questions
29
+ const newName = await p.text({
30
+ message: 'New project name?',
31
+ placeholder: `my-${basename(projectPath)}-variant`,
32
+ validate: (v) => {
33
+ if (!v.trim())
34
+ return 'Project name is required.';
35
+ },
36
+ });
37
+ if (p.isCancel(newName)) {
38
+ p.cancel('Cancelled');
39
+ process.exit(0);
40
+ }
41
+ const twist = await p.text({
42
+ message: 'What\'s different about your version? (your key differentiation)',
43
+ placeholder: 'e.g., Web-based version, Korean market focus, B2B instead of B2C',
44
+ validate: (v) => {
45
+ if (!v.trim())
46
+ return 'Please describe your differentiation.';
47
+ },
48
+ });
49
+ if (p.isCancel(twist)) {
50
+ p.cancel('Cancelled');
51
+ process.exit(0);
52
+ }
53
+ const targetAudience = await p.text({
54
+ message: 'Target audience or context? (optional)',
55
+ placeholder: 'e.g., Small business owners, Students, Enterprise teams',
56
+ });
57
+ if (p.isCancel(targetAudience)) {
58
+ p.cancel('Cancelled');
59
+ process.exit(0);
60
+ }
61
+ const techOverride = await p.text({
62
+ message: 'Tech stack? (leave blank to keep original)',
63
+ placeholder: originalTech || 'e.g., Next.js, Supabase, Vercel',
64
+ });
65
+ if (p.isCancel(techOverride)) {
66
+ p.cancel('Cancelled');
67
+ process.exit(0);
68
+ }
69
+ const techStack = (typeof techOverride === 'string' && techOverride.trim())
70
+ ? techOverride.trim()
71
+ : originalTech;
72
+ // Generate remixed pmpt.ai.md
73
+ const contextSection = (typeof targetAudience === 'string' && targetAudience.trim())
74
+ ? `\n## Target Audience\n${targetAudience.trim()}\n`
75
+ : '';
76
+ const techSection = techStack
77
+ ? `\n## Tech Stack\n${techStack}\n`
78
+ : '';
79
+ const remixedPrompt = `<!-- This file is for AI tools only. Do not edit manually. -->
80
+ <!-- Paste this into Claude Code, Codex, Cursor, or any AI coding tool. -->
81
+
82
+ # ${newName.trim()} — Remix
83
+
84
+ ## My Version
85
+ ${twist.trim()}
86
+ ${contextSection}${techSection}
87
+ ---
88
+
89
+ ## Instructions for AI
90
+
91
+ This is a **remix** of an existing project. Before building anything, do the following:
92
+
93
+ **Step 1 — Understand & Clarify (do this first)**
94
+
95
+ Read my "My Version" section above and the original project below carefully, then ask me clarifying questions. Specifically, identify:
96
+ - Anything in my differentiation that is ambiguous or unclear
97
+ - Information that would be needed to build my version but is not provided
98
+ - Assumptions you would have to make — ask instead of assuming
99
+ - Any conflict between my twist and the original that needs resolution
100
+
101
+ Ask all your questions in a single message. Wait for my answers before proceeding.
102
+
103
+ **Step 2 — Build**
104
+
105
+ After I answer your questions, build MY version based on the differentiation and my answers.
106
+ - Same core concept as the original, but with my differentiation applied throughout
107
+ - Do NOT copy content verbatim. Adapt everything to fit my context
108
+ - Start with core features first, then iterate
109
+
110
+ ### Documentation Rule
111
+
112
+ When you make progress, update \`.pmpt/docs/pmpt.md\`:
113
+ - When architecture or tech decisions are finalized
114
+ - When a feature is implemented (mark as done)
115
+ - When a development phase is completed
116
+
117
+ Keep the Snapshot Log up to date. Run \`pmpt save\` after milestones.
118
+
119
+ ---
120
+
121
+ ## Original Project Reference
122
+
123
+ ${originalAiMd}
124
+ `;
125
+ writeFileSync(aiMdPath, remixedPrompt, 'utf-8');
126
+ // Update pmpt.md project name if exists
127
+ const pmptMdPath = join(docsDir, 'pmpt.md');
128
+ if (existsSync(pmptMdPath)) {
129
+ let content = readFileSync(pmptMdPath, 'utf-8');
130
+ // Replace first heading
131
+ content = content.replace(/^# .+$/m, `# ${newName.trim()}`);
132
+ writeFileSync(pmptMdPath, content, 'utf-8');
133
+ }
134
+ // Update plan-progress.json
135
+ if (existsSync(planPath)) {
136
+ try {
137
+ const plan = JSON.parse(readFileSync(planPath, 'utf-8'));
138
+ plan.answers = {
139
+ ...plan.answers,
140
+ projectName: newName.trim(),
141
+ productIdea: `${originalIdea}\n\nMy differentiation: ${twist.trim()}`,
142
+ techStack,
143
+ };
144
+ writeFileSync(planPath, JSON.stringify(plan, null, 2), 'utf-8');
145
+ }
146
+ catch { /* ignore */ }
147
+ }
148
+ // Copy to clipboard
149
+ const copied = copyToClipboard(remixedPrompt);
150
+ p.log.success(`Remix prompt generated for "${newName.trim()}"`);
151
+ p.log.message('');
152
+ p.log.info('Next steps:');
153
+ p.log.message(' 1. Paste the prompt into your AI tool');
154
+ p.log.message(' 2. Build your version');
155
+ p.log.message(' 3. pmpt save — save your progress');
156
+ p.log.message(' 4. pmpt publish — share your remix');
157
+ p.log.message('');
158
+ if (copied) {
159
+ const banner = [
160
+ '┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓',
161
+ '┃ ┃',
162
+ '┃ 📋 NEXT STEP ┃',
163
+ '┃ ┃',
164
+ '┃ Remix prompt copied to clipboard! ┃',
165
+ '┃ Open your AI coding tool and paste it: ┃',
166
+ '┃ ┃',
167
+ '┃ ⌘ + V (Mac) ┃',
168
+ '┃ Ctrl + V (Windows/Linux) ┃',
169
+ '┃ ┃',
170
+ '┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛',
171
+ '',
172
+ ];
173
+ console.log(banner.join('\n'));
174
+ }
175
+ else {
176
+ p.log.warn('Could not copy to clipboard.');
177
+ p.log.info(`Read it at: ${aiMdPath}`);
178
+ }
179
+ p.outro('Ready to remix!');
180
+ }
package/dist/index.js CHANGED
@@ -39,6 +39,7 @@ import { cmdEdit } from './commands/edit.js';
39
39
  import { cmdUnpublish } from './commands/unpublish.js';
40
40
  import { cmdGraduate } from './commands/graduate.js';
41
41
  import { cmdClone } from './commands/clone.js';
42
+ import { cmdRemix } from './commands/remix.js';
42
43
  import { cmdExplore } from './commands/browse.js';
43
44
  import { cmdRecover } from './commands/recover.js';
44
45
  import { cmdDiff } from './commands/diff.js';
@@ -196,6 +197,10 @@ program
196
197
  .command('clone <slug>')
197
198
  .description('Clone a project from pmptwiki platform')
198
199
  .action(cmdClone);
200
+ program
201
+ .command('remix')
202
+ .description('Remix a cloned project — customize it with your own twist')
203
+ .action(cmdRemix);
199
204
  program
200
205
  .command('explore')
201
206
  .alias('exp')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmpt-cli",
3
- "version": "1.14.14",
3
+ "version": "1.14.17",
4
4
  "description": "Record and share your AI-driven product development journey",
5
5
  "type": "module",
6
6
  "bin": {