pmpt-cli 1.14.21 → 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/doctor.js +220 -0
- package/dist/commands/init.js +39 -9
- package/dist/commands/mcp-setup.js +73 -9
- package/dist/commands/plan.js +12 -2
- package/dist/index.js +12 -1
- package/dist/lib/plan.js +13 -0
- package/package.json +1 -1
|
@@ -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
|
@@ -5,7 +5,7 @@ import { initializeProject, isInitialized, getDocsDir, ensurePmptClaudeMd, ensur
|
|
|
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');
|
|
@@ -155,6 +155,7 @@ export async function cmdInit(path, options) {
|
|
|
155
155
|
options: [
|
|
156
156
|
{ value: 'auto', label: 'Auto-generate plan', hint: 'Recommended — instant AI prompt from project analysis' },
|
|
157
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' },
|
|
158
159
|
{ value: 'skip', label: 'Skip for now' },
|
|
159
160
|
],
|
|
160
161
|
});
|
|
@@ -162,6 +163,17 @@ export async function cmdInit(path, options) {
|
|
|
162
163
|
p.cancel('Cancelled');
|
|
163
164
|
process.exit(0);
|
|
164
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
|
+
}
|
|
165
177
|
if (scanChoice === 'auto') {
|
|
166
178
|
// Ask for project description
|
|
167
179
|
const defaultDesc = scanResult.readmeDescription
|
|
@@ -236,23 +248,41 @@ export async function cmdInit(path, options) {
|
|
|
236
248
|
}
|
|
237
249
|
else {
|
|
238
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');
|
|
239
254
|
p.outro('Ready! Run `pmpt plan` when you want to start.');
|
|
240
255
|
}
|
|
241
256
|
}
|
|
242
257
|
else {
|
|
243
258
|
// New/empty project — original flow
|
|
244
|
-
const startPlan = await p.
|
|
245
|
-
message: 'Start planning?
|
|
246
|
-
|
|
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
|
+
],
|
|
247
266
|
});
|
|
248
|
-
if (
|
|
249
|
-
p.log.message('');
|
|
250
|
-
await cmdPlan(projectPath);
|
|
251
|
-
}
|
|
252
|
-
else {
|
|
267
|
+
if (p.isCancel(startPlan) || startPlan === 'no') {
|
|
253
268
|
ensureMinimalDocs(projectPath);
|
|
254
269
|
p.outro('Ready! Run `pmpt plan` when you want to start.');
|
|
255
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
|
+
}
|
|
256
286
|
}
|
|
257
287
|
}
|
|
258
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/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/plan.js
CHANGED
|
@@ -200,6 +200,19 @@ ${techSection}
|
|
|
200
200
|
*Generated by pmpt plan*
|
|
201
201
|
`;
|
|
202
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
|
+
}
|
|
203
216
|
export function getPlanProgress(projectPath) {
|
|
204
217
|
const planPath = join(getConfigDir(projectPath), PLAN_FILE);
|
|
205
218
|
if (!existsSync(planPath))
|