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.
@@ -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
- `After significant milestones, save a snapshot:`,
165
- `- **If pmpt MCP tool is available** (Claude Code, Cursor with MCP): call \`pmpt_save\` directly with a descriptive summary. Do this proactively — do not wait for the user to ask.`,
166
- `- **Otherwise**: run \`pmpt save\` in terminal.`,
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
+ }
@@ -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.confirm({
242
- message: 'Start planning? (Generate AI prompt with 5 quick questions)',
243
- initialValue: true,
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 (!p.isCancel(startPlan) && startPlan) {
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
- function detectPmptMcpPath() {
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
- try {
60
- execSync('claude mcp remove pmpt', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
61
- }
62
- catch {
63
- // not configured yet that's fine
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
- execSync(`claude mcp add --transport stdio pmpt -- "${mcpBinaryPath}"`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
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');
@@ -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');
@@ -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. After milestones, save a snapshot:
118
- - **If pmpt MCP tool is available** (Claude Code, Cursor with MCP): call \`pmpt_save\` directly with a descriptive summary. Do this proactively — do not wait for the user to ask.
119
- - **Otherwise**: run \`pmpt save\` in terminal.
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
- .action(cmdPlan);
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')
@@ -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
- **If the pmpt MCP tool is available** (Claude Code, Cursor with MCP configured):
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
- **Otherwise:**
109
- Run \`pmpt save\` in the terminal.
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. Save a snapshot (MCP tool or \`pmpt save\`)
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))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmpt-cli",
3
- "version": "1.14.18",
3
+ "version": "1.15.0",
4
4
  "description": "Record and share your AI-driven product development journey",
5
5
  "type": "module",
6
6
  "bin": {