pmpt-cli 1.14.8 → 1.14.10
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/edit.js +124 -76
- package/dist/commands/init.js +25 -3
- package/dist/commands/save.js +23 -2
- package/dist/mcp.js +21 -2
- package/package.json +1 -1
package/dist/commands/edit.js
CHANGED
|
@@ -39,97 +39,145 @@ export async function cmdEdit() {
|
|
|
39
39
|
process.exit(0);
|
|
40
40
|
}
|
|
41
41
|
const project = myProjects.find((proj) => proj.slug === slug);
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
.filter(Boolean);
|
|
64
|
-
const category = await p.select({
|
|
65
|
-
message: 'Category:',
|
|
66
|
-
initialValue: project.category || 'other',
|
|
42
|
+
// Show current settings
|
|
43
|
+
const categoryLabel = [
|
|
44
|
+
{ value: 'web-app', label: 'Web App' },
|
|
45
|
+
{ value: 'mobile-app', label: 'Mobile App' },
|
|
46
|
+
{ value: 'cli-tool', label: 'CLI Tool' },
|
|
47
|
+
{ value: 'api-backend', label: 'API/Backend' },
|
|
48
|
+
{ value: 'ai-ml', label: 'AI/ML' },
|
|
49
|
+
{ value: 'game', label: 'Game' },
|
|
50
|
+
{ value: 'library', label: 'Library' },
|
|
51
|
+
{ value: 'other', label: 'Other' },
|
|
52
|
+
].find((o) => o.value === project.category)?.label ?? project.category ?? 'Other';
|
|
53
|
+
p.note([
|
|
54
|
+
`Description: ${project.description || '(none)'}`,
|
|
55
|
+
`Tags: ${project.tags?.length ? project.tags.join(', ') : '(none)'}`,
|
|
56
|
+
`Category: ${categoryLabel}`,
|
|
57
|
+
project.productUrl ? `Product: ${project.productUrl}` : 'Product: (none)',
|
|
58
|
+
`Visibility: ${project.unlisted ? 'Unlisted' : 'Listed'}`,
|
|
59
|
+
].join('\n'), 'Current Settings');
|
|
60
|
+
// Pick fields to edit
|
|
61
|
+
const fields = await p.multiselect({
|
|
62
|
+
message: 'What do you want to edit?',
|
|
67
63
|
options: [
|
|
68
|
-
{ value: '
|
|
69
|
-
{ value: '
|
|
70
|
-
{ value: '
|
|
71
|
-
{ value: '
|
|
72
|
-
{ value: '
|
|
73
|
-
{ value: 'game', label: 'Game' },
|
|
74
|
-
{ value: 'library', label: 'Library' },
|
|
75
|
-
{ value: 'other', label: 'Other' },
|
|
64
|
+
{ value: 'description', label: 'Description' },
|
|
65
|
+
{ value: 'tags', label: 'Tags' },
|
|
66
|
+
{ value: 'category', label: 'Category' },
|
|
67
|
+
{ value: 'productUrl', label: 'Product Link' },
|
|
68
|
+
{ value: 'unlisted', label: 'Visibility (listed/unlisted)' },
|
|
76
69
|
],
|
|
77
70
|
});
|
|
78
|
-
if (p.isCancel(
|
|
71
|
+
if (p.isCancel(fields)) {
|
|
79
72
|
p.cancel('Cancelled');
|
|
80
73
|
process.exit(0);
|
|
81
74
|
}
|
|
82
|
-
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
75
|
+
const updates = {};
|
|
76
|
+
const selected = new Set(fields);
|
|
77
|
+
if (selected.has('description')) {
|
|
78
|
+
const v = await p.text({
|
|
79
|
+
message: 'Description:',
|
|
80
|
+
defaultValue: project.description,
|
|
81
|
+
placeholder: project.description,
|
|
82
|
+
});
|
|
83
|
+
if (p.isCancel(v)) {
|
|
84
|
+
p.cancel('Cancelled');
|
|
85
|
+
process.exit(0);
|
|
86
|
+
}
|
|
87
|
+
updates.description = v;
|
|
95
88
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
},
|
|
89
|
+
if (selected.has('tags')) {
|
|
90
|
+
const v = await p.text({
|
|
91
|
+
message: 'Tags (comma-separated):',
|
|
92
|
+
defaultValue: project.tags.join(', '),
|
|
93
|
+
placeholder: project.tags.join(', '),
|
|
116
94
|
});
|
|
117
|
-
if (p.isCancel(
|
|
95
|
+
if (p.isCancel(v)) {
|
|
118
96
|
p.cancel('Cancelled');
|
|
119
97
|
process.exit(0);
|
|
120
98
|
}
|
|
121
|
-
|
|
99
|
+
updates.tags = v.split(',').map((t) => t.trim().toLowerCase()).filter(Boolean);
|
|
100
|
+
}
|
|
101
|
+
if (selected.has('category')) {
|
|
102
|
+
const v = await p.select({
|
|
103
|
+
message: 'Category:',
|
|
104
|
+
initialValue: project.category || 'other',
|
|
105
|
+
options: [
|
|
106
|
+
{ value: 'web-app', label: 'Web App' },
|
|
107
|
+
{ value: 'mobile-app', label: 'Mobile App' },
|
|
108
|
+
{ value: 'cli-tool', label: 'CLI Tool' },
|
|
109
|
+
{ value: 'api-backend', label: 'API/Backend' },
|
|
110
|
+
{ value: 'ai-ml', label: 'AI/ML' },
|
|
111
|
+
{ value: 'game', label: 'Game' },
|
|
112
|
+
{ value: 'library', label: 'Library' },
|
|
113
|
+
{ value: 'other', label: 'Other' },
|
|
114
|
+
],
|
|
115
|
+
});
|
|
116
|
+
if (p.isCancel(v)) {
|
|
117
|
+
p.cancel('Cancelled');
|
|
118
|
+
process.exit(0);
|
|
119
|
+
}
|
|
120
|
+
updates.category = v;
|
|
121
|
+
}
|
|
122
|
+
if (selected.has('productUrl')) {
|
|
123
|
+
const linkType = await p.select({
|
|
124
|
+
message: 'Product link:',
|
|
125
|
+
initialValue: project.productUrlType || 'none',
|
|
126
|
+
options: [
|
|
127
|
+
{ value: 'none', label: 'No link' },
|
|
128
|
+
{ value: 'git', label: 'Git Repository' },
|
|
129
|
+
{ value: 'url', label: 'Website / URL' },
|
|
130
|
+
],
|
|
131
|
+
});
|
|
132
|
+
if (p.isCancel(linkType)) {
|
|
133
|
+
p.cancel('Cancelled');
|
|
134
|
+
process.exit(0);
|
|
135
|
+
}
|
|
136
|
+
if (linkType === 'none') {
|
|
137
|
+
updates.productUrl = '';
|
|
138
|
+
updates.productUrlType = '';
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
updates.productUrlType = linkType;
|
|
142
|
+
const urlInput = await p.text({
|
|
143
|
+
message: 'Product URL:',
|
|
144
|
+
placeholder: linkType === 'git'
|
|
145
|
+
? `https://github.com/${auth.username}/${slug}`
|
|
146
|
+
: 'https://...',
|
|
147
|
+
defaultValue: project.productUrl || '',
|
|
148
|
+
validate: (v) => {
|
|
149
|
+
if (!v.trim())
|
|
150
|
+
return 'URL is required when link type is selected.';
|
|
151
|
+
try {
|
|
152
|
+
new URL(v);
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
return 'Invalid URL format.';
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
if (p.isCancel(urlInput)) {
|
|
160
|
+
p.cancel('Cancelled');
|
|
161
|
+
process.exit(0);
|
|
162
|
+
}
|
|
163
|
+
updates.productUrl = urlInput;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (selected.has('unlisted')) {
|
|
167
|
+
const v = await p.confirm({
|
|
168
|
+
message: 'Unlisted? (hidden from explore, accessible via direct URL)',
|
|
169
|
+
initialValue: project.unlisted ?? false,
|
|
170
|
+
});
|
|
171
|
+
if (p.isCancel(v)) {
|
|
172
|
+
p.cancel('Cancelled');
|
|
173
|
+
process.exit(0);
|
|
174
|
+
}
|
|
175
|
+
updates.unlisted = !!v;
|
|
122
176
|
}
|
|
123
177
|
const s2 = p.spinner();
|
|
124
178
|
s2.start('Updating...');
|
|
125
179
|
try {
|
|
126
|
-
await editProject(auth.token, slug,
|
|
127
|
-
description: description,
|
|
128
|
-
tags,
|
|
129
|
-
category: category,
|
|
130
|
-
productUrl,
|
|
131
|
-
productUrlType,
|
|
132
|
-
});
|
|
180
|
+
await editProject(auth.token, slug, updates);
|
|
133
181
|
s2.stop('Updated!');
|
|
134
182
|
p.log.success(`Project "${slug}" has been updated.`);
|
|
135
183
|
}
|
package/dist/commands/init.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as p from '@clack/prompts';
|
|
2
|
-
import { existsSync, readFileSync } from 'fs';
|
|
3
|
-
import { resolve } from 'path';
|
|
4
|
-
import { initializeProject, isInitialized } from '../lib/config.js';
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
3
|
+
import { resolve, basename, join } from 'path';
|
|
4
|
+
import { initializeProject, isInitialized, getDocsDir } from '../lib/config.js';
|
|
5
5
|
import { isGitRepo, getGitInfo, formatGitInfo, getCommitCount } from '../lib/git.js';
|
|
6
6
|
import { cmdPlan } from './plan.js';
|
|
7
7
|
import { scanProject, scanResultToAnswers } from '../lib/scanner.js';
|
|
@@ -232,6 +232,7 @@ export async function cmdInit(path, options) {
|
|
|
232
232
|
await cmdPlan(projectPath);
|
|
233
233
|
}
|
|
234
234
|
else {
|
|
235
|
+
ensureMinimalDocs(projectPath);
|
|
235
236
|
p.outro('Ready! Run `pmpt plan` when you want to start.');
|
|
236
237
|
}
|
|
237
238
|
}
|
|
@@ -246,6 +247,7 @@ export async function cmdInit(path, options) {
|
|
|
246
247
|
await cmdPlan(projectPath);
|
|
247
248
|
}
|
|
248
249
|
else {
|
|
250
|
+
ensureMinimalDocs(projectPath);
|
|
249
251
|
p.outro('Ready! Run `pmpt plan` when you want to start.');
|
|
250
252
|
}
|
|
251
253
|
}
|
|
@@ -256,3 +258,23 @@ export async function cmdInit(path, options) {
|
|
|
256
258
|
process.exit(1);
|
|
257
259
|
}
|
|
258
260
|
}
|
|
261
|
+
/** Create minimal pmpt.md so progress tracking works from the start */
|
|
262
|
+
function ensureMinimalDocs(projectPath) {
|
|
263
|
+
const docsDir = getDocsDir(projectPath);
|
|
264
|
+
const pmptMdPath = join(docsDir, 'pmpt.md');
|
|
265
|
+
if (existsSync(pmptMdPath))
|
|
266
|
+
return;
|
|
267
|
+
const name = basename(projectPath);
|
|
268
|
+
const skeleton = [
|
|
269
|
+
`# ${name}`,
|
|
270
|
+
'',
|
|
271
|
+
'## Progress',
|
|
272
|
+
'- Project initialized',
|
|
273
|
+
'',
|
|
274
|
+
'## Snapshot Log',
|
|
275
|
+
'',
|
|
276
|
+
'## Decisions',
|
|
277
|
+
'',
|
|
278
|
+
].join('\n');
|
|
279
|
+
writeFileSync(pmptMdPath, skeleton, 'utf-8');
|
|
280
|
+
}
|
package/dist/commands/save.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import * as p from '@clack/prompts';
|
|
2
|
-
import { resolve, join } from 'path';
|
|
2
|
+
import { resolve, join, basename } from 'path';
|
|
3
3
|
import { existsSync, statSync, readFileSync, writeFileSync } from 'fs';
|
|
4
|
-
import { isInitialized, getDocsDir } from '../lib/config.js';
|
|
4
|
+
import { isInitialized, getDocsDir, loadConfig } from '../lib/config.js';
|
|
5
5
|
import { createFullSnapshot, getTrackedFiles, getAllSnapshots } from '../lib/history.js';
|
|
6
|
+
import { getPlanProgress } from '../lib/plan.js';
|
|
6
7
|
export async function cmdSave(fileOrPath) {
|
|
7
8
|
const projectPath = fileOrPath && existsSync(fileOrPath) && statSync(fileOrPath).isDirectory()
|
|
8
9
|
? resolve(fileOrPath)
|
|
@@ -21,6 +22,26 @@ export async function cmdSave(fileOrPath) {
|
|
|
21
22
|
p.outro('');
|
|
22
23
|
return;
|
|
23
24
|
}
|
|
25
|
+
// Auto-create pmpt.md if missing
|
|
26
|
+
const pmptMdPath = join(docsDir, 'pmpt.md');
|
|
27
|
+
if (!existsSync(pmptMdPath)) {
|
|
28
|
+
const planProgress = getPlanProgress(projectPath);
|
|
29
|
+
const config = loadConfig(projectPath);
|
|
30
|
+
const name = planProgress?.answers?.projectName || config?.lastPublishedSlug || basename(projectPath);
|
|
31
|
+
const skeleton = [
|
|
32
|
+
`# ${name}`,
|
|
33
|
+
'',
|
|
34
|
+
'## Progress',
|
|
35
|
+
'- Project initialized',
|
|
36
|
+
'',
|
|
37
|
+
'## Snapshot Log',
|
|
38
|
+
'',
|
|
39
|
+
'## Decisions',
|
|
40
|
+
'',
|
|
41
|
+
].join('\n');
|
|
42
|
+
writeFileSync(pmptMdPath, skeleton, 'utf-8');
|
|
43
|
+
p.log.info('Created pmpt.md (project tracking document)');
|
|
44
|
+
}
|
|
24
45
|
// Ask for summary
|
|
25
46
|
const summary = await p.text({
|
|
26
47
|
message: 'What did you accomplish? (this is shown on your project page)',
|
package/dist/mcp.js
CHANGED
|
@@ -12,6 +12,7 @@ import { resolve, join } from 'path';
|
|
|
12
12
|
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
13
13
|
import glob from 'fast-glob';
|
|
14
14
|
import { createRequire } from 'module';
|
|
15
|
+
import { basename } from 'path';
|
|
15
16
|
import { isInitialized, loadConfig, saveConfig, getDocsDir, getHistoryDir } from './lib/config.js';
|
|
16
17
|
import { createFullSnapshot, getAllSnapshots, getTrackedFiles, resolveFullSnapshot } from './lib/history.js';
|
|
17
18
|
import { computeQuality } from './lib/quality.js';
|
|
@@ -105,14 +106,32 @@ server.tool('pmpt_save', 'Save a snapshot of .pmpt/docs/ files. Call after compl
|
|
|
105
106
|
try {
|
|
106
107
|
const pp = resolveProjectPath(projectPath);
|
|
107
108
|
assertInitialized(pp);
|
|
109
|
+
const docsDir = getDocsDir(pp);
|
|
110
|
+
// Auto-create pmpt.md if missing
|
|
111
|
+
const pmptMdPath = join(docsDir, 'pmpt.md');
|
|
112
|
+
if (!existsSync(pmptMdPath)) {
|
|
113
|
+
const planProgress = getPlanProgress(pp);
|
|
114
|
+
const config = loadConfig(pp);
|
|
115
|
+
const name = planProgress?.answers?.projectName || config?.lastPublishedSlug || basename(pp);
|
|
116
|
+
const skeleton = [
|
|
117
|
+
`# ${name}`,
|
|
118
|
+
'',
|
|
119
|
+
'## Progress',
|
|
120
|
+
'- Project initialized',
|
|
121
|
+
'',
|
|
122
|
+
'## Snapshot Log',
|
|
123
|
+
'',
|
|
124
|
+
'## Decisions',
|
|
125
|
+
'',
|
|
126
|
+
].join('\n');
|
|
127
|
+
writeFileSync(pmptMdPath, skeleton, 'utf-8');
|
|
128
|
+
}
|
|
108
129
|
const tracked = getTrackedFiles(pp);
|
|
109
130
|
if (tracked.length === 0) {
|
|
110
131
|
return { content: [{ type: 'text', text: 'No files to save. Add .md files to .pmpt/docs/ first.' }] };
|
|
111
132
|
}
|
|
112
133
|
// Auto-update pmpt.md with summary before snapshot
|
|
113
134
|
if (summary) {
|
|
114
|
-
const docsDir = getDocsDir(pp);
|
|
115
|
-
const pmptMdPath = join(docsDir, 'pmpt.md');
|
|
116
135
|
if (existsSync(pmptMdPath)) {
|
|
117
136
|
let content = readFileSync(pmptMdPath, 'utf-8');
|
|
118
137
|
const snapshots = getAllSnapshots(pp);
|