pmpt-cli 1.1.1 → 1.2.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.
@@ -0,0 +1,206 @@
1
+ import * as p from '@clack/prompts';
2
+ import { resolve, join, basename } from 'path';
3
+ import { existsSync, writeFileSync, statSync } from 'fs';
4
+ import { execSync } from 'child_process';
5
+ import { isInitialized, getConfigDir, getHistoryDir, loadConfig } from '../lib/config.js';
6
+ import { getAllSnapshots } from '../lib/history.js';
7
+ import { getPlanProgress } from '../lib/plan.js';
8
+ /**
9
+ * Create a zip file without external dependencies
10
+ * Uses native zip command on macOS/Linux, PowerShell on Windows
11
+ */
12
+ function createZip(sourceDir, outputPath) {
13
+ try {
14
+ const platform = process.platform;
15
+ if (platform === 'win32') {
16
+ // PowerShell Compress-Archive
17
+ execSync(`powershell -command "Compress-Archive -Path '${sourceDir}/*' -DestinationPath '${outputPath}'"`, {
18
+ stdio: 'pipe',
19
+ });
20
+ }
21
+ else {
22
+ // Unix zip command
23
+ execSync(`cd "${sourceDir}" && zip -r "${outputPath}" .`, {
24
+ stdio: 'pipe',
25
+ });
26
+ }
27
+ return true;
28
+ }
29
+ catch {
30
+ return false;
31
+ }
32
+ }
33
+ /**
34
+ * Generate metadata markdown file
35
+ */
36
+ function generateMetadataMarkdown(projectPath, snapshots, planProgress, config) {
37
+ const answers = planProgress?.answers || {};
38
+ const projectName = answers.projectName || basename(projectPath);
39
+ // Timeline info
40
+ const firstSnapshot = snapshots[0];
41
+ const lastSnapshot = snapshots[snapshots.length - 1];
42
+ const startDate = firstSnapshot?.timestamp || 'N/A';
43
+ const lastDate = lastSnapshot?.timestamp || 'N/A';
44
+ // Git info from last snapshot
45
+ const gitInfo = lastSnapshot?.git;
46
+ // All unique files across snapshots
47
+ const allFiles = new Set();
48
+ for (const snapshot of snapshots) {
49
+ for (const file of snapshot.files) {
50
+ allFiles.add(file);
51
+ }
52
+ }
53
+ // Parse features
54
+ const features = answers.coreFeatures
55
+ ? answers.coreFeatures
56
+ .split(/[,;\n]/)
57
+ .map((f) => f.trim())
58
+ .filter((f) => f)
59
+ .map((f) => `- ${f}`)
60
+ .join('\n')
61
+ : 'Not specified';
62
+ let md = `# ${projectName}
63
+
64
+ > Exported from pmpt-cli
65
+
66
+ ## Project Overview
67
+
68
+ **Product Idea:**
69
+ ${answers.productIdea || 'Not specified'}
70
+
71
+ ${answers.additionalContext ? `**Additional Context:**\n${answers.additionalContext}\n` : ''}
72
+ **Key Features:**
73
+ ${features}
74
+
75
+ **Tech Stack:**
76
+ ${answers.techStack || 'Not specified'}
77
+
78
+ ---
79
+
80
+ ## Export Information
81
+
82
+ | Item | Value |
83
+ |------|-------|
84
+ | Total Iterations | ${snapshots.length} |
85
+ | First Version | ${startDate} |
86
+ | Last Version | ${lastDate} |
87
+ | Files Tracked | ${allFiles.size} |
88
+ `;
89
+ if (gitInfo) {
90
+ md += `| Git Branch | ${gitInfo.branch} |
91
+ | Last Commit | ${gitInfo.commit} |
92
+ `;
93
+ }
94
+ md += `
95
+ ---
96
+
97
+ ## Version Timeline
98
+
99
+ | Version | Date | Files | Git |
100
+ |---------|------|-------|-----|
101
+ `;
102
+ for (const snapshot of snapshots) {
103
+ const gitCol = snapshot.git
104
+ ? `${snapshot.git.commit}${snapshot.git.dirty ? ' (dirty)' : ''}`
105
+ : '-';
106
+ md += `| v${snapshot.version} | ${snapshot.timestamp} | ${snapshot.files.length} | ${gitCol} |\n`;
107
+ }
108
+ md += `
109
+ ---
110
+
111
+ ## File List
112
+
113
+ `;
114
+ for (const file of Array.from(allFiles).sort()) {
115
+ md += `- ${file}\n`;
116
+ }
117
+ md += `
118
+ ---
119
+
120
+ *Generated by [pmpt-cli](https://pmptwiki.com)*
121
+ `;
122
+ return md;
123
+ }
124
+ export async function cmdExport(path, options) {
125
+ const projectPath = path ? resolve(path) : process.cwd();
126
+ if (!isInitialized(projectPath)) {
127
+ p.log.error('Project not initialized. Run `pmpt init` first.');
128
+ process.exit(1);
129
+ }
130
+ p.intro('pmpt export');
131
+ const config = loadConfig(projectPath);
132
+ const snapshots = getAllSnapshots(projectPath);
133
+ const planProgress = getPlanProgress(projectPath);
134
+ if (snapshots.length === 0) {
135
+ p.log.warn('No snapshots found.');
136
+ p.log.info('Run `pmpt save` or `pmpt plan` first to create versions.');
137
+ p.outro('');
138
+ return;
139
+ }
140
+ const projectName = planProgress?.answers?.projectName || basename(projectPath);
141
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 10);
142
+ const exportName = `pmpt-export-${projectName}-${timestamp}`;
143
+ // Output path
144
+ const outputPath = options?.output
145
+ ? resolve(options.output)
146
+ : resolve(projectPath, `${exportName}.zip`);
147
+ const s = p.spinner();
148
+ s.start('Preparing export...');
149
+ // Create temp directory for export
150
+ const tempDir = join(projectPath, '.pmpt', '.export-temp');
151
+ execSync(`rm -rf "${tempDir}" && mkdir -p "${tempDir}"`, { stdio: 'pipe' });
152
+ // Copy history folder
153
+ const historyDir = getHistoryDir(projectPath);
154
+ execSync(`cp -r "${historyDir}" "${tempDir}/history"`, { stdio: 'pipe' });
155
+ // Generate and save metadata
156
+ const metadataContent = generateMetadataMarkdown(projectPath, snapshots, planProgress, config);
157
+ writeFileSync(join(tempDir, 'README.md'), metadataContent, 'utf-8');
158
+ // Copy current docs if exists
159
+ const docsDir = join(getConfigDir(projectPath), 'docs');
160
+ if (existsSync(docsDir)) {
161
+ execSync(`cp -r "${docsDir}" "${tempDir}/docs"`, { stdio: 'pipe' });
162
+ }
163
+ // Copy config (without sensitive data)
164
+ if (config) {
165
+ const exportConfig = {
166
+ projectPath: basename(projectPath),
167
+ docsPath: config.docsPath,
168
+ createdAt: config.createdAt,
169
+ trackGit: config.trackGit,
170
+ };
171
+ writeFileSync(join(tempDir, 'config.json'), JSON.stringify(exportConfig, null, 2), 'utf-8');
172
+ }
173
+ // Copy plan progress if exists
174
+ if (planProgress) {
175
+ writeFileSync(join(tempDir, 'plan.json'), JSON.stringify(planProgress, null, 2), 'utf-8');
176
+ }
177
+ s.message('Creating archive...');
178
+ // Create zip
179
+ const zipSuccess = createZip(tempDir, outputPath);
180
+ // Cleanup temp directory
181
+ execSync(`rm -rf "${tempDir}"`, { stdio: 'pipe' });
182
+ if (!zipSuccess) {
183
+ s.stop('Export failed');
184
+ p.log.error('Failed to create zip archive.');
185
+ p.log.info('Make sure `zip` command is available on your system.');
186
+ process.exit(1);
187
+ }
188
+ s.stop('Export complete!');
189
+ // Summary
190
+ const fileSizeBytes = statSync(outputPath).size;
191
+ const fileSize = fileSizeBytes < 1024
192
+ ? `${fileSizeBytes} B`
193
+ : fileSizeBytes < 1024 * 1024
194
+ ? `${(fileSizeBytes / 1024).toFixed(1)} KB`
195
+ : `${(fileSizeBytes / 1024 / 1024).toFixed(1)} MB`;
196
+ const summary = [
197
+ `Project: ${projectName}`,
198
+ `Versions: ${snapshots.length}`,
199
+ `Size: ${fileSize}`,
200
+ '',
201
+ `Output: ${outputPath}`,
202
+ ];
203
+ p.note(summary.join('\n'), 'Export Summary');
204
+ p.log.info('Share this file to let others reproduce your AI development journey!');
205
+ p.outro('');
206
+ }
@@ -0,0 +1,152 @@
1
+ import * as p from '@clack/prompts';
2
+ import { resolve, join } from 'path';
3
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, cpSync, rmSync } from 'fs';
4
+ import { execSync } from 'child_process';
5
+ import { isInitialized, getConfigDir, getHistoryDir, getDocsDir, initializeProject } from '../lib/config.js';
6
+ /**
7
+ * Extract zip file
8
+ * Uses native unzip command on macOS/Linux, PowerShell on Windows
9
+ */
10
+ function extractZip(zipPath, destDir) {
11
+ try {
12
+ const platform = process.platform;
13
+ mkdirSync(destDir, { recursive: true });
14
+ if (platform === 'win32') {
15
+ // PowerShell Expand-Archive
16
+ execSync(`powershell -command "Expand-Archive -Path '${zipPath}' -DestinationPath '${destDir}' -Force"`, {
17
+ stdio: 'pipe',
18
+ });
19
+ }
20
+ else {
21
+ // Unix unzip command
22
+ execSync(`unzip -o "${zipPath}" -d "${destDir}"`, {
23
+ stdio: 'pipe',
24
+ });
25
+ }
26
+ return true;
27
+ }
28
+ catch {
29
+ return false;
30
+ }
31
+ }
32
+ export async function cmdImport(zipFile, options) {
33
+ if (!zipFile) {
34
+ p.log.error('Please provide a zip file path.');
35
+ p.log.info('Usage: pmpt import <file.zip>');
36
+ process.exit(1);
37
+ }
38
+ const zipPath = resolve(zipFile);
39
+ if (!existsSync(zipPath)) {
40
+ p.log.error(`File not found: ${zipPath}`);
41
+ process.exit(1);
42
+ }
43
+ if (!zipPath.endsWith('.zip')) {
44
+ p.log.error('Please provide a .zip file.');
45
+ process.exit(1);
46
+ }
47
+ p.intro('pmpt import');
48
+ const projectPath = process.cwd();
49
+ // Check if already initialized
50
+ if (isInitialized(projectPath) && !options?.force) {
51
+ const overwrite = await p.confirm({
52
+ message: 'Project already initialized. Merge imported history?',
53
+ initialValue: true,
54
+ });
55
+ if (p.isCancel(overwrite)) {
56
+ p.cancel('Import cancelled.');
57
+ process.exit(0);
58
+ }
59
+ if (!overwrite) {
60
+ p.log.info('Use --force to overwrite existing project.');
61
+ p.outro('');
62
+ return;
63
+ }
64
+ }
65
+ const s = p.spinner();
66
+ s.start('Extracting archive...');
67
+ // Create temp directory for extraction
68
+ const tempDir = join(projectPath, '.pmpt-import-temp');
69
+ rmSync(tempDir, { recursive: true, force: true });
70
+ const extracted = extractZip(zipPath, tempDir);
71
+ if (!extracted) {
72
+ rmSync(tempDir, { recursive: true, force: true });
73
+ s.stop('Extraction failed');
74
+ p.log.error('Failed to extract zip file.');
75
+ p.log.info('Make sure `unzip` command is available on your system.');
76
+ process.exit(1);
77
+ }
78
+ s.message('Importing project...');
79
+ // Read imported data
80
+ let importedPlan = null;
81
+ let importedConfig = null;
82
+ let projectName = 'imported-project';
83
+ // Read plan.json if exists
84
+ const planPath = join(tempDir, 'plan.json');
85
+ if (existsSync(planPath)) {
86
+ try {
87
+ importedPlan = JSON.parse(readFileSync(planPath, 'utf-8'));
88
+ if (importedPlan.answers?.projectName) {
89
+ projectName = importedPlan.answers.projectName;
90
+ }
91
+ }
92
+ catch {
93
+ // Ignore parse errors
94
+ }
95
+ }
96
+ // Read config.json if exists
97
+ const configPath = join(tempDir, 'config.json');
98
+ if (existsSync(configPath)) {
99
+ try {
100
+ importedConfig = JSON.parse(readFileSync(configPath, 'utf-8'));
101
+ }
102
+ catch {
103
+ // Ignore parse errors
104
+ }
105
+ }
106
+ // Initialize project if not exists
107
+ if (!isInitialized(projectPath)) {
108
+ initializeProject(projectPath, {
109
+ trackGit: importedConfig?.trackGit ?? true,
110
+ });
111
+ }
112
+ const pmptDir = getConfigDir(projectPath);
113
+ const historyDir = getHistoryDir(projectPath);
114
+ const docsDir = getDocsDir(projectPath);
115
+ // Import history
116
+ const importedHistoryDir = join(tempDir, 'history');
117
+ if (existsSync(importedHistoryDir)) {
118
+ // Copy all version folders
119
+ cpSync(importedHistoryDir, historyDir, { recursive: true, force: true });
120
+ }
121
+ // Import docs
122
+ const importedDocsDir = join(tempDir, 'docs');
123
+ if (existsSync(importedDocsDir)) {
124
+ cpSync(importedDocsDir, docsDir, { recursive: true, force: true });
125
+ }
126
+ // Import plan progress
127
+ if (importedPlan) {
128
+ const planProgressPath = join(pmptDir, 'plan-progress.json');
129
+ writeFileSync(planProgressPath, JSON.stringify(importedPlan, null, 2), 'utf-8');
130
+ }
131
+ // Count imported versions
132
+ let versionCount = 0;
133
+ if (existsSync(historyDir)) {
134
+ const { readdirSync } = await import('fs');
135
+ versionCount = readdirSync(historyDir).filter(d => d.startsWith('v')).length;
136
+ }
137
+ // Cleanup temp directory
138
+ rmSync(tempDir, { recursive: true, force: true });
139
+ s.stop('Import complete!');
140
+ // Summary
141
+ const summary = [
142
+ `Project: ${projectName}`,
143
+ `Versions imported: ${versionCount}`,
144
+ `Location: ${pmptDir}`,
145
+ ];
146
+ p.note(summary.join('\n'), 'Import Summary');
147
+ p.log.info('Next steps:');
148
+ p.log.message(' pmpt history — View imported versions');
149
+ p.log.message(' pmpt plan — View or copy AI prompt');
150
+ p.log.message(' pmpt save — Save a new snapshot');
151
+ p.outro('Ready to continue the journey!');
152
+ }
package/dist/index.js CHANGED
@@ -10,6 +10,8 @@ import { cmdWatch } from './commands/watch.js';
10
10
  import { cmdPlan } from './commands/plan.js';
11
11
  import { cmdSave } from './commands/save.js';
12
12
  import { cmdSquash } from './commands/squash.js';
13
+ import { cmdExport } from './commands/export.js';
14
+ import { cmdImport } from './commands/import.js';
13
15
  const program = new Command();
14
16
  program
15
17
  .name('pmpt')
@@ -18,12 +20,14 @@ program
18
20
  .addHelpText('after', `
19
21
  Examples:
20
22
  $ pmpt init Initialize project
21
- $ pmpt plan Start product planning (6 questions → AI prompt)
23
+ $ pmpt plan Start product planning (5 questions → AI prompt)
22
24
  $ pmpt save Save snapshot of docs folder
23
25
  $ pmpt watch Auto-detect file changes
24
26
  $ pmpt history View version history
25
27
  $ pmpt history --compact Hide minor changes
26
28
  $ pmpt squash v2 v5 Merge versions v2-v5 into v2
29
+ $ pmpt export Export history as shareable zip
30
+ $ pmpt import <file.zip> Import project from zip
27
31
 
28
32
  Folder structure:
29
33
  .pmpt/
@@ -60,9 +64,19 @@ program
60
64
  .command('squash <from> <to> [path]')
61
65
  .description('Squash multiple versions into one (e.g., pmpt squash v2 v5)')
62
66
  .action(cmdSquash);
67
+ program
68
+ .command('export [path]')
69
+ .description('Export project history as a shareable zip archive')
70
+ .option('-o, --output <file>', 'Output file path')
71
+ .action(cmdExport);
72
+ program
73
+ .command('import <file>')
74
+ .description('Import project from exported zip archive')
75
+ .option('-f, --force', 'Overwrite existing project')
76
+ .action(cmdImport);
63
77
  program
64
78
  .command('plan [path]')
65
- .description('Quick product planning with 6 questions — auto-generate AI prompt')
79
+ .description('Quick product planning with 5 questions — auto-generate AI prompt')
66
80
  .option('--reset', 'Restart plan from scratch')
67
81
  .action(cmdPlan);
68
82
  // Contribution commands
package/dist/lib/plan.js CHANGED
@@ -90,7 +90,19 @@ Add a "## Progress" section below and keep it updated:
90
90
  - [ ] Pending item
91
91
  \`\`\`
92
92
 
93
- This keeps a living record of our development journey.
93
+ Also, add a "## Snapshot Log" section to record what was built at each checkpoint:
94
+ \`\`\`
95
+ ## Snapshot Log
96
+ ### v1 - Initial Setup
97
+ - Set up project structure
98
+ - Installed dependencies: React, Tailwind
99
+
100
+ ### v2 - Auth Feature
101
+ - Implemented login/signup
102
+ - Added JWT authentication
103
+ \`\`\`
104
+
105
+ This helps others understand what was built at each version when they import this project.
94
106
  `;
95
107
  }
96
108
  // Generate plan document
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmpt-cli",
3
- "version": "1.1.1",
3
+ "version": "1.2.1",
4
4
  "description": "Record and share your AI-driven product development journey",
5
5
  "type": "module",
6
6
  "bin": {