pmpt-cli 1.14.18 → 1.15.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/clone.js +11 -4
- package/dist/commands/doctor.js +220 -0
- package/dist/commands/init.js +43 -10
- package/dist/commands/mcp-setup.js +73 -9
- package/dist/commands/plan.js +12 -2
- package/dist/commands/remix.js +7 -3
- package/dist/index.js +12 -1
- package/dist/lib/config.js +83 -1
- package/dist/lib/plan.js +20 -5
- package/package.json +1 -1
package/dist/commands/clone.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as p from '@clack/prompts';
|
|
2
2
|
import { join, dirname, resolve, sep } from 'path';
|
|
3
3
|
import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
|
|
4
|
-
import { isInitialized, getConfigDir, getHistoryDir, getDocsDir, initializeProject } from '../lib/config.js';
|
|
4
|
+
import { isInitialized, getConfigDir, getHistoryDir, getDocsDir, initializeProject, ensurePmptClaudeMd, ensureMcpJson } from '../lib/config.js';
|
|
5
5
|
import { validatePmptFile, isSafeFilename } from '../lib/pmptFile.js';
|
|
6
6
|
import { fetchPmptFile, trackClone } from '../lib/api.js';
|
|
7
7
|
import { copyToClipboard } from '../lib/clipboard.js';
|
|
@@ -112,6 +112,9 @@ export async function cmdClone(slug) {
|
|
|
112
112
|
const docsDir = getDocsDir(projectPath);
|
|
113
113
|
// History is not restored — user's journey starts fresh from v1.
|
|
114
114
|
// The version summary is embedded in pmpt.ai.md for AI reference.
|
|
115
|
+
// Add pmpt MCP instructions to CLAUDE.md and register .mcp.json for Claude Code
|
|
116
|
+
ensurePmptClaudeMd(projectPath);
|
|
117
|
+
ensureMcpJson(projectPath);
|
|
115
118
|
if (pmptData.docs) {
|
|
116
119
|
restoreDocs(docsDir, pmptData.docs);
|
|
117
120
|
}
|
|
@@ -161,9 +164,13 @@ export async function cmdClone(slug) {
|
|
|
161
164
|
`- When requirements change or new decisions are made`,
|
|
162
165
|
'',
|
|
163
166
|
`Keep the Progress and Snapshot Log sections in pmpt.md up to date.`,
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
+
`**Always save proactively after milestones — do not wait for the user to ask.**`,
|
|
168
|
+
``,
|
|
169
|
+
`Try the pmpt MCP tool first:`,
|
|
170
|
+
`- Claude Code: call \`mcp__pmpt__pmpt_save\` with a descriptive \`summary\``,
|
|
171
|
+
`- Other MCP clients: call \`pmpt_save\` with a descriptive \`summary\``,
|
|
172
|
+
``,
|
|
173
|
+
`If no MCP tool is available, run \`pmpt save\` in terminal.`,
|
|
167
174
|
'',
|
|
168
175
|
'---',
|
|
169
176
|
'',
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
import { execSync } from 'child_process';
|
|
3
|
+
import { existsSync, readFileSync } from 'fs';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { homedir } from 'os';
|
|
6
|
+
import { detectPmptMcpPath, getWrapperStatus } from './mcp-setup.js';
|
|
7
|
+
function checkNodeVersion() {
|
|
8
|
+
const version = process.versions.node;
|
|
9
|
+
const major = parseInt(version.split('.')[0], 10);
|
|
10
|
+
if (major >= 18) {
|
|
11
|
+
return { label: 'Node.js', status: 'ok', detail: `v${version}` };
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
label: 'Node.js',
|
|
15
|
+
status: 'fail',
|
|
16
|
+
detail: `v${version} (requires ≥18)`,
|
|
17
|
+
fix: 'Update Node.js: https://nodejs.org',
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function checkNvm() {
|
|
21
|
+
const nvmDir = process.env.NVM_DIR || join(homedir(), '.nvm');
|
|
22
|
+
const isNvm = !!process.env.NVM_DIR || process.argv[1]?.includes('.nvm/');
|
|
23
|
+
if (!isNvm && !existsSync(nvmDir)) {
|
|
24
|
+
return { label: 'nvm', status: 'ok', detail: 'Not using nvm (no PATH issues)' };
|
|
25
|
+
}
|
|
26
|
+
// Check if default alias is set
|
|
27
|
+
let defaultAlias = '';
|
|
28
|
+
try {
|
|
29
|
+
const aliasFile = join(nvmDir, 'alias', 'default');
|
|
30
|
+
if (existsSync(aliasFile)) {
|
|
31
|
+
defaultAlias = readFileSync(aliasFile, 'utf-8').trim();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch { /* ignore */ }
|
|
35
|
+
if (defaultAlias) {
|
|
36
|
+
return {
|
|
37
|
+
label: 'nvm',
|
|
38
|
+
status: 'warn',
|
|
39
|
+
detail: `Active (default: ${defaultAlias}). MCP clients may not load nvm in non-interactive shells.`,
|
|
40
|
+
fix: 'Use "pmpt mcp-setup" to create a stable wrapper, or consider volta/fnm.',
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
label: 'nvm',
|
|
45
|
+
status: 'warn',
|
|
46
|
+
detail: 'Active, no default alias set. MCP path may break across sessions.',
|
|
47
|
+
fix: 'Run "nvm alias default <version>" and "pmpt mcp-setup".',
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function checkPmptMcpBinary() {
|
|
51
|
+
const path = detectPmptMcpPath();
|
|
52
|
+
if (path) {
|
|
53
|
+
return { label: 'pmpt-mcp binary', status: 'ok', detail: path };
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
label: 'pmpt-mcp binary',
|
|
57
|
+
status: 'fail',
|
|
58
|
+
detail: 'Not found in PATH or sibling directory',
|
|
59
|
+
fix: 'Run "npm install -g pmpt-cli" to install.',
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function checkWrapper() {
|
|
63
|
+
const wrapper = getWrapperStatus();
|
|
64
|
+
if (!wrapper.exists) {
|
|
65
|
+
const isNvm = !!process.env.NVM_DIR || process.argv[1]?.includes('.nvm/');
|
|
66
|
+
if (isNvm) {
|
|
67
|
+
return {
|
|
68
|
+
label: 'Wrapper script',
|
|
69
|
+
status: 'warn',
|
|
70
|
+
detail: 'Not created (recommended for nvm users)',
|
|
71
|
+
fix: 'Run "pmpt mcp-setup" to create ~/.pmpt/bin/pmpt-mcp wrapper.',
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return { label: 'Wrapper script', status: 'ok', detail: 'Not needed (not using nvm)' };
|
|
75
|
+
}
|
|
76
|
+
if (wrapper.targetValid) {
|
|
77
|
+
return { label: 'Wrapper script', status: 'ok', detail: `OK → ${wrapper.targetPath}` };
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
label: 'Wrapper script',
|
|
81
|
+
status: 'fail',
|
|
82
|
+
detail: `Stale — target binary missing: ${wrapper.targetPath}`,
|
|
83
|
+
fix: 'Run "pmpt mcp-setup" to update the wrapper after Node version change.',
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function checkMcpClients() {
|
|
87
|
+
const results = [];
|
|
88
|
+
// Claude Code
|
|
89
|
+
try {
|
|
90
|
+
execSync('which claude', { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
91
|
+
try {
|
|
92
|
+
const list = execSync('claude mcp list', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
93
|
+
if (list.includes('pmpt')) {
|
|
94
|
+
results.push({ label: 'Claude Code MCP', status: 'ok', detail: 'pmpt registered' });
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
results.push({
|
|
98
|
+
label: 'Claude Code MCP',
|
|
99
|
+
status: 'warn',
|
|
100
|
+
detail: 'Claude Code found but pmpt not registered',
|
|
101
|
+
fix: 'Run "pmpt mcp-setup" and select Claude Code.',
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
results.push({
|
|
107
|
+
label: 'Claude Code MCP',
|
|
108
|
+
status: 'warn',
|
|
109
|
+
detail: 'Claude Code found but "claude mcp list" failed',
|
|
110
|
+
fix: 'Make sure Claude Code is up to date.',
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
// Claude Code not installed — skip
|
|
116
|
+
}
|
|
117
|
+
// Cursor
|
|
118
|
+
const cursorConfig = join(homedir(), '.cursor', 'mcp.json');
|
|
119
|
+
if (existsSync(join(homedir(), '.cursor'))) {
|
|
120
|
+
try {
|
|
121
|
+
if (existsSync(cursorConfig)) {
|
|
122
|
+
const content = JSON.parse(readFileSync(cursorConfig, 'utf-8'));
|
|
123
|
+
if (content?.mcpServers?.pmpt) {
|
|
124
|
+
const cmd = content.mcpServers.pmpt.command;
|
|
125
|
+
if (existsSync(cmd)) {
|
|
126
|
+
results.push({ label: 'Cursor MCP', status: 'ok', detail: `pmpt → ${cmd}` });
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
results.push({
|
|
130
|
+
label: 'Cursor MCP',
|
|
131
|
+
status: 'fail',
|
|
132
|
+
detail: `pmpt registered but binary missing: ${cmd}`,
|
|
133
|
+
fix: 'Run "pmpt mcp-setup" to update the path.',
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
results.push({
|
|
139
|
+
label: 'Cursor MCP',
|
|
140
|
+
status: 'warn',
|
|
141
|
+
detail: 'Cursor found but pmpt not in mcp.json',
|
|
142
|
+
fix: 'Run "pmpt mcp-setup" and select Cursor.',
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
results.push({ label: 'Cursor MCP', status: 'warn', detail: 'Could not parse ~/.cursor/mcp.json' });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// Project .mcp.json
|
|
152
|
+
const projectMcp = join(process.cwd(), '.mcp.json');
|
|
153
|
+
if (existsSync(projectMcp)) {
|
|
154
|
+
try {
|
|
155
|
+
const content = JSON.parse(readFileSync(projectMcp, 'utf-8'));
|
|
156
|
+
if (content?.mcpServers?.pmpt) {
|
|
157
|
+
const cmd = content.mcpServers.pmpt.command;
|
|
158
|
+
if (existsSync(cmd)) {
|
|
159
|
+
results.push({ label: '.mcp.json', status: 'ok', detail: `pmpt → ${cmd}` });
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
results.push({
|
|
163
|
+
label: '.mcp.json',
|
|
164
|
+
status: 'fail',
|
|
165
|
+
detail: `pmpt registered but binary missing: ${cmd}`,
|
|
166
|
+
fix: 'Run "pmpt mcp-setup" to update the path.',
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
results.push({ label: '.mcp.json', status: 'warn', detail: 'Could not parse .mcp.json' });
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (results.length === 0) {
|
|
176
|
+
results.push({
|
|
177
|
+
label: 'MCP clients',
|
|
178
|
+
status: 'warn',
|
|
179
|
+
detail: 'No MCP client configuration found',
|
|
180
|
+
fix: 'Run "pmpt mcp-setup" to configure an AI tool.',
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
return results;
|
|
184
|
+
}
|
|
185
|
+
const icons = { ok: '✓', warn: '⚠', fail: '✗' };
|
|
186
|
+
const colors = { ok: '\x1b[32m', warn: '\x1b[33m', fail: '\x1b[31m' };
|
|
187
|
+
const reset = '\x1b[0m';
|
|
188
|
+
export async function cmdDoctor() {
|
|
189
|
+
p.intro('pmpt doctor');
|
|
190
|
+
const s = p.spinner();
|
|
191
|
+
s.start('Running diagnostics...');
|
|
192
|
+
const checks = [
|
|
193
|
+
checkNodeVersion(),
|
|
194
|
+
checkNvm(),
|
|
195
|
+
checkPmptMcpBinary(),
|
|
196
|
+
checkWrapper(),
|
|
197
|
+
...checkMcpClients(),
|
|
198
|
+
];
|
|
199
|
+
s.stop('Diagnostics complete');
|
|
200
|
+
// Display results
|
|
201
|
+
const lines = [];
|
|
202
|
+
let hasIssues = false;
|
|
203
|
+
for (const check of checks) {
|
|
204
|
+
const icon = icons[check.status];
|
|
205
|
+
const color = colors[check.status];
|
|
206
|
+
lines.push(`${color}${icon}${reset} ${check.label}: ${check.detail}`);
|
|
207
|
+
if (check.fix) {
|
|
208
|
+
lines.push(` → ${check.fix}`);
|
|
209
|
+
hasIssues = true;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
p.note(lines.join('\n'), 'Diagnostic Results');
|
|
213
|
+
if (hasIssues) {
|
|
214
|
+
p.log.warn('Some issues found. Run the suggested fixes above.');
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
p.log.success('Everything looks good!');
|
|
218
|
+
}
|
|
219
|
+
p.outro('');
|
|
220
|
+
}
|
package/dist/commands/init.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import * as p from '@clack/prompts';
|
|
2
2
|
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
3
3
|
import { resolve, basename, join } from 'path';
|
|
4
|
-
import { initializeProject, isInitialized, getDocsDir } from '../lib/config.js';
|
|
4
|
+
import { initializeProject, isInitialized, getDocsDir, ensurePmptClaudeMd, ensureMcpJson } 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';
|
|
8
|
-
import { savePlanDocuments, initPlanProgress, savePlanProgress } from '../lib/plan.js';
|
|
8
|
+
import { savePlanDocuments, initPlanProgress, savePlanProgress, generateAnswersTemplate } from '../lib/plan.js';
|
|
9
9
|
import { copyToClipboard } from '../lib/clipboard.js';
|
|
10
10
|
export async function cmdInit(path, options) {
|
|
11
11
|
p.intro('pmpt init');
|
|
@@ -97,6 +97,9 @@ export async function cmdInit(path, options) {
|
|
|
97
97
|
gitCommitsAtInit: isGit ? gitCommits : undefined,
|
|
98
98
|
});
|
|
99
99
|
s.stop('Initialized');
|
|
100
|
+
// Add pmpt MCP instructions to CLAUDE.md and register .mcp.json for Claude Code
|
|
101
|
+
ensurePmptClaudeMd(projectPath);
|
|
102
|
+
ensureMcpJson(projectPath);
|
|
100
103
|
// Build folder structure display
|
|
101
104
|
const notes = [
|
|
102
105
|
`Path: ${config.projectPath}`,
|
|
@@ -152,6 +155,7 @@ export async function cmdInit(path, options) {
|
|
|
152
155
|
options: [
|
|
153
156
|
{ value: 'auto', label: 'Auto-generate plan', hint: 'Recommended — instant AI prompt from project analysis' },
|
|
154
157
|
{ value: 'manual', label: 'Manual planning', hint: '5 questions interactive flow' },
|
|
158
|
+
{ value: 'file', label: 'Fill in answers file', hint: 'Windows/PowerShell friendly — edit a JSON file then run pmpt plan --answers-file' },
|
|
155
159
|
{ value: 'skip', label: 'Skip for now' },
|
|
156
160
|
],
|
|
157
161
|
});
|
|
@@ -159,6 +163,17 @@ export async function cmdInit(path, options) {
|
|
|
159
163
|
p.cancel('Cancelled');
|
|
160
164
|
process.exit(0);
|
|
161
165
|
}
|
|
166
|
+
if (scanChoice === 'file') {
|
|
167
|
+
const outFile = 'answers.json';
|
|
168
|
+
const template = generateAnswersTemplate();
|
|
169
|
+
writeFileSync(resolve(projectPath, outFile), JSON.stringify(template, null, 2), 'utf-8');
|
|
170
|
+
p.log.success(`Template created: ${outFile}`);
|
|
171
|
+
p.log.info('Next steps:');
|
|
172
|
+
p.log.message(` 1. Open and fill in ${outFile}`);
|
|
173
|
+
p.log.message(` 2. pmpt plan --answers-file ${outFile}`);
|
|
174
|
+
p.outro('Ready!');
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
162
177
|
if (scanChoice === 'auto') {
|
|
163
178
|
// Ask for project description
|
|
164
179
|
const defaultDesc = scanResult.readmeDescription
|
|
@@ -233,23 +248,41 @@ export async function cmdInit(path, options) {
|
|
|
233
248
|
}
|
|
234
249
|
else {
|
|
235
250
|
ensureMinimalDocs(projectPath);
|
|
251
|
+
p.log.info('Tip: On Windows/PowerShell, use a template file to avoid paste issues:');
|
|
252
|
+
p.log.message(' pmpt plan --template # creates answers.json');
|
|
253
|
+
p.log.message(' pmpt plan --answers-file answers.json');
|
|
236
254
|
p.outro('Ready! Run `pmpt plan` when you want to start.');
|
|
237
255
|
}
|
|
238
256
|
}
|
|
239
257
|
else {
|
|
240
258
|
// New/empty project — original flow
|
|
241
|
-
const startPlan = await p.
|
|
242
|
-
message: 'Start planning?
|
|
243
|
-
|
|
259
|
+
const startPlan = await p.select({
|
|
260
|
+
message: 'Start planning?',
|
|
261
|
+
options: [
|
|
262
|
+
{ value: 'yes', label: 'Yes — answer 5 questions now', hint: 'Interactive flow' },
|
|
263
|
+
{ value: 'file', label: 'Fill in answers file', hint: 'Windows/PowerShell friendly — edit a JSON file then run pmpt plan --answers-file' },
|
|
264
|
+
{ value: 'no', label: 'Skip for now' },
|
|
265
|
+
],
|
|
244
266
|
});
|
|
245
|
-
if (
|
|
246
|
-
p.log.message('');
|
|
247
|
-
await cmdPlan(projectPath);
|
|
248
|
-
}
|
|
249
|
-
else {
|
|
267
|
+
if (p.isCancel(startPlan) || startPlan === 'no') {
|
|
250
268
|
ensureMinimalDocs(projectPath);
|
|
251
269
|
p.outro('Ready! Run `pmpt plan` when you want to start.');
|
|
252
270
|
}
|
|
271
|
+
else if (startPlan === 'file') {
|
|
272
|
+
const outFile = 'answers.json';
|
|
273
|
+
const template = generateAnswersTemplate();
|
|
274
|
+
writeFileSync(resolve(projectPath, outFile), JSON.stringify(template, null, 2), 'utf-8');
|
|
275
|
+
ensureMinimalDocs(projectPath);
|
|
276
|
+
p.log.success(`Template created: ${outFile}`);
|
|
277
|
+
p.log.info('Next steps:');
|
|
278
|
+
p.log.message(` 1. Open and fill in ${outFile}`);
|
|
279
|
+
p.log.message(` 2. pmpt plan --answers-file ${outFile}`);
|
|
280
|
+
p.outro('Ready!');
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
p.log.message('');
|
|
284
|
+
await cmdPlan(projectPath);
|
|
285
|
+
}
|
|
253
286
|
}
|
|
254
287
|
}
|
|
255
288
|
catch (error) {
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import * as p from '@clack/prompts';
|
|
2
2
|
import { execSync } from 'child_process';
|
|
3
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
3
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync } from 'fs';
|
|
4
4
|
import { join, dirname } from 'path';
|
|
5
5
|
import { homedir } from 'os';
|
|
6
|
-
|
|
6
|
+
/** Stable wrapper path that survives nvm version switches */
|
|
7
|
+
const WRAPPER_DIR = join(homedir(), '.pmpt', 'bin');
|
|
8
|
+
const WRAPPER_PATH = join(WRAPPER_DIR, 'pmpt-mcp');
|
|
9
|
+
export function detectPmptMcpPath() {
|
|
7
10
|
// Strategy 1: sibling to the current pmpt binary (same bin directory)
|
|
8
11
|
const pmptBin = process.argv[1];
|
|
9
12
|
const siblingPath = join(dirname(pmptBin), 'pmpt-mcp');
|
|
@@ -24,6 +27,51 @@ function detectPmptMcpPath() {
|
|
|
24
27
|
}
|
|
25
28
|
return null;
|
|
26
29
|
}
|
|
30
|
+
/** Detect if nvm is in use */
|
|
31
|
+
function isNvmEnvironment() {
|
|
32
|
+
return !!process.env.NVM_DIR || process.argv[1]?.includes('.nvm/');
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Create a wrapper script at ~/.pmpt/bin/pmpt-mcp that:
|
|
36
|
+
* 1. Loads nvm if available
|
|
37
|
+
* 2. Executes the real pmpt-mcp binary
|
|
38
|
+
* This makes the MCP path stable across Node version changes.
|
|
39
|
+
*/
|
|
40
|
+
function createWrapperScript(realBinaryPath) {
|
|
41
|
+
mkdirSync(WRAPPER_DIR, { recursive: true });
|
|
42
|
+
const nvmDir = process.env.NVM_DIR || join(homedir(), '.nvm');
|
|
43
|
+
const script = `#!/bin/bash
|
|
44
|
+
# pmpt-mcp wrapper — loads nvm then runs the real binary
|
|
45
|
+
# Auto-generated by pmpt mcp-setup. Re-run "pmpt mcp-setup" to update.
|
|
46
|
+
|
|
47
|
+
export NVM_DIR="${nvmDir}"
|
|
48
|
+
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
|
|
49
|
+
|
|
50
|
+
exec "${realBinaryPath}" "$@"
|
|
51
|
+
`;
|
|
52
|
+
writeFileSync(WRAPPER_PATH, script, { mode: 0o755 });
|
|
53
|
+
chmodSync(WRAPPER_PATH, 0o755);
|
|
54
|
+
return WRAPPER_PATH;
|
|
55
|
+
}
|
|
56
|
+
/** Check if wrapper exists and the target binary it points to is still valid */
|
|
57
|
+
export function getWrapperStatus() {
|
|
58
|
+
if (!existsSync(WRAPPER_PATH)) {
|
|
59
|
+
return { exists: false, targetValid: false, targetPath: null };
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
const content = readFileSync(WRAPPER_PATH, 'utf-8');
|
|
63
|
+
const match = content.match(/exec "(.+?)"/);
|
|
64
|
+
const targetPath = match?.[1] || null;
|
|
65
|
+
return {
|
|
66
|
+
exists: true,
|
|
67
|
+
targetValid: targetPath ? existsSync(targetPath) : false,
|
|
68
|
+
targetPath,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return { exists: true, targetValid: false, targetPath: null };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
27
75
|
function isCommandAvailable(cmd) {
|
|
28
76
|
try {
|
|
29
77
|
const which = process.platform === 'win32' ? 'where' : 'which';
|
|
@@ -55,14 +103,15 @@ function isJsonConfigured(configPath) {
|
|
|
55
103
|
}
|
|
56
104
|
}
|
|
57
105
|
function configureClaudeCode(mcpBinaryPath) {
|
|
58
|
-
// Remove existing entry first (ignore errors if not found)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
106
|
+
// Remove existing entry at any scope first (ignore errors if not found)
|
|
107
|
+
for (const scope of ['user', 'local', 'project']) {
|
|
108
|
+
try {
|
|
109
|
+
execSync(`claude mcp remove --scope ${scope} pmpt`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
110
|
+
}
|
|
111
|
+
catch { /* not configured at this scope */ }
|
|
64
112
|
}
|
|
65
|
-
|
|
113
|
+
// Register at user scope so it works across all projects
|
|
114
|
+
execSync(`claude mcp add --scope user --transport stdio pmpt -- "${mcpBinaryPath}"`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
66
115
|
}
|
|
67
116
|
function configureJsonFile(configPath, mcpBinaryPath) {
|
|
68
117
|
let config = {};
|
|
@@ -93,6 +142,21 @@ export async function cmdMcpSetup() {
|
|
|
93
142
|
let mcpPath = detectPmptMcpPath();
|
|
94
143
|
if (mcpPath) {
|
|
95
144
|
s.stop(`Found: ${mcpPath}`);
|
|
145
|
+
// If nvm environment, offer to create a stable wrapper
|
|
146
|
+
if (isNvmEnvironment()) {
|
|
147
|
+
p.log.warn('nvm detected — the binary path may break when you switch Node versions.');
|
|
148
|
+
const useWrapper = await p.confirm({
|
|
149
|
+
message: 'Create a stable wrapper at ~/.pmpt/bin/pmpt-mcp? (recommended for nvm users)',
|
|
150
|
+
initialValue: true,
|
|
151
|
+
});
|
|
152
|
+
if (!p.isCancel(useWrapper) && useWrapper) {
|
|
153
|
+
const wrapperPath = createWrapperScript(mcpPath);
|
|
154
|
+
p.log.success(`Wrapper created: ${wrapperPath}`);
|
|
155
|
+
p.log.info(`Points to: ${mcpPath}`);
|
|
156
|
+
p.log.info('After switching Node versions, run "pmpt mcp-setup" to update the wrapper.');
|
|
157
|
+
mcpPath = wrapperPath;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
96
160
|
}
|
|
97
161
|
else {
|
|
98
162
|
s.stop('Could not auto-detect pmpt-mcp path');
|
package/dist/commands/plan.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import * as p from '@clack/prompts';
|
|
2
2
|
import { resolve } from 'path';
|
|
3
|
-
import { existsSync, readFileSync } from 'fs';
|
|
3
|
+
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
4
4
|
import { isInitialized, loadConfig } from '../lib/config.js';
|
|
5
5
|
import { copyToClipboard } from '../lib/clipboard.js';
|
|
6
6
|
import { cmdWatch } from './watch.js';
|
|
7
|
-
import { PLAN_QUESTIONS, getPlanProgress, initPlanProgress, savePlanProgress, savePlanDocuments, } from '../lib/plan.js';
|
|
7
|
+
import { PLAN_QUESTIONS, getPlanProgress, initPlanProgress, savePlanProgress, savePlanDocuments, generateAnswersTemplate, } from '../lib/plan.js';
|
|
8
8
|
function loadAnswersFromFile(projectPath, inputPath) {
|
|
9
9
|
const filePath = resolve(projectPath, inputPath);
|
|
10
10
|
if (!existsSync(filePath)) {
|
|
@@ -27,6 +27,16 @@ function loadAnswersFromFile(projectPath, inputPath) {
|
|
|
27
27
|
}
|
|
28
28
|
export async function cmdPlan(path, options) {
|
|
29
29
|
const projectPath = path ? resolve(path) : process.cwd();
|
|
30
|
+
// Template generation (no init check needed)
|
|
31
|
+
if (options?.template) {
|
|
32
|
+
const outFile = resolve(projectPath, options.template);
|
|
33
|
+
const template = generateAnswersTemplate();
|
|
34
|
+
writeFileSync(outFile, JSON.stringify(template, null, 2), 'utf-8');
|
|
35
|
+
p.log.success(`Template created: ${outFile}`);
|
|
36
|
+
p.log.info('Fill in the fields and run:');
|
|
37
|
+
p.log.message(` pmpt plan --answers-file ${options.template}`);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
30
40
|
// Check initialization
|
|
31
41
|
if (!isInitialized(projectPath)) {
|
|
32
42
|
p.intro('pmpt plan');
|
package/dist/commands/remix.js
CHANGED
|
@@ -114,9 +114,13 @@ When you make progress, update \`.pmpt/docs/pmpt.md\`:
|
|
|
114
114
|
- When a feature is implemented (mark as done)
|
|
115
115
|
- When a development phase is completed
|
|
116
116
|
|
|
117
|
-
Keep the Snapshot Log up to date.
|
|
118
|
-
|
|
119
|
-
|
|
117
|
+
Keep the Snapshot Log up to date. **Always save proactively after milestones — do not wait for the user to ask.**
|
|
118
|
+
|
|
119
|
+
Try the pmpt MCP tool first:
|
|
120
|
+
- Claude Code: call \`mcp__pmpt__pmpt_save\` with a descriptive \`summary\`
|
|
121
|
+
- Other MCP clients: call \`pmpt_save\` with a descriptive \`summary\`
|
|
122
|
+
|
|
123
|
+
If no MCP tool is available, run \`pmpt save\` in terminal.
|
|
120
124
|
|
|
121
125
|
---
|
|
122
126
|
|
package/dist/index.js
CHANGED
|
@@ -45,6 +45,7 @@ import { cmdRecover } from './commands/recover.js';
|
|
|
45
45
|
import { cmdDiff } from './commands/diff.js';
|
|
46
46
|
import { cmdInternalSeed } from './commands/internal-seed.js';
|
|
47
47
|
import { cmdMcpSetup } from './commands/mcp-setup.js';
|
|
48
|
+
import { cmdDoctor } from './commands/doctor.js';
|
|
48
49
|
import { trackCommand } from './lib/api.js';
|
|
49
50
|
import { checkForUpdates } from './lib/update-check.js';
|
|
50
51
|
import { createRequire } from 'module';
|
|
@@ -85,6 +86,7 @@ Examples:
|
|
|
85
86
|
$ pmpt graduate Graduate a project (Hall of Fame)
|
|
86
87
|
$ pmpt recover Recover damaged pmpt.md via AI
|
|
87
88
|
$ pmpt mcp-setup Configure MCP for AI tools
|
|
89
|
+
$ pmpt doctor (doc) Diagnose MCP & environment issues
|
|
88
90
|
$ pmpt feedback (fb) Share ideas or report bugs
|
|
89
91
|
|
|
90
92
|
Workflow:
|
|
@@ -146,7 +148,11 @@ program
|
|
|
146
148
|
.description('Quick product planning with 5 questions — auto-generate AI prompt')
|
|
147
149
|
.option('--reset', 'Restart plan from scratch')
|
|
148
150
|
.option('--answers-file <file>', 'Load plan answers from JSON file (non-interactive)')
|
|
149
|
-
.
|
|
151
|
+
.option('--template [file]', 'Generate a fillable answers template (default: answers.json)')
|
|
152
|
+
.action((path, options) => cmdPlan(path, {
|
|
153
|
+
...options,
|
|
154
|
+
template: options.template === true ? 'answers.json' : options.template,
|
|
155
|
+
}));
|
|
150
156
|
program
|
|
151
157
|
.command('logout')
|
|
152
158
|
.description('Clear saved GitHub authentication')
|
|
@@ -214,6 +220,11 @@ program
|
|
|
214
220
|
.command('mcp-setup')
|
|
215
221
|
.description('Configure pmpt MCP server for AI tools (Claude Code, Cursor, etc.)')
|
|
216
222
|
.action(cmdMcpSetup);
|
|
223
|
+
program
|
|
224
|
+
.command('doctor')
|
|
225
|
+
.alias('doc')
|
|
226
|
+
.description('Diagnose MCP connection, PATH issues, and environment health')
|
|
227
|
+
.action(cmdDoctor);
|
|
217
228
|
program
|
|
218
229
|
.command('feedback')
|
|
219
230
|
.alias('fb')
|
package/dist/lib/config.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
2
|
-
import { join } from 'path';
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
import { execSync } from 'child_process';
|
|
3
4
|
const CONFIG_DIR = '.pmpt';
|
|
4
5
|
const CONFIG_FILE = 'config.json';
|
|
5
6
|
const DEFAULT_DOCS_DIR = 'docs';
|
|
@@ -61,6 +62,87 @@ export function saveConfig(projectPath, config) {
|
|
|
61
62
|
const configPath = join(getConfigDir(projectPath), CONFIG_FILE);
|
|
62
63
|
writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
63
64
|
}
|
|
65
|
+
const PMPT_CLAUDE_SECTION = `
|
|
66
|
+
<!-- pmpt -->
|
|
67
|
+
## pmpt MCP Tools
|
|
68
|
+
|
|
69
|
+
This project uses [pmpt](https://pmptwiki.com) to track development progress.
|
|
70
|
+
|
|
71
|
+
**If pmpt MCP is available in this session**, use these tools proactively — do not wait for the user to ask:
|
|
72
|
+
- \`mcp__pmpt__pmpt_save\` — save a snapshot after milestones (always include a \`summary\`)
|
|
73
|
+
- \`mcp__pmpt__pmpt_status\` — check project status
|
|
74
|
+
- \`mcp__pmpt__pmpt_read_context\` — read project context at session start
|
|
75
|
+
|
|
76
|
+
Save after: feature completion, architecture decisions, bug fixes, or any significant progress.
|
|
77
|
+
|
|
78
|
+
If pmpt MCP is not available, run \`pmpt save\` in terminal instead.
|
|
79
|
+
<!-- /pmpt -->
|
|
80
|
+
`;
|
|
81
|
+
/**
|
|
82
|
+
* Add pmpt MCP instructions to CLAUDE.md in the project root.
|
|
83
|
+
* Creates the file if it doesn't exist; appends the section if not already present.
|
|
84
|
+
*/
|
|
85
|
+
export function ensurePmptClaudeMd(projectPath) {
|
|
86
|
+
const claudeMdPath = join(projectPath, 'CLAUDE.md');
|
|
87
|
+
const marker = '<!-- pmpt -->';
|
|
88
|
+
if (existsSync(claudeMdPath)) {
|
|
89
|
+
const content = readFileSync(claudeMdPath, 'utf-8');
|
|
90
|
+
if (content.includes(marker))
|
|
91
|
+
return; // already has pmpt section
|
|
92
|
+
writeFileSync(claudeMdPath, content.trimEnd() + '\n' + PMPT_CLAUDE_SECTION, 'utf-8');
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
writeFileSync(claudeMdPath, '# Project Instructions\n' + PMPT_CLAUDE_SECTION, 'utf-8');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function detectPmptMcpPath() {
|
|
99
|
+
// Strategy 1: sibling to the current pmpt binary
|
|
100
|
+
try {
|
|
101
|
+
const pmptBin = process.argv[1];
|
|
102
|
+
const siblingPath = join(dirname(pmptBin), 'pmpt-mcp');
|
|
103
|
+
if (existsSync(siblingPath))
|
|
104
|
+
return siblingPath;
|
|
105
|
+
}
|
|
106
|
+
catch { /* skip */ }
|
|
107
|
+
// Strategy 2: which / where command
|
|
108
|
+
try {
|
|
109
|
+
const cmd = process.platform === 'win32' ? 'where pmpt-mcp' : 'which pmpt-mcp';
|
|
110
|
+
const result = execSync(cmd, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
111
|
+
const firstLine = result.split('\n')[0].trim();
|
|
112
|
+
if (firstLine && existsSync(firstLine))
|
|
113
|
+
return firstLine;
|
|
114
|
+
}
|
|
115
|
+
catch { /* not in PATH */ }
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Create or update .mcp.json in the project root to register the pmpt MCP server.
|
|
120
|
+
* Skips silently if pmpt-mcp binary cannot be found.
|
|
121
|
+
*/
|
|
122
|
+
export function ensureMcpJson(projectPath) {
|
|
123
|
+
const mcpJsonPath = join(projectPath, '.mcp.json');
|
|
124
|
+
const mcpPath = detectPmptMcpPath();
|
|
125
|
+
if (!mcpPath)
|
|
126
|
+
return; // can't detect binary — skip silently
|
|
127
|
+
let config = {};
|
|
128
|
+
if (existsSync(mcpJsonPath)) {
|
|
129
|
+
try {
|
|
130
|
+
config = JSON.parse(readFileSync(mcpJsonPath, 'utf-8'));
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
config = {};
|
|
134
|
+
}
|
|
135
|
+
// Already registered — don't overwrite
|
|
136
|
+
const servers = config.mcpServers;
|
|
137
|
+
if (servers?.pmpt)
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (!config.mcpServers || typeof config.mcpServers !== 'object') {
|
|
141
|
+
config.mcpServers = {};
|
|
142
|
+
}
|
|
143
|
+
config.mcpServers.pmpt = { command: mcpPath };
|
|
144
|
+
writeFileSync(mcpJsonPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
145
|
+
}
|
|
64
146
|
const PMPT_README = `# .pmpt — Your Project's Development Journal
|
|
65
147
|
|
|
66
148
|
This folder is managed by [pmpt](https://pmptwiki.com). It records your product development journey with AI.
|
package/dist/lib/plan.js
CHANGED
|
@@ -102,17 +102,19 @@ After significant milestones, save a snapshot using the method below.
|
|
|
102
102
|
|
|
103
103
|
### Saving Snapshots
|
|
104
104
|
|
|
105
|
-
**
|
|
106
|
-
Call the \`pmpt_save\` MCP tool directly with a descriptive summary. Do this proactively after milestones — do not wait for the user to ask.
|
|
105
|
+
**Always save proactively after milestones — do not wait for the user to ask.**
|
|
107
106
|
|
|
108
|
-
|
|
109
|
-
|
|
107
|
+
Try the pmpt MCP tool first:
|
|
108
|
+
- Claude Code: call \`mcp__pmpt__pmpt_save\` with a descriptive \`summary\`
|
|
109
|
+
- Other MCP clients: call \`pmpt_save\` with a descriptive \`summary\`
|
|
110
|
+
|
|
111
|
+
If no MCP tool is available, run \`pmpt save\` in the terminal.
|
|
110
112
|
|
|
111
113
|
### Per-Feature Checklist
|
|
112
114
|
After completing each feature above:
|
|
113
115
|
1. Mark the feature done in \`.pmpt/docs/pmpt.md\` (change \`- [ ]\` to \`- [x]\`)
|
|
114
116
|
2. Add a brief note to the Snapshot Log section
|
|
115
|
-
3.
|
|
117
|
+
3. Call \`mcp__pmpt__pmpt_save\` (or \`pmpt save\` in terminal) with a summary
|
|
116
118
|
|
|
117
119
|
### What to Record in pmpt.md
|
|
118
120
|
|
|
@@ -198,6 +200,19 @@ ${techSection}
|
|
|
198
200
|
*Generated by pmpt plan*
|
|
199
201
|
`;
|
|
200
202
|
}
|
|
203
|
+
export function generateAnswersTemplate() {
|
|
204
|
+
const questions = {};
|
|
205
|
+
const fields = {};
|
|
206
|
+
for (const q of PLAN_QUESTIONS) {
|
|
207
|
+
questions[q.key] = `[${PLAN_QUESTIONS.indexOf(q) + 1}/${PLAN_QUESTIONS.length}] ${q.question}`;
|
|
208
|
+
fields[q.key] = '';
|
|
209
|
+
}
|
|
210
|
+
return {
|
|
211
|
+
_help: 'Fill in the fields below, then run: pmpt plan --answers-file <this-file>',
|
|
212
|
+
_questions: questions,
|
|
213
|
+
...fields,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
201
216
|
export function getPlanProgress(projectPath) {
|
|
202
217
|
const planPath = join(getConfigDir(projectPath), PLAN_FILE);
|
|
203
218
|
if (!existsSync(planPath))
|