pmpt-cli 1.0.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/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # promptwiki-cli
2
+
3
+ CLI tool for contributing to [PromptWiki](https://pmptwiki.com) — a community-driven guide to working with AI.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g promptwiki-cli
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### Create a new document
14
+
15
+ ```bash
16
+ promptwiki new
17
+ ```
18
+
19
+ Interactive prompts will guide you through creating a properly structured markdown file.
20
+
21
+ ### Validate a document
22
+
23
+ ```bash
24
+ promptwiki validate ko/guide/beginner/my-guide.md
25
+ ```
26
+
27
+ Checks frontmatter schema, required fields, and minimum content length before submission.
28
+
29
+ ### Submit via Pull Request
30
+
31
+ ```bash
32
+ promptwiki submit ko/guide/beginner/my-guide.md
33
+ ```
34
+
35
+ Automatically forks `promptwiki/content`, creates a branch, and opens a Pull Request. Requires a GitHub Personal Access Token with `repo` scope (you'll be prompted on first use).
36
+
37
+ ### Logout
38
+
39
+ ```bash
40
+ promptwiki logout
41
+ ```
42
+
43
+ Removes stored GitHub credentials from `~/.config/promptwiki/auth.json`.
44
+
45
+ ## Workflow
46
+
47
+ ```
48
+ promptwiki new # create file interactively
49
+ # edit the file in your editor
50
+ promptwiki validate # check before submitting
51
+ promptwiki submit # fork → branch → PR
52
+ ```
53
+
54
+ Once your PR is merged into `promptwiki/content`, the site at pmptwiki.com updates automatically.
55
+
56
+ ## License
57
+
58
+ MIT
@@ -0,0 +1,90 @@
1
+ import * as p from '@clack/prompts';
2
+ import { resolve, join } from 'path';
3
+ import { readFileSync, existsSync } from 'fs';
4
+ import { isInitialized } from '../lib/config.js';
5
+ import { getAllSnapshots } from '../lib/history.js';
6
+ // Simple diff calculation: count changed lines
7
+ function calculateDiffSize(oldContent, newContent) {
8
+ const oldLines = oldContent.split('\n');
9
+ const newLines = newContent.split('\n');
10
+ let changes = 0;
11
+ const maxLen = Math.max(oldLines.length, newLines.length);
12
+ for (let i = 0; i < maxLen; i++) {
13
+ if (oldLines[i] !== newLines[i]) {
14
+ changes++;
15
+ }
16
+ }
17
+ return changes;
18
+ }
19
+ // Get total diff between two snapshots
20
+ function getSnapshotDiff(prev, curr) {
21
+ let totalChanges = 0;
22
+ const allFiles = new Set([...prev.files, ...curr.files]);
23
+ for (const file of allFiles) {
24
+ const prevPath = join(prev.snapshotDir, file);
25
+ const currPath = join(curr.snapshotDir, file);
26
+ const prevContent = existsSync(prevPath) ? readFileSync(prevPath, 'utf-8') : '';
27
+ const currContent = existsSync(currPath) ? readFileSync(currPath, 'utf-8') : '';
28
+ totalChanges += calculateDiffSize(prevContent, currContent);
29
+ }
30
+ return totalChanges;
31
+ }
32
+ export function cmdHistory(path, options) {
33
+ const projectPath = path ? resolve(path) : process.cwd();
34
+ if (!isInitialized(projectPath)) {
35
+ p.log.error('Project not initialized. Run `pmpt init` first.');
36
+ process.exit(1);
37
+ }
38
+ const snapshots = getAllSnapshots(projectPath);
39
+ if (snapshots.length === 0) {
40
+ p.intro('PromptWiki — Version History');
41
+ p.log.warn('No snapshots saved yet.');
42
+ p.log.info('Save snapshots with pmpt save or pmpt watch.');
43
+ p.outro('');
44
+ return;
45
+ }
46
+ // In compact mode, filter out versions with minimal changes
47
+ let displaySnapshots = snapshots;
48
+ const hiddenVersions = [];
49
+ if (options?.compact && snapshots.length > 1) {
50
+ displaySnapshots = [snapshots[0]]; // Always show first
51
+ for (let i = 1; i < snapshots.length; i++) {
52
+ const diffSize = getSnapshotDiff(snapshots[i - 1], snapshots[i]);
53
+ // Threshold: hide if less than 5 lines changed
54
+ if (diffSize < 5) {
55
+ hiddenVersions.push(snapshots[i].version);
56
+ }
57
+ else {
58
+ displaySnapshots.push(snapshots[i]);
59
+ }
60
+ }
61
+ }
62
+ const title = options?.compact
63
+ ? `PromptWiki — Version History (${displaySnapshots.length} shown, ${hiddenVersions.length} hidden)`
64
+ : `PromptWiki — Version History (${snapshots.length} total)`;
65
+ p.intro(title);
66
+ for (const snapshot of displaySnapshots) {
67
+ const dateStr = new Date(snapshot.timestamp).toLocaleString(undefined, {
68
+ month: '2-digit',
69
+ day: '2-digit',
70
+ hour: '2-digit',
71
+ minute: '2-digit',
72
+ });
73
+ let header = `v${snapshot.version} — ${dateStr}`;
74
+ if (snapshot.git) {
75
+ header += ` · ${snapshot.git.commit}`;
76
+ if (snapshot.git.dirty)
77
+ header += ' (dirty)';
78
+ }
79
+ const files = snapshot.files.map((f) => ` - ${f}`).join('\n');
80
+ p.note(files || ' (no files)', header);
81
+ }
82
+ if (options?.compact && hiddenVersions.length > 0) {
83
+ p.log.info(`Hidden versions (minor changes): ${hiddenVersions.map(v => `v${v}`).join(', ')}`);
84
+ p.log.info('Run without --compact to see all versions.');
85
+ }
86
+ if (!options?.compact && snapshots.length > 3) {
87
+ p.log.info('Tip: Use --compact to hide minor changes, or "pmpt squash v1 v3" to merge.');
88
+ }
89
+ p.outro('');
90
+ }
@@ -0,0 +1,130 @@
1
+ import * as p from '@clack/prompts';
2
+ import { existsSync } from 'fs';
3
+ import { resolve } from 'path';
4
+ import { initializeProject, isInitialized } from '../lib/config.js';
5
+ import { isGitRepo, getGitInfo, formatGitInfo } from '../lib/git.js';
6
+ import { cmdPlan } from './plan.js';
7
+ export async function cmdInit(path, options) {
8
+ p.intro('PromptWiki — Project Initialization');
9
+ const projectPath = path ? resolve(path) : process.cwd();
10
+ if (!existsSync(projectPath)) {
11
+ p.outro(`Path does not exist: ${projectPath}`);
12
+ process.exit(1);
13
+ }
14
+ if (isInitialized(projectPath)) {
15
+ p.outro(`Project already initialized: ${projectPath}`);
16
+ process.exit(0);
17
+ }
18
+ // Detect Git repository
19
+ const isGit = isGitRepo(projectPath);
20
+ let repoUrl = options?.repo;
21
+ let gitInfo = null;
22
+ if (isGit) {
23
+ gitInfo = getGitInfo(projectPath, repoUrl);
24
+ if (gitInfo?.repo && !repoUrl) {
25
+ repoUrl = gitInfo.repo;
26
+ }
27
+ }
28
+ // Build confirmation message
29
+ const confirmMessage = [
30
+ `Track AI conversation history in this folder?`,
31
+ ` Path: ${projectPath}`,
32
+ ];
33
+ if (isGit && gitInfo) {
34
+ confirmMessage.push(` Git: ${formatGitInfo(gitInfo)}`);
35
+ if (repoUrl) {
36
+ confirmMessage.push(` Repository: ${repoUrl}`);
37
+ }
38
+ }
39
+ const confirm = await p.confirm({
40
+ message: confirmMessage.join('\n'),
41
+ initialValue: true,
42
+ });
43
+ if (p.isCancel(confirm) || !confirm) {
44
+ p.cancel('Cancelled');
45
+ process.exit(0);
46
+ }
47
+ // If Git repo but no repoUrl, suggest connecting
48
+ if (isGit && !repoUrl) {
49
+ p.log.info(`Tip: Connect a GitHub repo with --repo for more features!`);
50
+ p.log.message(` • Auto-record commit hash for each version`);
51
+ p.log.message(` • Create PRs directly with pmpt submit`);
52
+ p.log.message(` • Others can reproduce exact code states`);
53
+ p.log.message('');
54
+ const repoChoice = await p.select({
55
+ message: 'Connect GitHub repository?',
56
+ options: [
57
+ { value: 'now', label: 'Connect now', hint: 'Enter repository URL' },
58
+ { value: 'later', label: 'Connect later', hint: 'Re-run with pmpt init --repo <url>' },
59
+ { value: 'skip', label: 'Skip', hint: 'Use Git tracking only' },
60
+ ],
61
+ });
62
+ if (p.isCancel(repoChoice)) {
63
+ p.cancel('Cancelled');
64
+ process.exit(0);
65
+ }
66
+ if (repoChoice === 'now') {
67
+ const inputRepo = await p.text({
68
+ message: 'Enter GitHub repository URL',
69
+ placeholder: 'https://github.com/username/repo',
70
+ validate: (value) => {
71
+ if (!value)
72
+ return 'Please enter repository URL';
73
+ if (!value.includes('github.com'))
74
+ return 'Please enter a GitHub URL';
75
+ return undefined;
76
+ },
77
+ });
78
+ if (!p.isCancel(inputRepo) && inputRepo) {
79
+ repoUrl = inputRepo;
80
+ }
81
+ }
82
+ }
83
+ const s = p.spinner();
84
+ s.start('Initializing project...');
85
+ try {
86
+ const config = initializeProject(projectPath, {
87
+ repo: repoUrl,
88
+ trackGit: isGit,
89
+ });
90
+ s.stop('Initialized');
91
+ const notes = [
92
+ `Path: ${config.projectPath}`,
93
+ '',
94
+ 'Folder structure:',
95
+ ' .promptwiki/',
96
+ ' ├── config.json Config file',
97
+ ' ├── pmpt/ Working folder (MD files)',
98
+ ' └── .history/ Version history',
99
+ ];
100
+ if (config.repo) {
101
+ notes.push('', `Git repository: ${config.repo}`);
102
+ }
103
+ if (config.trackGit) {
104
+ notes.push(`Git tracking: Enabled`);
105
+ }
106
+ notes.push('', 'Get started with:');
107
+ notes.push(' pmpt plan # Start product planning');
108
+ notes.push(' pmpt save # Save current state snapshot');
109
+ notes.push(' pmpt watch # Auto-detect file changes');
110
+ notes.push(' pmpt history # View version history');
111
+ p.note(notes.join('\n'), 'Project Info');
112
+ // Ask to start plan mode
113
+ const startPlan = await p.confirm({
114
+ message: 'Start plan mode? (Recommended for first-timers!)',
115
+ initialValue: true,
116
+ });
117
+ if (!p.isCancel(startPlan) && startPlan) {
118
+ p.log.message('');
119
+ await cmdPlan(projectPath);
120
+ }
121
+ else {
122
+ p.outro('PromptWiki project initialized');
123
+ }
124
+ }
125
+ catch (error) {
126
+ s.stop('Initialization failed');
127
+ p.log.error(error.message);
128
+ process.exit(1);
129
+ }
130
+ }
@@ -0,0 +1,78 @@
1
+ import * as p from '@clack/prompts';
2
+ import { writeFileSync, mkdirSync } from 'fs';
3
+ import { dirname } from 'path';
4
+ import { generateContent, generateFilePath } from '../lib/template.js';
5
+ export async function cmdNew() {
6
+ p.intro('PromptWiki — 새 문서 만들기');
7
+ const answers = await p.group({
8
+ lang: () => p.select({
9
+ message: '언어를 선택하세요',
10
+ options: [
11
+ { value: 'ko', label: '한국어 (ko)' },
12
+ { value: 'en', label: 'English (en)' },
13
+ ],
14
+ }),
15
+ purpose: () => p.select({
16
+ message: '문서 유형을 선택하세요',
17
+ options: [
18
+ { value: 'guide', label: '가이드', hint: '개념 설명 + 방법' },
19
+ { value: 'rule', label: '규칙', hint: '해야 할 것 / 하지 말 것' },
20
+ { value: 'template', label: '템플릿', hint: '복사해서 쓰는 프롬프트' },
21
+ { value: 'example', label: '사례', hint: '실제 사용 사례' },
22
+ { value: 'reference', label: '레퍼런스', hint: '참고 자료 모음' },
23
+ ],
24
+ }),
25
+ level: () => p.select({
26
+ message: '난이도를 선택하세요',
27
+ options: [
28
+ { value: 'beginner', label: '입문' },
29
+ { value: 'intermediate', label: '중급' },
30
+ { value: 'advanced', label: '고급' },
31
+ ],
32
+ }),
33
+ title: () => p.text({
34
+ message: '제목을 입력하세요',
35
+ placeholder: 'AI에게 충분한 배경을 주면 답변이 달라진다',
36
+ validate: (v) => (v.trim().length < 5 ? '5자 이상 입력해주세요' : undefined),
37
+ }),
38
+ tags: () => p.text({
39
+ message: '태그를 입력하세요 (쉼표 구분, 선택)',
40
+ placeholder: 'context, beginner, prompt',
41
+ }),
42
+ persona: () => p.multiselect({
43
+ message: '대상 독자를 선택하세요 (선택)',
44
+ options: [
45
+ { value: 'general', label: '일반' },
46
+ { value: 'power-user', label: '파워유저' },
47
+ { value: 'developer', label: '개발자' },
48
+ { value: 'organization', label: '조직' },
49
+ ],
50
+ required: false,
51
+ }),
52
+ }, {
53
+ onCancel: () => {
54
+ p.cancel('취소되었습니다');
55
+ process.exit(0);
56
+ },
57
+ });
58
+ const fm = {
59
+ title: answers.title,
60
+ purpose: answers.purpose,
61
+ level: answers.level,
62
+ lang: answers.lang,
63
+ tags: answers.tags
64
+ ? answers.tags.split(',').map((t) => t.trim()).filter(Boolean)
65
+ : [],
66
+ persona: answers.persona.length ? answers.persona : undefined,
67
+ };
68
+ const filePath = generateFilePath(fm);
69
+ const content = generateContent(fm);
70
+ mkdirSync(dirname(filePath), { recursive: true });
71
+ writeFileSync(filePath, content, 'utf-8');
72
+ p.outro(`파일이 생성되었습니다: ${filePath}
73
+
74
+ 다음 단계:
75
+ 1. 파일을 열어 본문을 작성하세요
76
+ 2. promptwiki validate ${filePath}
77
+ 3. promptwiki submit ${filePath}`);
78
+ }
@@ -0,0 +1,158 @@
1
+ import * as p from '@clack/prompts';
2
+ import { resolve } from 'path';
3
+ import { readFileSync } from 'fs';
4
+ import { isInitialized } from '../lib/config.js';
5
+ import { cmdWatch } from './watch.js';
6
+ import { PLAN_QUESTIONS, getPlanProgress, initPlanProgress, savePlanProgress, savePlanDocuments, } from '../lib/plan.js';
7
+ export async function cmdPlan(path, options) {
8
+ const projectPath = path ? resolve(path) : process.cwd();
9
+ // Check initialization
10
+ if (!isInitialized(projectPath)) {
11
+ p.intro('PromptWiki Plan');
12
+ p.log.error('Project not initialized.');
13
+ p.log.info('Run `pmpt init` first to initialize the project.');
14
+ p.outro('');
15
+ process.exit(1);
16
+ }
17
+ // Reset option
18
+ if (options?.reset) {
19
+ const confirm = await p.confirm({
20
+ message: 'Restart plan from scratch?',
21
+ initialValue: false,
22
+ });
23
+ if (p.isCancel(confirm) || !confirm) {
24
+ p.cancel('Cancelled');
25
+ process.exit(0);
26
+ }
27
+ initPlanProgress(projectPath);
28
+ }
29
+ // Check progress
30
+ let progress = getPlanProgress(projectPath);
31
+ // If already completed
32
+ if (progress?.completed) {
33
+ p.intro('PromptWiki Plan');
34
+ p.log.success('Plan already completed.');
35
+ const action = await p.select({
36
+ message: 'What would you like to do?',
37
+ options: [
38
+ { value: 'view', label: 'View AI prompt', hint: 'Copy and paste to AI' },
39
+ { value: 'restart', label: 'Restart plan', hint: 'Start fresh' },
40
+ { value: 'watch', label: 'Start file watching', hint: 'pmpt watch' },
41
+ { value: 'exit', label: 'Exit' },
42
+ ],
43
+ });
44
+ if (p.isCancel(action) || action === 'exit') {
45
+ p.outro('See you next time!');
46
+ process.exit(0);
47
+ }
48
+ if (action === 'view') {
49
+ // Read pmpt.md from pmpt folder
50
+ const { getPmptDir } = await import('../lib/config.js');
51
+ const { existsSync } = await import('fs');
52
+ const { join } = await import('path');
53
+ const pmptDir = getPmptDir(projectPath);
54
+ const promptPath = join(pmptDir, 'pmpt.md');
55
+ try {
56
+ if (existsSync(promptPath)) {
57
+ const content = readFileSync(promptPath, 'utf-8');
58
+ p.log.message('');
59
+ p.log.info('=== AI Prompt (copy the content below) ===');
60
+ p.log.message('');
61
+ console.log(content);
62
+ p.log.message('');
63
+ p.log.info(`File location: ${promptPath}`);
64
+ }
65
+ else {
66
+ p.log.error('AI prompt file not found.');
67
+ }
68
+ }
69
+ catch {
70
+ p.log.error('Failed to read AI prompt file.');
71
+ }
72
+ p.outro('');
73
+ process.exit(0);
74
+ }
75
+ if (action === 'restart') {
76
+ initPlanProgress(projectPath);
77
+ progress = getPlanProgress(projectPath);
78
+ }
79
+ if (action === 'watch') {
80
+ await cmdWatch(projectPath);
81
+ return;
82
+ }
83
+ }
84
+ // Starting fresh
85
+ if (!progress) {
86
+ progress = initPlanProgress(projectPath);
87
+ }
88
+ p.intro('PromptWiki Plan — Quick Product Planning');
89
+ p.log.info('Answer 6 questions to generate an AI-ready prompt.');
90
+ p.log.message('You can answer in any language you prefer.');
91
+ p.log.message('');
92
+ const answers = {};
93
+ // Ask questions
94
+ for (let i = 0; i < PLAN_QUESTIONS.length; i++) {
95
+ const question = PLAN_QUESTIONS[i];
96
+ const questionNum = `[${i + 1}/${PLAN_QUESTIONS.length}]`;
97
+ const answer = await p.text({
98
+ message: `${questionNum} ${question.question}`,
99
+ placeholder: question.placeholder,
100
+ validate: question.required
101
+ ? (value) => (!value ? 'This field is required' : undefined)
102
+ : undefined,
103
+ });
104
+ if (p.isCancel(answer)) {
105
+ p.cancel('Continue later with `pmpt plan`');
106
+ process.exit(0);
107
+ }
108
+ answers[question.key] = answer;
109
+ }
110
+ // Generate documents
111
+ const s = p.spinner();
112
+ s.start('Generating documents...');
113
+ const { planPath, promptPath } = savePlanDocuments(projectPath, answers);
114
+ // Update progress
115
+ progress.completed = true;
116
+ progress.answers = answers;
117
+ savePlanProgress(projectPath, progress);
118
+ s.stop('Done!');
119
+ // Show results
120
+ p.log.message('');
121
+ p.log.success('Plan completed!');
122
+ p.log.message('');
123
+ p.log.info(`Plan document: ${planPath}`);
124
+ p.log.info(`AI prompt: ${promptPath}`);
125
+ p.log.message('');
126
+ // Preview AI prompt
127
+ const showPrompt = await p.confirm({
128
+ message: 'View AI prompt now?',
129
+ initialValue: true,
130
+ });
131
+ if (!p.isCancel(showPrompt) && showPrompt) {
132
+ const content = readFileSync(promptPath, 'utf-8');
133
+ p.log.message('');
134
+ p.log.info('=== AI Prompt (copy the content below) ===');
135
+ p.log.message('');
136
+ console.log(content);
137
+ p.log.message('');
138
+ }
139
+ // Next steps
140
+ p.log.message('');
141
+ p.log.step('Next steps:');
142
+ p.log.message('1. Copy the AI prompt above and paste to Claude/ChatGPT');
143
+ p.log.message('2. Build your product with AI');
144
+ p.log.message('3. Save snapshots with pmpt save (or pmpt watch for auto-save)');
145
+ p.log.message('');
146
+ const startWatch = await p.confirm({
147
+ message: 'Start file watching? (pmpt watch)',
148
+ initialValue: false,
149
+ });
150
+ if (!p.isCancel(startWatch) && startWatch) {
151
+ p.log.message('');
152
+ await cmdWatch(projectPath);
153
+ }
154
+ else {
155
+ p.log.info('Start watching later with `pmpt watch`');
156
+ p.outro('Good luck!');
157
+ }
158
+ }
@@ -0,0 +1,48 @@
1
+ import * as p from '@clack/prompts';
2
+ import { resolve } from 'path';
3
+ import { existsSync, statSync } from 'fs';
4
+ import { isInitialized, getPmptDir } from '../lib/config.js';
5
+ import { createFullSnapshot, getTrackedFiles } from '../lib/history.js';
6
+ export async function cmdSave(fileOrPath) {
7
+ const projectPath = fileOrPath && existsSync(fileOrPath) && statSync(fileOrPath).isDirectory()
8
+ ? resolve(fileOrPath)
9
+ : process.cwd();
10
+ if (!isInitialized(projectPath)) {
11
+ p.log.error('Project not initialized. Run `pmpt init` first.');
12
+ process.exit(1);
13
+ }
14
+ p.intro('PromptWiki — Save Snapshot');
15
+ const pmptDir = getPmptDir(projectPath);
16
+ const files = getTrackedFiles(projectPath);
17
+ if (files.length === 0) {
18
+ p.log.warn('No files to save.');
19
+ p.log.info(`pmpt folder: ${pmptDir}`);
20
+ p.log.info('Start with `pmpt plan` or add MD files to the pmpt folder.');
21
+ p.outro('');
22
+ return;
23
+ }
24
+ const s = p.spinner();
25
+ s.start(`Creating snapshot of ${files.length} file(s)...`);
26
+ try {
27
+ const entry = createFullSnapshot(projectPath);
28
+ s.stop('Snapshot saved');
29
+ let msg = `v${entry.version} saved`;
30
+ if (entry.git) {
31
+ msg += ` · ${entry.git.commit}`;
32
+ if (entry.git.dirty)
33
+ msg += ' (uncommitted)';
34
+ }
35
+ p.log.success(msg);
36
+ p.log.message('');
37
+ p.log.info('Files included:');
38
+ for (const file of entry.files) {
39
+ p.log.message(` - ${file}`);
40
+ }
41
+ }
42
+ catch (error) {
43
+ s.stop('Save failed');
44
+ p.log.error(error.message);
45
+ process.exit(1);
46
+ }
47
+ p.outro('View history: pmpt history');
48
+ }
@@ -0,0 +1,75 @@
1
+ import * as p from '@clack/prompts';
2
+ import { resolve, join } from 'path';
3
+ import { existsSync, rmSync, writeFileSync, readFileSync } from 'fs';
4
+ import { isInitialized, getHistoryDir } from '../lib/config.js';
5
+ import { getAllSnapshots } from '../lib/history.js';
6
+ export async function cmdSquash(from, to, path) {
7
+ const projectPath = path ? resolve(path) : process.cwd();
8
+ if (!isInitialized(projectPath)) {
9
+ p.log.error('Project not initialized. Run `pmpt init` first.');
10
+ process.exit(1);
11
+ }
12
+ // Parse version numbers
13
+ const fromVersion = parseInt(from.replace(/^v/, ''), 10);
14
+ const toVersion = parseInt(to.replace(/^v/, ''), 10);
15
+ if (isNaN(fromVersion) || isNaN(toVersion)) {
16
+ p.log.error('Invalid version format. Use: pmpt squash v2 v3');
17
+ process.exit(1);
18
+ }
19
+ if (fromVersion >= toVersion) {
20
+ p.log.error('First version must be less than second version.');
21
+ process.exit(1);
22
+ }
23
+ const snapshots = getAllSnapshots(projectPath);
24
+ if (snapshots.length === 0) {
25
+ p.log.error('No snapshots found.');
26
+ process.exit(1);
27
+ }
28
+ // Find snapshots to squash
29
+ const toSquash = snapshots.filter(s => s.version >= fromVersion && s.version <= toVersion);
30
+ if (toSquash.length < 2) {
31
+ p.log.error(`Need at least 2 versions to squash. Found ${toSquash.length} in range v${fromVersion}-v${toVersion}.`);
32
+ process.exit(1);
33
+ }
34
+ p.intro('PromptWiki — Squash Versions');
35
+ p.log.info(`Squashing v${fromVersion} through v${toVersion} (${toSquash.length} versions)`);
36
+ const confirm = await p.confirm({
37
+ message: `This will keep v${fromVersion} and delete v${fromVersion + 1} through v${toVersion}. Continue?`,
38
+ initialValue: false,
39
+ });
40
+ if (p.isCancel(confirm) || !confirm) {
41
+ p.cancel('Cancelled');
42
+ process.exit(0);
43
+ }
44
+ const s = p.spinner();
45
+ s.start('Squashing versions...');
46
+ try {
47
+ const historyDir = getHistoryDir(projectPath);
48
+ const keepSnapshot = toSquash[0]; // Keep the first one
49
+ const deleteSnapshots = toSquash.slice(1); // Delete the rest
50
+ // Delete the snapshots we're squashing
51
+ for (const snapshot of deleteSnapshots) {
52
+ const snapshotDir = snapshot.snapshotDir;
53
+ if (existsSync(snapshotDir)) {
54
+ rmSync(snapshotDir, { recursive: true });
55
+ }
56
+ }
57
+ // Update metadata of kept snapshot to note squash
58
+ const metaPath = join(keepSnapshot.snapshotDir, '.meta.json');
59
+ if (existsSync(metaPath)) {
60
+ const meta = JSON.parse(readFileSync(metaPath, 'utf-8'));
61
+ meta.squashedFrom = toSquash.map(s => s.version);
62
+ meta.squashedAt = new Date().toISOString();
63
+ writeFileSync(metaPath, JSON.stringify(meta, null, 2), 'utf-8');
64
+ }
65
+ s.stop('Squashed');
66
+ p.log.success(`Squashed v${fromVersion}-v${toVersion} into v${fromVersion}`);
67
+ p.log.info(`Deleted ${deleteSnapshots.length} version(s)`);
68
+ }
69
+ catch (error) {
70
+ s.stop('Squash failed');
71
+ p.log.error(error.message);
72
+ process.exit(1);
73
+ }
74
+ p.outro('View history: pmpt history');
75
+ }
@@ -0,0 +1,35 @@
1
+ import * as p from '@clack/prompts';
2
+ import { resolve } from 'path';
3
+ import { isInitialized, loadConfig } from '../lib/config.js';
4
+ import { getTrackedFiles, getAllSnapshots } from '../lib/history.js';
5
+ export function cmdStatus(path) {
6
+ const projectPath = path ? resolve(path) : process.cwd();
7
+ if (!isInitialized(projectPath)) {
8
+ p.log.error('Project not initialized. Run `pmpt init` first.');
9
+ process.exit(1);
10
+ }
11
+ const config = loadConfig(projectPath);
12
+ const tracked = getTrackedFiles(projectPath);
13
+ const snapshots = getAllSnapshots(projectPath);
14
+ p.intro('PromptWiki — Project Status');
15
+ const notes = [
16
+ `Path: ${projectPath}`,
17
+ `Created: ${new Date(config.createdAt).toLocaleString()}`,
18
+ ];
19
+ if (config.lastPublished) {
20
+ notes.push(`Last published: ${new Date(config.lastPublished).toLocaleString()}`);
21
+ }
22
+ notes.push('');
23
+ notes.push(`pmpt folder: .promptwiki/pmpt/`);
24
+ notes.push(`Snapshots: ${snapshots.length}`);
25
+ notes.push('');
26
+ notes.push(`Tracked files: ${tracked.length}`);
27
+ for (const f of tracked) {
28
+ notes.push(` - ${f}`);
29
+ }
30
+ if (tracked.length === 0) {
31
+ notes.push(' (none - start with pmpt plan)');
32
+ }
33
+ p.note(notes.join('\n'), 'Project Info');
34
+ p.outro('');
35
+ }