safevibe
Version:
Safevibe CLI - Simple personal secret vault for AI developers and amateur vibe coders
158 lines (152 loc) • 5.54 kB
JavaScript
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')}
`;
}