UNPKG

@insforge/install

Version:

CLI tool for installing Insforge MCP servers across different AI clients

318 lines (291 loc) 12.8 kB
#!/usr/bin/env node import { clientNames, logger, readConfig, writeConfig } from "./utils.js"; import { execSync } from 'child_process'; import os from 'os'; import path from 'path'; import yargs from "yargs"; import { hideBin } from "yargs/helpers"; import pc from "picocolors"; var { green, red, yellow, cyan } = pc; // Section header helper const LINE = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'; function printHeader(text) { console.log(); console.log(LINE); console.log(` ${text}`); console.log(LINE); console.log(); } // ASCII art logo for InsForge const INSFORGE_LOGO = ` ██╗███╗ ██╗███████╗███████╗ ██████╗ ██████╗ ██████╗ ███████╗ ██║████╗ ██║██╔════╝██╔════╝██╔═══██╗██╔══██╗██╔════╝ ██╔════╝ ██║██╔██╗ ██║███████╗█████╗ ██║ ██║██████╔╝██║ ███╗█████╗ ██║██║╚██╗██║╚════██║██╔══╝ ██║ ██║██╔══██╗██║ ██║██╔══╝ ██║██║ ╚████║███████║██║ ╚██████╔╝██║ ██║╚██████╔╝███████╗ ╚═╝╚═╝ ╚═══╝╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝ `; function printPostInstallMessage() { console.log(INSFORGE_LOGO); console.log(green('✓ InsForge MCP is now configured!')); console.log(); console.log('Next steps:'); console.log(' 1. Restart your coding agent to load InsForge'); console.log(' 2. Try these commands in your agent:'); console.log(); console.log(` ${yellow('"Create a posts table with title, content, and author"')}`); console.log(' (Sets up your database schema)'); console.log(); console.log(` ${yellow('"Add image upload for user profiles"')}`); console.log(' (Creates storage bucket and handles file uploads)'); console.log(); console.log('Learn more:'); console.log(' 📚 Documentation: https://docs.insforge.dev/introduction'); console.log(' 💬 Discord: https://discord.com/invite/MPxwj5xVvW'); console.log(' ⭐ GitHub: https://github.com/insforge/insforge'); console.log(); } function builder(yargs2) { return yargs2.option("client", { type: "string", description: "Client to use for installation", demandOption: true, choices: clientNames }).option("env", { type: "string", description: "Environment variables as key=value pairs (can be used multiple times). API_KEY is required.", array: true }).option("dev", { type: "boolean", description: "Install dev version (@insforge/mcp@dev) instead of latest", default: false }); } async function handler(argv) { if (!argv.client || !clientNames.includes(argv.client)) { logger.error(`Invalid client: ${argv.client}. Available clients: ${clientNames.join(", ")}`); return; } const envVars = {}; if (argv.env && argv.env.length > 0) { for (const envVar of argv.env) { const [key, ...valueParts] = envVar.split("="); if (key && valueParts.length > 0) { envVars[key] = valueParts.join("="); } else { logger.warn(`Invalid environment variable format: ${envVar}. Expected KEY=VALUE format.`); } } } if (!envVars.API_KEY) { logger.error("API_KEY environment variable is required. Use --env API_KEY=your_key"); return; } // Add default base URL if not provided if (!envVars.API_BASE_URL) { envVars.API_BASE_URL = "http://localhost:7130"; } const name = "insforge"; const mcpVersion = argv.dev ? "@insforge/mcp@dev" : "@insforge/mcp@latest"; try { printHeader('InsForge MCP Installer'); logger.info(`Setting up MCP for ${cyan(argv.client)}...`); printHeader(`Configuring ${argv.client}`); const config = readConfig(argv.client); // Handle different config structures for different clients if (argv.client === "claude-code") { // Claude Code uses mcpServers at root level if (!config.mcpServers) config.mcpServers = {}; const isWindows = process.platform === 'win32'; config.mcpServers[name] = isWindows ? { command: "cmd", args: ["/c", "npx", "-y", mcpVersion], env: { API_KEY: envVars.API_KEY, API_BASE_URL: envVars.API_BASE_URL } } : { command: "npx", args: ["-y", mcpVersion], env: { API_KEY: envVars.API_KEY, API_BASE_URL: envVars.API_BASE_URL } }; writeConfig(config, argv.client); // Create or update .claude/settings.local.json with enableAllProjectMcpServers const fs = await import('fs'); const claudeDir = path.join(process.cwd(), '.claude'); const settingsPath = path.join(claudeDir, 'settings.local.json'); if (!fs.existsSync(claudeDir)) { fs.mkdirSync(claudeDir, { recursive: true }); } let settings = {}; if (fs.existsSync(settingsPath)) { try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); } catch (error) { logger.warn(`Could not parse existing settings.local.json: ${error.message}`); } } // Add "insforge" to enabledMcpjsonServers if not already present if (!settings.enabledMcpjsonServers) { settings.enabledMcpjsonServers = []; } if (!settings.enabledMcpjsonServers.includes("insforge")) { settings.enabledMcpjsonServers.push("insforge"); } // Remove "insforge" from disabledMcpjsonServers if present if (settings.disabledMcpjsonServers && Array.isArray(settings.disabledMcpjsonServers)) { settings.disabledMcpjsonServers = settings.disabledMcpjsonServers.filter( (server) => server !== "insforge" ); } fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2)); logger.info("Added \"insforge\" to enabledMcpjsonServers in .claude/settings.local.json"); } else if (argv.client === "codex") { // Use Codex CLI for Codex const homeDir = os.homedir(); const isWindows = process.platform === 'win32'; const codexPath = isWindows ? path.join(homeDir, 'AppData', 'Roaming', 'npm', 'codex.cmd') : 'codex'; const envArgs = Object.entries(envVars) .filter(([key]) => key !== 'CLIENT_NAME') .map(([key, value]) => `--env ${key}=${value}`) .join(' '); try { // First try to remove existing insforge MCP if it exists try { const removeCmd = isWindows ? `"${codexPath}" mcp remove ${name}` : `codex mcp remove ${name}`; execSync(removeCmd, { stdio: 'pipe', shell: true }); logger.info("Removed existing insforge MCP installation."); } catch (removeError) { // It's okay if remove fails - the MCP might not exist logger.info("No existing insforge MCP found"); } // Now add the MCP server using codex CLI with --env flags const command = isWindows ? `"${codexPath}" mcp add ${name} ${envArgs} -- npx -y ${mcpVersion}` : `codex mcp add ${name} ${envArgs} -- npx -y ${mcpVersion}`; logger.info(`Adding insforge MCP server (${mcpVersion})...`); execSync(command, { stdio: 'inherit', shell: true }); } catch (error) { throw new Error(`Failed to add MCP server via Codex CLI: ${error.message}`); } } else if (argv.client === "cursor") { // Cursor uses mcpServers at root level if (!config.mcpServers) config.mcpServers = {}; config.mcpServers[name] = { command: "npx", args: ["-y", mcpVersion], env: { API_KEY: envVars.API_KEY, API_BASE_URL: envVars.API_BASE_URL } }; writeConfig(config, argv.client); } else if (argv.client === "windsurf") { if (!config.mcpServers) config.mcpServers = {}; config.mcpServers[name] = { command: "npx", args: ["-y", mcpVersion], env: { API_KEY: envVars.API_KEY, API_BASE_URL: envVars.API_BASE_URL } }; writeConfig(config, argv.client); } else if (argv.client === "cline" || argv.client === "roocode" || argv.client === "trae" || argv.client === "qoder") { if (!config.mcpServers) config.mcpServers = {}; config.mcpServers[name] = { command: "npx", args: ["-y", mcpVersion], env: { API_KEY: envVars.API_KEY, API_BASE_URL: envVars.API_BASE_URL } }; writeConfig(config, argv.client); } else if (argv.client === "copilot") { // Copilot uses "servers" instead of "mcpServers" const fs = await import('fs'); const vscodeDir = path.join(process.cwd(), '.vscode'); const mcpConfigPath = path.join(vscodeDir, 'mcp.json'); if (!fs.existsSync(vscodeDir)) { fs.mkdirSync(vscodeDir, { recursive: true }); } let copilotConfig = { servers: {} }; if (fs.existsSync(mcpConfigPath)) { try { copilotConfig = JSON.parse(fs.readFileSync(mcpConfigPath, 'utf8')); if (!copilotConfig.servers) copilotConfig.servers = {}; } catch (error) { logger.warn(`Could not parse existing mcp.json: ${error.message}`); copilotConfig = { servers: {} }; } } copilotConfig.servers[name] = { command: "npx", args: ["-y", mcpVersion], env: { API_KEY: envVars.API_KEY, API_BASE_URL: envVars.API_BASE_URL } }; fs.writeFileSync(mcpConfigPath, JSON.stringify(copilotConfig, null, 2)); logger.info(`Configured Copilot MCP at: ${mcpConfigPath}`); } // Fetch instructions documentation and save to appropriate files let instructionsContent = null; try { const fetch = (await import('node-fetch')).default; const fs = await import('fs'); const apiBaseUrl = envVars.API_BASE_URL || "http://localhost:7130"; const response = await fetch(`${apiBaseUrl}/api/docs/instructions`); if (response.ok) { const result = await response.json(); if (result && result.content) { instructionsContent = result.content; } } } catch (fetchError) { logger.warn(`Could not download instructions: ${fetchError.message}`); } // Save instructions to agent.md for all clients if (instructionsContent) { const fs = await import('fs'); const frontmatter = `--- description: Instructions building apps with MCP globs: * alwaysApply: true --- `; const contentWithFrontmatter = frontmatter + instructionsContent; const agentsMdPath = path.join(process.cwd(), 'AGENTS.md'); fs.writeFileSync(agentsMdPath, contentWithFrontmatter, 'utf-8'); logger.info(`Saved instructions to: ${agentsMdPath}`); } printHeader('Setup Complete!'); printPostInstallMessage(); } catch (e) { logger.error(red(e.message)); } } var parser = yargs(hideBin(process.argv)).scriptName("@insforge/install").command("install", "Install Insforge MCP server", builder, handler).help().alias("h", "help").version().alias("v", "version"); if (!process.argv.slice(2).length || process.argv[2].startsWith("--")) { parser.parse(["install", ...process.argv.slice(2)]); } else { parser.parse(); }