vaspera-pm 2.10.0 → 2.10.2

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/cli.js CHANGED
@@ -292,11 +292,13 @@ async function validateApiKey(apiKey) {
292
292
  }
293
293
  };
294
294
  }
295
+ const errorMessage = error2 instanceof Error ? error2.message : "Unknown error";
296
+ const isNetworkError = errorMessage.includes("ECONNREFUSED") || errorMessage.includes("ENOTFOUND") || errorMessage.includes("fetch failed") || errorMessage.includes("network");
295
297
  return {
296
298
  valid: false,
297
299
  error: {
298
- code: "VPM-INTERNAL-001",
299
- message: "Failed to validate API key. Please try again."
300
+ code: isNetworkError ? "VPM-NETWORK-001" : "VPM-INTERNAL-001",
301
+ message: isNetworkError ? "Unable to reach VasperaPM servers. Check your internet connection or try again later." : `API key validation failed: ${errorMessage}`
300
302
  }
301
303
  };
302
304
  }
@@ -31713,7 +31715,7 @@ var BANNER = `
31713
31715
  \u2551 \u2551
31714
31716
  \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D\x1B[0m
31715
31717
  `;
31716
- var VERSION7 = true ? "2.10.0" : "2.3.1";
31718
+ var VERSION7 = true ? "2.10.1" : "2.3.1";
31717
31719
  var HELP = `
31718
31720
  \x1B[1mUsage:\x1B[0m vaspera-pm <command> [path] [options]
31719
31721
 
@@ -31771,9 +31773,128 @@ var HELP = `
31771
31773
  function getClaudeConfigPath() {
31772
31774
  return join23(homedir2(), ".claude", "claude_desktop_config.json");
31773
31775
  }
31776
+ function getCursorConfigPath() {
31777
+ return join23(homedir2(), ".cursor", "mcp.json");
31778
+ }
31779
+ function getProjectMcpConfigPath() {
31780
+ return join23(process.cwd(), ".mcp.json");
31781
+ }
31774
31782
  function getVasperaConfigPath() {
31775
31783
  return join23(homedir2(), ".vasperapm", "config.json");
31776
31784
  }
31785
+ var VASPERA_PM_INSTRUCTIONS = `
31786
+ # VasperaPM Integration
31787
+
31788
+ This project uses VasperaPM for AI-powered product management.
31789
+
31790
+ ## Available MCP Tools
31791
+ When VasperaPM MCP server is connected, you have access to these tools:
31792
+ - \`synthesize_requirements\` - Extract requirements from documents
31793
+ - \`infer_prd_from_code\` - Reverse-engineer PRD from code
31794
+ - \`verify_docs_code\` - Detect documentation drift
31795
+ - \`generate_api_docs\` - Create OpenAPI specifications
31796
+ - \`generate_test_specs\` - Generate test plans
31797
+ - \`sync_to_tracker\` - Export to Jira/Linear/GitHub/ADO
31798
+ - \`explode_backlog\` - Break features into user stories
31799
+ - \`review_prd\` - Quality review PRDs
31800
+
31801
+ ## Usage Tips
31802
+ - Use \`verify_docs_code\` to check if documentation is in sync with code
31803
+ - Use \`generate_api_docs\` to auto-generate API documentation
31804
+ - Use \`sync_to_tracker\` to export requirements to your project tracker
31805
+ `;
31806
+ function updateProjectInstructions(projectId) {
31807
+ const cwd = process.cwd();
31808
+ const claudeMdPath = join23(cwd, "CLAUDE.md");
31809
+ const cursorRulesPath = join23(cwd, ".cursorrules");
31810
+ let targetFile;
31811
+ let existingContent = null;
31812
+ if (existsSync19(claudeMdPath)) {
31813
+ targetFile = claudeMdPath;
31814
+ existingContent = readFileSync15(claudeMdPath, "utf-8");
31815
+ } else if (existsSync19(cursorRulesPath)) {
31816
+ targetFile = cursorRulesPath;
31817
+ existingContent = readFileSync15(cursorRulesPath, "utf-8");
31818
+ } else {
31819
+ targetFile = claudeMdPath;
31820
+ }
31821
+ if (existingContent?.includes("# VasperaPM Integration")) {
31822
+ return { updated: false, file: targetFile, action: "Skipped" };
31823
+ }
31824
+ const projectSection = `
31825
+ ## Project ID
31826
+ \`${projectId}\`
31827
+ `;
31828
+ const newContent = existingContent ? existingContent.trimEnd() + "\n\n---\n" + VASPERA_PM_INSTRUCTIONS + projectSection : VASPERA_PM_INSTRUCTIONS.trim() + projectSection;
31829
+ writeFileSync6(targetFile, newContent);
31830
+ return {
31831
+ updated: true,
31832
+ file: targetFile.split("/").pop() || targetFile,
31833
+ action: existingContent ? "Updated" : "Created"
31834
+ };
31835
+ }
31836
+ function detectMcpConfigLocations() {
31837
+ const locations = [
31838
+ { path: getCursorConfigPath(), name: "Cursor (global)" },
31839
+ { path: getClaudeConfigPath(), name: "Claude Desktop (global)" },
31840
+ { path: getProjectMcpConfigPath(), name: "Project (.mcp.json)" }
31841
+ ];
31842
+ return locations.map((loc) => {
31843
+ const config = readJsonFile(loc.path);
31844
+ return {
31845
+ ...loc,
31846
+ exists: config !== null,
31847
+ hasVasperaPm: config?.mcpServers?.["vaspera-pm"] !== void 0
31848
+ };
31849
+ });
31850
+ }
31851
+ function mergeMcpConfig(configPath, vasperaApiKey, anthropicApiKey) {
31852
+ try {
31853
+ let config = readJsonFile(configPath);
31854
+ const existed = config !== null;
31855
+ if (!config) {
31856
+ config = { mcpServers: {} };
31857
+ }
31858
+ if (!config.mcpServers) {
31859
+ config.mcpServers = {};
31860
+ }
31861
+ const existingVpm = config.mcpServers["vaspera-pm"];
31862
+ if (existingVpm?.env?.VASPERA_API_KEY === vasperaApiKey) {
31863
+ return {
31864
+ success: true,
31865
+ path: configPath,
31866
+ action: "unchanged",
31867
+ message: "vaspera-pm already configured with same API key"
31868
+ };
31869
+ }
31870
+ const mcpEnv = {};
31871
+ if (vasperaApiKey && vasperaApiKey.startsWith("vpm_")) {
31872
+ mcpEnv.VASPERA_API_KEY = vasperaApiKey;
31873
+ }
31874
+ if (anthropicApiKey) {
31875
+ mcpEnv.ANTHROPIC_API_KEY = anthropicApiKey;
31876
+ }
31877
+ config.mcpServers["vaspera-pm"] = {
31878
+ command: "npx",
31879
+ args: ["-y", "vaspera-pm@latest", "serve"],
31880
+ env: mcpEnv
31881
+ };
31882
+ writeJsonFile(configPath, config);
31883
+ return {
31884
+ success: true,
31885
+ path: configPath,
31886
+ action: existed ? "updated" : "created",
31887
+ message: existed ? `Added vaspera-pm to existing config (${Object.keys(config.mcpServers).length} servers total)` : "Created new config with vaspera-pm"
31888
+ };
31889
+ } catch (err) {
31890
+ return {
31891
+ success: false,
31892
+ path: configPath,
31893
+ action: "error",
31894
+ message: err instanceof Error ? err.message : "Unknown error"
31895
+ };
31896
+ }
31897
+ }
31777
31898
  function readJsonFile(path7) {
31778
31899
  try {
31779
31900
  if (!existsSync19(path7)) return null;
@@ -31816,23 +31937,20 @@ function warn(message) {
31816
31937
  async function install() {
31817
31938
  console.log(BANNER);
31818
31939
  console.log("\x1B[1mInstalling VasperaPM for Claude Code...\x1B[0m\n");
31819
- const configPath = getClaudeConfigPath();
31820
- let config = readJsonFile(configPath);
31821
- if (!config) {
31822
- config = { mcpServers: {} };
31823
- info("Creating Claude Code configuration...");
31824
- }
31825
- if (!config.mcpServers) {
31826
- config.mcpServers = {};
31827
- }
31828
- if (config.mcpServers["vaspera-pm"]) {
31829
- warn("VasperaPM is already installed in Claude Code.");
31830
- const answer = await prompt("\nDo you want to reinstall? (y/N): ");
31831
- if (answer.toLowerCase() !== "y") {
31832
- info("Installation cancelled.");
31833
- return;
31940
+ console.log("\x1B[1m1. Detecting MCP configurations...\x1B[0m\n");
31941
+ const configLocations = detectMcpConfigLocations();
31942
+ for (const loc of configLocations) {
31943
+ if (loc.exists) {
31944
+ if (loc.hasVasperaPm) {
31945
+ info(`${loc.name}: Found (vaspera-pm already configured)`);
31946
+ } else {
31947
+ success(`${loc.name}: Found (will add vaspera-pm)`);
31948
+ }
31949
+ } else {
31950
+ console.log(` \x1B[90m${loc.name}: Not found\x1B[0m`);
31834
31951
  }
31835
31952
  }
31953
+ console.log("\n\x1B[1m2. Checking API keys...\x1B[0m\n");
31836
31954
  const vasperaConfig = readJsonFile(getVasperaConfigPath());
31837
31955
  let vasperaApiKey = vasperaConfig?.apiKey || process.env.VASPERA_API_KEY || "";
31838
31956
  let anthropicApiKey = process.env.ANTHROPIC_API_KEY || "";
@@ -31844,7 +31962,7 @@ async function install() {
31844
31962
  info("Using local mode with your Anthropic API key");
31845
31963
  vasperaApiKey = "";
31846
31964
  } else {
31847
- console.log("\n\x1B[1mAPI Key Setup\x1B[0m");
31965
+ console.log("\x1B[1mAPI Key Setup\x1B[0m");
31848
31966
  console.log("\nVasperaPM needs an API key to work. Choose one:\n");
31849
31967
  console.log(" \x1B[32mOption 1:\x1B[0m Connect to VasperaPM (recommended)");
31850
31968
  console.log(" Run: \x1B[36mvasperapm connect\x1B[0m");
@@ -31866,19 +31984,43 @@ async function install() {
31866
31984
  return;
31867
31985
  }
31868
31986
  }
31869
- const mcpEnv = {};
31870
- if (vasperaApiKey && vasperaApiKey.startsWith("vpm_live_")) {
31871
- mcpEnv.VASPERA_API_KEY = vasperaApiKey;
31987
+ console.log("\n\x1B[1m3. Select configurations to update\x1B[0m\n");
31988
+ const existingConfigs = configLocations.filter((c) => c.exists || c.name.includes("Project"));
31989
+ const configurableConfigs = existingConfigs.length > 0 ? existingConfigs : configLocations.slice(0, 2);
31990
+ console.log("Which MCP configurations should be updated?\n");
31991
+ for (let i = 0; i < configurableConfigs.length; i++) {
31992
+ const loc = configurableConfigs[i];
31993
+ const status2 = loc.hasVasperaPm ? " (already has vaspera-pm)" : "";
31994
+ console.log(` [${i + 1}] ${loc.name}${status2}`);
31872
31995
  }
31873
- if (anthropicApiKey) {
31874
- mcpEnv.ANTHROPIC_API_KEY = anthropicApiKey;
31996
+ console.log(` [a] All of the above`);
31997
+ console.log(` [n] None (just show instructions)
31998
+ `);
31999
+ const configChoice = await prompt("Select option(s) [1,2,a,n]: ");
32000
+ let selectedConfigs = [];
32001
+ if (configChoice.toLowerCase() === "a") {
32002
+ selectedConfigs = configurableConfigs;
32003
+ } else if (configChoice.toLowerCase() === "n") {
32004
+ selectedConfigs = [];
32005
+ } else {
32006
+ const indices = configChoice.split(",").map((s) => parseInt(s.trim(), 10) - 1);
32007
+ selectedConfigs = indices.filter((i) => i >= 0 && i < configurableConfigs.length).map((i) => configurableConfigs[i]);
32008
+ }
32009
+ if (selectedConfigs.length > 0) {
32010
+ console.log("\n\x1B[1m4. Applying configurations...\x1B[0m\n");
32011
+ for (const loc of selectedConfigs) {
32012
+ const result = mergeMcpConfig(loc.path, vasperaApiKey, anthropicApiKey);
32013
+ if (result.success) {
32014
+ if (result.action === "unchanged") {
32015
+ info(`${loc.name}: ${result.message}`);
32016
+ } else {
32017
+ success(`${loc.name}: ${result.message}`);
32018
+ }
32019
+ } else {
32020
+ error(`${loc.name}: ${result.message}`);
32021
+ }
32022
+ }
31875
32023
  }
31876
- config.mcpServers["vaspera-pm"] = {
31877
- command: "vasperapm",
31878
- args: ["serve"],
31879
- env: mcpEnv
31880
- };
31881
- writeJsonFile(configPath, config);
31882
32024
  if (vasperaApiKey && vasperaApiKey.startsWith("vpm_live_")) {
31883
32025
  const vasperaDir = join23(homedir2(), ".vasperapm");
31884
32026
  if (!existsSync19(vasperaDir)) {
@@ -31887,11 +32029,27 @@ async function install() {
31887
32029
  writeJsonFile(getVasperaConfigPath(), { apiKey: vasperaApiKey });
31888
32030
  }
31889
32031
  console.log("\n");
31890
- success("VasperaPM installed successfully!");
32032
+ success("VasperaPM installation complete!");
31891
32033
  console.log("\n\x1B[1mNext steps:\x1B[0m");
31892
- console.log(" 1. Restart Claude Code (or VSCode)");
31893
- console.log(" 2. Look for VasperaPM tools in the MCP panel");
31894
- console.log(' 3. Try: "Generate a PRD for a todo app"\n');
32034
+ if (selectedConfigs.length > 0) {
32035
+ console.log(" 1. Restart your IDE (Cursor/VSCode/Claude Desktop)");
32036
+ console.log(" 2. Look for VasperaPM tools in the MCP panel");
32037
+ console.log(' 3. Try: "Generate a PRD for a todo app"\n');
32038
+ } else {
32039
+ console.log(" To manually configure MCP, add this to your mcp.json:\n");
32040
+ console.log(' \x1B[36m"vaspera-pm": {\x1B[0m');
32041
+ console.log(' \x1B[36m "command": "npx",\x1B[0m');
32042
+ console.log(' \x1B[36m "args": ["-y", "vaspera-pm@latest", "serve"],\x1B[0m');
32043
+ console.log(' \x1B[36m "env": {\x1B[0m');
32044
+ if (vasperaApiKey) {
32045
+ console.log(` \x1B[36m "VASPERA_API_KEY": "${vasperaApiKey}"\x1B[0m`);
32046
+ }
32047
+ if (anthropicApiKey) {
32048
+ console.log(` \x1B[36m "ANTHROPIC_API_KEY": "${anthropicApiKey.substring(0, 10)}..."\x1B[0m`);
32049
+ }
32050
+ console.log(" \x1B[36m }\x1B[0m");
32051
+ console.log(" \x1B[36m}\x1B[0m\n");
32052
+ }
31895
32053
  console.log("\x1B[1mAvailable Tools (30 total):\x1B[0m");
31896
32054
  console.log(" \x1B[33m\u2022\x1B[0m synthesize_requirements - Extract requirements from docs");
31897
32055
  console.log(" \x1B[33m\u2022\x1B[0m infer_prd_from_code - Reverse-engineer PRD from code");
@@ -32100,6 +32258,10 @@ async function connect() {
32100
32258
  success("Added .vaspera/ to .gitignore");
32101
32259
  }
32102
32260
  }
32261
+ const claudeMdResult = updateProjectInstructions(projectId);
32262
+ if (claudeMdResult.updated) {
32263
+ success(`${claudeMdResult.action} ${claudeMdResult.file} with VasperaPM context`);
32264
+ }
32103
32265
  console.log("\n");
32104
32266
  success("Project connected successfully!");
32105
32267
  console.log("\n\x1B[1mConfiguration saved to:\x1B[0m .vaspera/config.json");
@@ -32207,13 +32369,25 @@ function parseOptions(args) {
32207
32369
  }
32208
32370
  return { path: path7, output, ci, verbose, agents, debate, format, files, noCache };
32209
32371
  }
32372
+ function hasAIKey() {
32373
+ const vasperaKey = process.env.VASPERA_API_KEY;
32374
+ const anthropicKey = process.env.ANTHROPIC_API_KEY;
32375
+ return !!(vasperaKey && vasperaKey.startsWith("vpm_")) || !!anthropicKey;
32376
+ }
32377
+ function showAIKeyError() {
32378
+ error("API key required for AI-powered analysis");
32379
+ console.log("\nChoose one of these options:\n");
32380
+ console.log(" \x1B[32mOption 1:\x1B[0m Use your VasperaPM account (recommended)");
32381
+ console.log(" Run: \x1B[36mvasperapm connect\x1B[0m");
32382
+ console.log(" Then retry your command.\n");
32383
+ console.log(" \x1B[32mOption 2:\x1B[0m Use your own Anthropic API key");
32384
+ console.log(" \x1B[36mexport ANTHROPIC_API_KEY=sk-ant-xxx\x1B[0m");
32385
+ console.log(" Get one at: https://console.anthropic.com\n");
32386
+ }
32210
32387
  async function runAnalyze(args) {
32211
32388
  const options = parseOptions(args);
32212
- if (!process.env.ANTHROPIC_API_KEY) {
32213
- error("ANTHROPIC_API_KEY environment variable is required");
32214
- console.log("\nSet your API key:");
32215
- console.log(" export ANTHROPIC_API_KEY=sk-ant-xxx");
32216
- console.log("\nOr get one at: https://console.anthropic.com\n");
32389
+ if (!hasAIKey()) {
32390
+ showAIKeyError();
32217
32391
  process.exit(1);
32218
32392
  }
32219
32393
  if (options.agents) {
@@ -32497,8 +32671,8 @@ async function runVerify(args) {
32497
32671
  fixPreference = args[++i];
32498
32672
  }
32499
32673
  }
32500
- if (!process.env.ANTHROPIC_API_KEY) {
32501
- error("ANTHROPIC_API_KEY environment variable is required");
32674
+ if (!hasAIKey()) {
32675
+ showAIKeyError();
32502
32676
  process.exit(1);
32503
32677
  }
32504
32678
  if (options.format === "json" && suggestFixes) {
@@ -32610,8 +32784,8 @@ function formatFixSuggestionsForJSON(suggestions) {
32610
32784
  }
32611
32785
  async function runFix(args) {
32612
32786
  const options = parseOptions(args);
32613
- if (!process.env.ANTHROPIC_API_KEY) {
32614
- error("ANTHROPIC_API_KEY environment variable is required");
32787
+ if (!hasAIKey()) {
32788
+ showAIKeyError();
32615
32789
  process.exit(1);
32616
32790
  }
32617
32791
  let interactive = false;
@@ -32926,20 +33100,20 @@ async function runInit() {
32926
33100
  };
32927
33101
  writeJsonFile(configPath, config);
32928
33102
  const mcpConfigPath = join23(process.cwd(), ".mcp.json");
32929
- if (!existsSync19(mcpConfigPath)) {
32930
- const mcpConfig = {
32931
- mcpServers: {
32932
- "vaspera-pm": {
32933
- command: "npx",
32934
- args: ["vaspera-pm@latest", "serve"],
32935
- env: {
32936
- ANTHROPIC_API_KEY: "${ANTHROPIC_API_KEY}"
32937
- }
32938
- }
32939
- }
32940
- };
32941
- writeJsonFile(mcpConfigPath, mcpConfig);
32942
- success("Created .mcp.json for Claude Code integration");
33103
+ const mcpResult = mergeMcpConfig(
33104
+ mcpConfigPath,
33105
+ "",
33106
+ // No VasperaPM API key in init (uses ANTHROPIC_API_KEY)
33107
+ anthropicKey || "${ANTHROPIC_API_KEY}"
33108
+ );
33109
+ if (mcpResult.success) {
33110
+ if (mcpResult.action === "created") {
33111
+ success("Created .mcp.json for Claude Code integration");
33112
+ } else if (mcpResult.action === "updated") {
33113
+ success("Added vaspera-pm to existing .mcp.json (preserved other servers)");
33114
+ } else {
33115
+ info(".mcp.json already has vaspera-pm configured");
33116
+ }
32943
33117
  }
32944
33118
  console.log("\n");
32945
33119
  success("VasperaPM initialized successfully!");