pmpt-cli 1.2.0 → 1.3.0
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/export.js +78 -149
- package/dist/commands/import.js +156 -0
- package/dist/index.js +9 -2
- package/dist/lib/plan.js +13 -1
- package/dist/lib/pmptFile.js +106 -0
- package/package.json +1 -1
package/dist/commands/export.js
CHANGED
|
@@ -1,125 +1,48 @@
|
|
|
1
1
|
import * as p from '@clack/prompts';
|
|
2
2
|
import { resolve, join, basename } from 'path';
|
|
3
|
-
import { existsSync, writeFileSync, statSync } from 'fs';
|
|
4
|
-
import {
|
|
5
|
-
import { isInitialized, getConfigDir, getHistoryDir, loadConfig } from '../lib/config.js';
|
|
3
|
+
import { existsSync, readFileSync, writeFileSync, statSync } from 'fs';
|
|
4
|
+
import { isInitialized, getDocsDir, loadConfig } from '../lib/config.js';
|
|
6
5
|
import { getAllSnapshots } from '../lib/history.js';
|
|
7
6
|
import { getPlanProgress } from '../lib/plan.js';
|
|
7
|
+
import { createPmptFile, SCHEMA_VERSION } from '../lib/pmptFile.js';
|
|
8
|
+
import glob from 'fast-glob';
|
|
8
9
|
/**
|
|
9
|
-
*
|
|
10
|
-
* Uses native zip command on macOS/Linux, PowerShell on Windows
|
|
10
|
+
* Read all files from a snapshot directory
|
|
11
11
|
*/
|
|
12
|
-
function
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
function readSnapshotFiles(snapshotDir) {
|
|
13
|
+
const files = {};
|
|
14
|
+
if (!existsSync(snapshotDir))
|
|
15
|
+
return files;
|
|
16
|
+
const mdFiles = glob.sync('**/*.md', { cwd: snapshotDir });
|
|
17
|
+
for (const file of mdFiles) {
|
|
18
|
+
const filePath = join(snapshotDir, file);
|
|
19
|
+
try {
|
|
20
|
+
files[file] = readFileSync(filePath, 'utf-8');
|
|
20
21
|
}
|
|
21
|
-
|
|
22
|
-
//
|
|
23
|
-
execSync(`cd "${sourceDir}" && zip -r "${outputPath}" .`, {
|
|
24
|
-
stdio: 'pipe',
|
|
25
|
-
});
|
|
22
|
+
catch {
|
|
23
|
+
// Skip files that can't be read
|
|
26
24
|
}
|
|
27
|
-
return true;
|
|
28
|
-
}
|
|
29
|
-
catch {
|
|
30
|
-
return false;
|
|
31
25
|
}
|
|
26
|
+
return files;
|
|
32
27
|
}
|
|
33
28
|
/**
|
|
34
|
-
*
|
|
29
|
+
* Read current docs folder
|
|
35
30
|
*/
|
|
36
|
-
function
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
for (const snapshot of snapshots) {
|
|
49
|
-
for (const file of snapshot.files) {
|
|
50
|
-
allFiles.add(file);
|
|
31
|
+
function readDocsFolder(docsDir) {
|
|
32
|
+
const files = {};
|
|
33
|
+
if (!existsSync(docsDir))
|
|
34
|
+
return files;
|
|
35
|
+
const mdFiles = glob.sync('**/*.md', { cwd: docsDir });
|
|
36
|
+
for (const file of mdFiles) {
|
|
37
|
+
const filePath = join(docsDir, file);
|
|
38
|
+
try {
|
|
39
|
+
files[file] = readFileSync(filePath, 'utf-8');
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// Skip files that can't be read
|
|
51
43
|
}
|
|
52
44
|
}
|
|
53
|
-
|
|
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;
|
|
45
|
+
return files;
|
|
123
46
|
}
|
|
124
47
|
export async function cmdExport(path, options) {
|
|
125
48
|
const projectPath = path ? resolve(path) : process.cwd();
|
|
@@ -138,69 +61,75 @@ export async function cmdExport(path, options) {
|
|
|
138
61
|
return;
|
|
139
62
|
}
|
|
140
63
|
const projectName = planProgress?.answers?.projectName || basename(projectPath);
|
|
141
|
-
const timestamp = new Date().toISOString().
|
|
142
|
-
const exportName =
|
|
143
|
-
// Output path
|
|
64
|
+
const timestamp = new Date().toISOString().slice(0, 10);
|
|
65
|
+
const exportName = `${projectName}-${timestamp}`;
|
|
66
|
+
// Output path - .pmpt extension
|
|
144
67
|
const outputPath = options?.output
|
|
145
68
|
? resolve(options.output)
|
|
146
|
-
: resolve(projectPath, `${exportName}.
|
|
69
|
+
: resolve(projectPath, `${exportName}.pmpt`);
|
|
147
70
|
const s = p.spinner();
|
|
148
|
-
s.start('
|
|
149
|
-
//
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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');
|
|
71
|
+
s.start('Creating .pmpt file...');
|
|
72
|
+
// Build history array with file contents
|
|
73
|
+
const history = [];
|
|
74
|
+
for (const snapshot of snapshots) {
|
|
75
|
+
const files = readSnapshotFiles(snapshot.snapshotDir);
|
|
76
|
+
history.push({
|
|
77
|
+
version: snapshot.version,
|
|
78
|
+
timestamp: snapshot.timestamp,
|
|
79
|
+
files,
|
|
80
|
+
git: snapshot.git,
|
|
81
|
+
});
|
|
172
82
|
}
|
|
173
|
-
//
|
|
174
|
-
|
|
175
|
-
|
|
83
|
+
// Read current docs
|
|
84
|
+
const docsDir = getDocsDir(projectPath);
|
|
85
|
+
const docs = readDocsFolder(docsDir);
|
|
86
|
+
// Build metadata
|
|
87
|
+
const meta = {
|
|
88
|
+
projectName,
|
|
89
|
+
description: planProgress?.answers?.productIdea,
|
|
90
|
+
createdAt: config?.createdAt || new Date().toISOString(),
|
|
91
|
+
exportedAt: new Date().toISOString(),
|
|
92
|
+
};
|
|
93
|
+
// Convert plan answers to typed format
|
|
94
|
+
const planAnswers = planProgress?.answers
|
|
95
|
+
? {
|
|
96
|
+
projectName: planProgress.answers.projectName,
|
|
97
|
+
productIdea: planProgress.answers.productIdea,
|
|
98
|
+
additionalContext: planProgress.answers.additionalContext,
|
|
99
|
+
coreFeatures: planProgress.answers.coreFeatures,
|
|
100
|
+
techStack: planProgress.answers.techStack,
|
|
101
|
+
}
|
|
102
|
+
: undefined;
|
|
103
|
+
// Create .pmpt file content
|
|
104
|
+
const pmptContent = createPmptFile(meta, planAnswers, docs, history);
|
|
105
|
+
// Write file
|
|
106
|
+
try {
|
|
107
|
+
writeFileSync(outputPath, pmptContent, 'utf-8');
|
|
176
108
|
}
|
|
177
|
-
|
|
178
|
-
// Create zip
|
|
179
|
-
const zipSuccess = createZip(tempDir, outputPath);
|
|
180
|
-
// Cleanup temp directory
|
|
181
|
-
execSync(`rm -rf "${tempDir}"`, { stdio: 'pipe' });
|
|
182
|
-
if (!zipSuccess) {
|
|
109
|
+
catch (err) {
|
|
183
110
|
s.stop('Export failed');
|
|
184
|
-
p.log.error('Failed to
|
|
185
|
-
p.log.info('Make sure `zip` command is available on your system.');
|
|
111
|
+
p.log.error('Failed to write .pmpt file.');
|
|
186
112
|
process.exit(1);
|
|
187
113
|
}
|
|
188
114
|
s.stop('Export complete!');
|
|
189
|
-
//
|
|
115
|
+
// File size
|
|
190
116
|
const fileSizeBytes = statSync(outputPath).size;
|
|
191
117
|
const fileSize = fileSizeBytes < 1024
|
|
192
118
|
? `${fileSizeBytes} B`
|
|
193
119
|
: fileSizeBytes < 1024 * 1024
|
|
194
120
|
? `${(fileSizeBytes / 1024).toFixed(1)} KB`
|
|
195
121
|
: `${(fileSizeBytes / 1024 / 1024).toFixed(1)} MB`;
|
|
122
|
+
// Summary
|
|
196
123
|
const summary = [
|
|
197
124
|
`Project: ${projectName}`,
|
|
198
125
|
`Versions: ${snapshots.length}`,
|
|
126
|
+
`Schema: v${SCHEMA_VERSION}`,
|
|
199
127
|
`Size: ${fileSize}`,
|
|
200
128
|
'',
|
|
201
129
|
`Output: ${outputPath}`,
|
|
202
130
|
];
|
|
203
131
|
p.note(summary.join('\n'), 'Export Summary');
|
|
204
|
-
p.log.info('Share this file to let others reproduce your AI development journey!');
|
|
132
|
+
p.log.info('Share this .pmpt file to let others reproduce your AI development journey!');
|
|
133
|
+
p.log.message(' pmpt import <file.pmpt> — Import on another machine');
|
|
205
134
|
p.outro('');
|
|
206
135
|
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
import { resolve, join, dirname } from 'path';
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from 'fs';
|
|
4
|
+
import { isInitialized, getConfigDir, getHistoryDir, getDocsDir, initializeProject } from '../lib/config.js';
|
|
5
|
+
import { validatePmptFile } from '../lib/pmptFile.js';
|
|
6
|
+
/**
|
|
7
|
+
* Restore history from .pmpt file
|
|
8
|
+
*/
|
|
9
|
+
function restoreHistory(historyDir, history) {
|
|
10
|
+
mkdirSync(historyDir, { recursive: true });
|
|
11
|
+
for (const version of history) {
|
|
12
|
+
const timestamp = version.timestamp.replace(/[:.]/g, '-').slice(0, 19);
|
|
13
|
+
const snapshotName = `v${version.version}-${timestamp}`;
|
|
14
|
+
const snapshotDir = join(historyDir, snapshotName);
|
|
15
|
+
mkdirSync(snapshotDir, { recursive: true });
|
|
16
|
+
// Write files
|
|
17
|
+
for (const [filename, content] of Object.entries(version.files)) {
|
|
18
|
+
const filePath = join(snapshotDir, filename);
|
|
19
|
+
const fileDir = dirname(filePath);
|
|
20
|
+
if (fileDir !== snapshotDir) {
|
|
21
|
+
mkdirSync(fileDir, { recursive: true });
|
|
22
|
+
}
|
|
23
|
+
writeFileSync(filePath, content, 'utf-8');
|
|
24
|
+
}
|
|
25
|
+
// Write metadata
|
|
26
|
+
const metaPath = join(snapshotDir, '.meta.json');
|
|
27
|
+
writeFileSync(metaPath, JSON.stringify({
|
|
28
|
+
version: version.version,
|
|
29
|
+
timestamp: version.timestamp,
|
|
30
|
+
files: Object.keys(version.files),
|
|
31
|
+
git: version.git,
|
|
32
|
+
}, null, 2), 'utf-8');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Restore docs from .pmpt file
|
|
37
|
+
*/
|
|
38
|
+
function restoreDocs(docsDir, docs) {
|
|
39
|
+
mkdirSync(docsDir, { recursive: true });
|
|
40
|
+
for (const [filename, content] of Object.entries(docs)) {
|
|
41
|
+
const filePath = join(docsDir, filename);
|
|
42
|
+
const fileDir = dirname(filePath);
|
|
43
|
+
if (fileDir !== docsDir) {
|
|
44
|
+
mkdirSync(fileDir, { recursive: true });
|
|
45
|
+
}
|
|
46
|
+
writeFileSync(filePath, content, 'utf-8');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export async function cmdImport(pmptFile, options) {
|
|
50
|
+
if (!pmptFile) {
|
|
51
|
+
p.log.error('Please provide a .pmpt file path.');
|
|
52
|
+
p.log.info('Usage: pmpt import <file.pmpt>');
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
const filePath = resolve(pmptFile);
|
|
56
|
+
if (!existsSync(filePath)) {
|
|
57
|
+
p.log.error(`File not found: ${filePath}`);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
if (!filePath.endsWith('.pmpt')) {
|
|
61
|
+
p.log.error('Please provide a .pmpt file.');
|
|
62
|
+
p.log.info('Use `pmpt export` to create a .pmpt file.');
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
p.intro('pmpt import');
|
|
66
|
+
const s = p.spinner();
|
|
67
|
+
s.start('Reading .pmpt file...');
|
|
68
|
+
// Read file
|
|
69
|
+
let fileContent;
|
|
70
|
+
try {
|
|
71
|
+
fileContent = readFileSync(filePath, 'utf-8');
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
s.stop('Read failed');
|
|
75
|
+
p.log.error('Failed to read file.');
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
// Validate
|
|
79
|
+
s.message('Validating...');
|
|
80
|
+
const validation = validatePmptFile(fileContent);
|
|
81
|
+
if (!validation.success || !validation.data) {
|
|
82
|
+
s.stop('Validation failed');
|
|
83
|
+
p.log.error(validation.error || 'Invalid .pmpt file.');
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
const pmptData = validation.data;
|
|
87
|
+
s.stop('Validation passed');
|
|
88
|
+
// Show summary and confirm
|
|
89
|
+
const summaryLines = [
|
|
90
|
+
`Project: ${pmptData.meta.projectName}`,
|
|
91
|
+
`Versions: ${pmptData.history.length}`,
|
|
92
|
+
`Schema: v${pmptData.schemaVersion}`,
|
|
93
|
+
pmptData.meta.description ? `Description: ${pmptData.meta.description.slice(0, 50)}...` : '',
|
|
94
|
+
].filter(Boolean);
|
|
95
|
+
p.note(summaryLines.join('\n'), 'Import Preview');
|
|
96
|
+
const projectPath = process.cwd();
|
|
97
|
+
// Check if already initialized
|
|
98
|
+
if (isInitialized(projectPath) && !options?.force) {
|
|
99
|
+
const overwrite = await p.confirm({
|
|
100
|
+
message: 'Project already initialized. Merge imported history?',
|
|
101
|
+
initialValue: true,
|
|
102
|
+
});
|
|
103
|
+
if (p.isCancel(overwrite)) {
|
|
104
|
+
p.cancel('Import cancelled.');
|
|
105
|
+
process.exit(0);
|
|
106
|
+
}
|
|
107
|
+
if (!overwrite) {
|
|
108
|
+
p.log.info('Use --force to overwrite existing project.');
|
|
109
|
+
p.outro('');
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const importSpinner = p.spinner();
|
|
114
|
+
importSpinner.start('Importing project...');
|
|
115
|
+
// Initialize project if not exists
|
|
116
|
+
if (!isInitialized(projectPath)) {
|
|
117
|
+
initializeProject(projectPath, { trackGit: true });
|
|
118
|
+
}
|
|
119
|
+
const pmptDir = getConfigDir(projectPath);
|
|
120
|
+
const historyDir = getHistoryDir(projectPath);
|
|
121
|
+
const docsDir = getDocsDir(projectPath);
|
|
122
|
+
// Restore history
|
|
123
|
+
restoreHistory(historyDir, pmptData.history);
|
|
124
|
+
// Restore docs
|
|
125
|
+
if (pmptData.docs) {
|
|
126
|
+
restoreDocs(docsDir, pmptData.docs);
|
|
127
|
+
}
|
|
128
|
+
// Restore plan progress
|
|
129
|
+
if (pmptData.plan) {
|
|
130
|
+
const planProgressPath = join(pmptDir, 'plan-progress.json');
|
|
131
|
+
writeFileSync(planProgressPath, JSON.stringify({
|
|
132
|
+
completed: true,
|
|
133
|
+
startedAt: pmptData.meta.createdAt,
|
|
134
|
+
updatedAt: pmptData.meta.exportedAt,
|
|
135
|
+
answers: pmptData.plan,
|
|
136
|
+
}, null, 2), 'utf-8');
|
|
137
|
+
}
|
|
138
|
+
// Count imported versions
|
|
139
|
+
let versionCount = 0;
|
|
140
|
+
if (existsSync(historyDir)) {
|
|
141
|
+
versionCount = readdirSync(historyDir).filter(d => d.startsWith('v')).length;
|
|
142
|
+
}
|
|
143
|
+
importSpinner.stop('Import complete!');
|
|
144
|
+
// Summary
|
|
145
|
+
const summary = [
|
|
146
|
+
`Project: ${pmptData.meta.projectName}`,
|
|
147
|
+
`Versions imported: ${versionCount}`,
|
|
148
|
+
`Location: ${pmptDir}`,
|
|
149
|
+
];
|
|
150
|
+
p.note(summary.join('\n'), 'Import Summary');
|
|
151
|
+
p.log.info('Next steps:');
|
|
152
|
+
p.log.message(' pmpt history — View imported versions');
|
|
153
|
+
p.log.message(' pmpt plan — View or copy AI prompt');
|
|
154
|
+
p.log.message(' pmpt save — Save a new snapshot');
|
|
155
|
+
p.outro('Ready to continue the journey!');
|
|
156
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -11,11 +11,12 @@ import { cmdPlan } from './commands/plan.js';
|
|
|
11
11
|
import { cmdSave } from './commands/save.js';
|
|
12
12
|
import { cmdSquash } from './commands/squash.js';
|
|
13
13
|
import { cmdExport } from './commands/export.js';
|
|
14
|
+
import { cmdImport } from './commands/import.js';
|
|
14
15
|
const program = new Command();
|
|
15
16
|
program
|
|
16
17
|
.name('pmpt')
|
|
17
18
|
.description('pmpt — Record and share your AI-driven product development journey')
|
|
18
|
-
.version('1.
|
|
19
|
+
.version('1.3.0')
|
|
19
20
|
.addHelpText('after', `
|
|
20
21
|
Examples:
|
|
21
22
|
$ pmpt init Initialize project
|
|
@@ -25,7 +26,8 @@ Examples:
|
|
|
25
26
|
$ pmpt history View version history
|
|
26
27
|
$ pmpt history --compact Hide minor changes
|
|
27
28
|
$ pmpt squash v2 v5 Merge versions v2-v5 into v2
|
|
28
|
-
$ pmpt export Export
|
|
29
|
+
$ pmpt export Export as .pmpt file (single JSON)
|
|
30
|
+
$ pmpt import <file.pmpt> Import from .pmpt file
|
|
29
31
|
|
|
30
32
|
Folder structure:
|
|
31
33
|
.pmpt/
|
|
@@ -67,6 +69,11 @@ program
|
|
|
67
69
|
.description('Export project history as a shareable zip archive')
|
|
68
70
|
.option('-o, --output <file>', 'Output file path')
|
|
69
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);
|
|
70
77
|
program
|
|
71
78
|
.command('plan [path]')
|
|
72
79
|
.description('Quick product planning with 5 questions — auto-generate AI prompt')
|
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
|
-
|
|
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
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* .pmpt File Format Schema (v1.0)
|
|
3
|
+
*
|
|
4
|
+
* Single JSON file format for sharing pmpt projects.
|
|
5
|
+
*/
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
// Schema version
|
|
8
|
+
export const SCHEMA_VERSION = '1.0';
|
|
9
|
+
export const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
|
|
10
|
+
// Git info schema
|
|
11
|
+
const GitInfoSchema = z.object({
|
|
12
|
+
commit: z.string(),
|
|
13
|
+
commitFull: z.string().optional(),
|
|
14
|
+
branch: z.string().optional(),
|
|
15
|
+
dirty: z.boolean().optional(),
|
|
16
|
+
tag: z.string().optional(),
|
|
17
|
+
}).optional();
|
|
18
|
+
// Single version/snapshot schema
|
|
19
|
+
const VersionSchema = z.object({
|
|
20
|
+
version: z.number().min(1),
|
|
21
|
+
timestamp: z.string(),
|
|
22
|
+
files: z.record(z.string(), z.string()), // filename -> content
|
|
23
|
+
git: GitInfoSchema,
|
|
24
|
+
});
|
|
25
|
+
// Plan answers schema
|
|
26
|
+
const PlanSchema = z.object({
|
|
27
|
+
projectName: z.string(),
|
|
28
|
+
productIdea: z.string().optional(),
|
|
29
|
+
additionalContext: z.string().optional(),
|
|
30
|
+
coreFeatures: z.string().optional(),
|
|
31
|
+
techStack: z.string().optional(),
|
|
32
|
+
}).optional();
|
|
33
|
+
// Project metadata schema
|
|
34
|
+
const MetaSchema = z.object({
|
|
35
|
+
projectName: z.string(),
|
|
36
|
+
author: z.string().optional(),
|
|
37
|
+
description: z.string().optional(),
|
|
38
|
+
createdAt: z.string(),
|
|
39
|
+
exportedAt: z.string(),
|
|
40
|
+
});
|
|
41
|
+
// Full .pmpt file schema
|
|
42
|
+
export const PmptFileSchema = z.object({
|
|
43
|
+
schemaVersion: z.string(),
|
|
44
|
+
cliMinVersion: z.string().optional(),
|
|
45
|
+
meta: MetaSchema,
|
|
46
|
+
plan: PlanSchema,
|
|
47
|
+
docs: z.record(z.string(), z.string()).optional(), // current docs
|
|
48
|
+
history: z.array(VersionSchema),
|
|
49
|
+
});
|
|
50
|
+
/**
|
|
51
|
+
* Validate .pmpt file content
|
|
52
|
+
*/
|
|
53
|
+
export function validatePmptFile(content) {
|
|
54
|
+
// Check size
|
|
55
|
+
if (content.length > MAX_FILE_SIZE) {
|
|
56
|
+
return {
|
|
57
|
+
success: false,
|
|
58
|
+
error: `File too large. Maximum size is ${MAX_FILE_SIZE / 1024 / 1024}MB`,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
// Parse JSON
|
|
62
|
+
let json;
|
|
63
|
+
try {
|
|
64
|
+
json = JSON.parse(content);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return {
|
|
68
|
+
success: false,
|
|
69
|
+
error: 'Invalid JSON format',
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
// Validate schema
|
|
73
|
+
const result = PmptFileSchema.safeParse(json);
|
|
74
|
+
if (!result.success) {
|
|
75
|
+
const firstError = result.error.errors[0];
|
|
76
|
+
return {
|
|
77
|
+
success: false,
|
|
78
|
+
error: `Validation error: ${firstError.path.join('.')} - ${firstError.message}`,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
// Check schema version
|
|
82
|
+
if (result.data.schemaVersion !== SCHEMA_VERSION) {
|
|
83
|
+
return {
|
|
84
|
+
success: false,
|
|
85
|
+
error: `Unsupported schema version: ${result.data.schemaVersion}. Current: ${SCHEMA_VERSION}`,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
success: true,
|
|
90
|
+
data: result.data,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Create .pmpt file content from project data
|
|
95
|
+
*/
|
|
96
|
+
export function createPmptFile(meta, plan, docs, history) {
|
|
97
|
+
const pmptFile = {
|
|
98
|
+
schemaVersion: SCHEMA_VERSION,
|
|
99
|
+
cliMinVersion: '1.3.0',
|
|
100
|
+
meta,
|
|
101
|
+
plan,
|
|
102
|
+
docs,
|
|
103
|
+
history,
|
|
104
|
+
};
|
|
105
|
+
return JSON.stringify(pmptFile, null, 2);
|
|
106
|
+
}
|