UNPKG

safevibe

Version:

Safevibe CLI - Simple personal secret vault for AI developers and amateur vibe coders

158 lines (152 loc) 5.54 kB
import { spawn } from "node:child_process"; import { join } from "node:path"; import { fileURLToPath } from "node:url"; import { dirname } from "node:path"; import chalk from "chalk"; import ora from "ora"; /** * MCP Server Management for CLI * Simplified without project management */ const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Path to the MCP server executable (bundled with CLI package) // When installed via npm, this will resolve to the correct location const MCP_SERVER_PATH = join(__dirname, "../mcp-server/index.js"); /** * Start the MCP server as a subprocess */ export async function startMcpServer() { const spinner = ora("Starting Safevibe MCP server...").start(); try { // Start the MCP server process const serverProcess = spawn("node", [MCP_SERVER_PATH], { stdio: ["pipe", "pipe", "pipe"], // Keep stdio pipes for communication env: { ...process.env, SAFEVIBE_BACKEND_URL: process.env.SAFEVIBE_BACKEND_URL || "https://safevibe-backend.vercel.app", }, }); let ready = false; let startupError = null; // Listen for server readiness serverProcess.stderr?.on("data", (data) => { const output = data.toString(); if (output.includes("Safevibe MCP Server started successfully")) { ready = true; spinner.succeed("MCP server started"); } else if (output.includes("Failed to start")) { startupError = output; } }); // Handle server errors serverProcess.on("error", (error) => { spinner.fail("Failed to start MCP server"); throw new Error(`MCP server error: ${error.message}`); }); serverProcess.on("exit", (code) => { if (code !== 0 && code !== null) { spinner.fail("MCP server exited unexpectedly"); console.error(chalk.red(`MCP server exited with code ${code}`)); } }); // Wait for server to be ready (with timeout) const timeout = 10000; // 10 seconds const startTime = Date.now(); while (!ready && !startupError && Date.now() - startTime < timeout) { await new Promise(resolve => setTimeout(resolve, 100)); } if (startupError) { spinner.fail("MCP server failed to start"); throw new Error(startupError); } if (!ready) { spinner.fail("MCP server startup timeout"); serverProcess.kill(); throw new Error("MCP server did not start within timeout period"); } // Return server control object return { process: serverProcess, ready: true, stop: async () => { return new Promise((resolve) => { if (serverProcess.killed || serverProcess.exitCode !== null) { resolve(); return; } serverProcess.once("exit", () => resolve()); serverProcess.kill("SIGTERM"); // Force kill after 5 seconds setTimeout(() => { if (!serverProcess.killed) { serverProcess.kill("SIGKILL"); } }, 5000); }); } }; } catch (error) { spinner.fail("Failed to start MCP server"); throw error; } } /** * Check if the MCP server executable exists */ export async function checkMcpServer() { try { const { access } = await import("node:fs/promises"); const { constants } = await import("node:fs"); await access(MCP_SERVER_PATH, constants.F_OK); return true; } catch { return false; } } /** * Get MCP server configuration for Cursor */ export function getMcpConfig() { return { mcpServers: { safevibe: { command: "node", args: [MCP_SERVER_PATH], env: { SAFEVIBE_BACKEND_URL: process.env.SAFEVIBE_BACKEND_URL || "https://safevibe-backend.vercel.app", }, }, }, }; } /** * Generate Cursor configuration instructions */ export function getCursorInstructions() { const config = getMcpConfig(); return ` ${chalk.cyan("🔗 Cursor Integration")} ${chalk.yellow("Add this to your Cursor settings.json:")} ${chalk.white(JSON.stringify(config, null, 2))} ${chalk.cyan("How to configure:")} ${chalk.white("1. Open Cursor")} ${chalk.white("2. Press")} ${chalk.yellow("Cmd+,")} ${chalk.white("(macOS) or")} ${chalk.yellow("Ctrl+,")} ${chalk.white("(Windows/Linux)")} ${chalk.white("3. Click")} ${chalk.yellow('"Open Settings (JSON)"')} ${chalk.white("4. Add the configuration above")} ${chalk.white("5. Restart Cursor")} ${chalk.cyan("Ready to use! Ask Cursor:")} ${chalk.gray('• "Store my OpenAI API key securely"')} ${chalk.gray('• "Get my database password"')} ${chalk.gray('• "List all my secrets"')} ${chalk.gray('• "Rotate my API key"')} ${chalk.cyan("Available MCP Tools:")} ${chalk.gray('• save_key - Store encrypted secrets')} ${chalk.gray('• get_key - Retrieve encrypted secrets')} ${chalk.gray('• list_keys - List all your secrets')} ${chalk.gray('• rotate_key - Rotate secret versions')} `; }