@sigyl-dev/cli
Version:
Official Sigyl CLI for installing and managing MCP packages. Zero-config installation for public packages, secure API-based authentication.
278 lines (258 loc) • 11 kB
text/typescript
import 'dotenv/config';
import { Command } from "commander"
import chalk from "chalk"
import { scanAndGenerate } from "./commands/scan"
import { initTemplate } from "./commands/init"
import { dev } from "./commands/dev"
import { createInstallCommand } from "./commands/install"
import { createRunCommand } from "./commands/run"
import inspectCommand from "./commands/inspect"
import { integrateWithExpress } from "./commands/integrate"
import { configCommand } from "./commands/config"
import { existsSync, rmSync } from "node:fs"
import { join } from "node:path"
import inquirer from "inquirer"
import { runWizard } from "./wizard"
// Read version from package.json
const packageJson = require('../package.json');
const program = new Command()
program
.name("sigyl/cli")
.description("Sigyl/CLI: Add Model Context Protocol (MCP) endpoints to your Express/Node.js applications. Zero-config AI tool integration for REST APIs.")
.version(packageJson.version)
// ============================================================================
// CONFIG Command - Configure CLI settings
// ============================================================================
program.addCommand(configCommand)
// ============================================================================
// INTEGRATE Command - Recommended developer flow
// ============================================================================
program
.command("integrate")
.description("Integrate MCP endpoints into your existing Express app (recommended)")
.option("--out <directory>", "Output directory for integration code", ".sigyl-mcp")
.option("--endpoint <path>", "MCP endpoint path", "/mcp")
.option("--auto-add", "(Coming soon) Automatically add integration to your app")
.option("-l, --language <language>", "Server language (typescript|javascript)", "typescript")
.argument("[directory]", "Directory containing Express app", ".")
.action(async (directory: string, options: any) => {
try {
console.log(chalk.blue("🔗 Sigyl MCP Integration"))
console.log(chalk.gray("Integrating MCP endpoints into your Express app...\n"))
await integrateWithExpress({
directory,
outDir: options.out,
serverLanguage: options.language,
autoAdd: options.autoAdd,
endpoint: options.endpoint
})
} catch (error) {
console.error(chalk.red("❌ Integration failed:"), error)
process.exit(1)
}
})
// ============================================================================
// SCAN Command - Legacy/advanced
// ============================================================================
program
.command("scan")
.description("Scan Express application and generate standalone MCP server (legacy)")
.argument("[directory]", "Directory to scan (default: current directory)", ".")
.option("-o, --out <directory>", "Output directory for generated server", ".mcp-generated")
.option("-p, --port <port>", "Port where your Express app runs", "3000")
.option("-l, --language <language>", "Server language (typescript|javascript)", "typescript")
.option("--framework <framework>", "Web framework (express)", "express")
.action(async (directory: string, options: any) => {
try {
console.log(chalk.blue("🔍 Sigyl MCP Server Generator"))
console.log(chalk.gray("Scanning Express application and generating MCP server...\n"))
await scanAndGenerate(directory, {
outDir: options.out,
serverLanguage: options.language,
port: options.port,
framework: options.framework
})
} catch (error) {
console.error(chalk.red("❌ Scan failed:"), error)
process.exit(1)
}
})
// ============================================================================
// INIT Command - Create template server (legacy)
// ============================================================================
program
.command("init")
.description("Create a template MCP server with sample tools (legacy)")
.option("-o, --out <directory>", "Output directory", ".mcp-generated")
.option("-l, --language <language>", "Server language (typescript|javascript)", "typescript")
.option("-n, --name <name>", "Server name", "my-mcp-server")
.action(async (options: any) => {
try {
console.log(chalk.blue("🔧 Sigyl MCP Template Generator"))
console.log(chalk.gray("Creating template MCP server...\n"))
await initTemplate({
outDir: options.out,
serverLanguage: options.language,
name: options.name
})
} catch (error) {
console.error(chalk.red("❌ Template creation failed:"), error)
process.exit(1)
}
})
// ============================================================================
// DEV Command - Development mode (legacy)
// ============================================================================
program
.command("dev")
.description("Start development mode with hot reload (legacy)")
.argument("[directory]", "Directory containing Express app", ".")
.option("-p, --port <port>", "MCP server port", "8181")
.option("--app-port <port>", "Express app port", "3000")
.option("-l, --language <language>", "Server language (typescript|javascript)", "typescript")
.option("--no-open", "Don't open MCP Inspector automatically")
.action(async (directory: string, options: any) => {
try {
console.log(chalk.blue("🚀 Sigyl MCP Development Mode"))
console.log(chalk.gray("Starting development server with hot reload...\n"))
await dev({
directory,
port: options.port,
appPort: options.appPort,
serverLanguage: options.language,
open: options.open
})
} catch (error) {
console.error(chalk.red("❌ Development mode failed:"), error)
process.exit(1)
}
})
// ============================================================================
// INSPECT Command - Launch MCP Inspector
// ============================================================================
program
.command("inspect")
.description("Launch MCP Inspector to test your server or endpoint")
.argument("[server-path]", "Path or URL to MCP server (default: .sigyl-mcp/integration or .mcp-generated/server.js)", ".sigyl-mcp/integration")
.option("--server-port <port>", "Port for MCP server (default: 8080)")
.option("--playground-port <port>", "Port for Inspector Playground UI (default: 3001)")
.option("--playground-dir <dir>", "Path to Inspector Playground directory (default: playground)")
.option("--auto-build-playground", "Auto-build playground UI if missing")
.option("--inspector-mode <mode>", "Inspector mode: local (default) or remote", "local")
.action(async (serverPath: string, options: any) => {
try {
console.log(chalk.blue("🕵️ Sigyl MCP Inspector"))
console.log(chalk.gray("Launching MCP Inspector UI...\n"))
await inspectCommand(
[],
serverPath,
{
serverPort: options.serverPort ? parseInt(options.serverPort, 10) : undefined,
playgroundPort: options.playgroundPort ? parseInt(options.playgroundPort, 10) : undefined,
playgroundDir: options.playgroundDir,
autoBuildPlayground: !!options.autoBuildPlayground,
inspectorMode: options.inspectorMode,
}
)
} catch (error) {
console.error(chalk.red("❌ Inspector launch failed:"), error)
process.exit(1)
}
})
// ============================================================================
// INSTALL Command - Install in Claude Desktop (legacy)
// ============================================================================
program.addCommand(createInstallCommand())
// ============================================================================
// RUN Command - Run MCP server
// ============================================================================
program.addCommand(createRunCommand())
// ============================================================================
// CLEAN Command - Clean generated files (legacy)
// ============================================================================
program
.command("clean")
.description("Clean generated MCP server files (legacy)")
.option("-o, --out <directory>", "Output directory to clean", ".mcp-generated")
.action((options: any) => {
try {
const outDir = options.out
if (existsSync(outDir)) {
rmSync(outDir, { recursive: true, force: true })
console.log(chalk.green(`✅ Cleaned ${outDir} directory`))
} else {
console.log(chalk.yellow(`⚠️ Directory ${outDir} not found`))
}
} catch (error) {
console.error(chalk.red("❌ Clean failed:"), error)
process.exit(1)
}
})
// ============================================================================
// BUILD Command - Build MCP server (legacy)
// ============================================================================
program
.command("build")
.description("Build MCP server from TypeScript to JavaScript (legacy)")
.argument("[directory]", "Directory containing MCP server", ".mcp-generated")
.action(async (directory: string) => {
try {
console.log(chalk.blue("🔨 Building Sigyl MCP Server"))
console.log(chalk.gray("Compiling TypeScript to JavaScript...\n"))
const packageJsonPath = join(directory, "package.json")
const tsconfigPath = join(directory, "tsconfig.json")
if (!existsSync(packageJsonPath)) {
console.error(chalk.red(`❌ No package.json found in ${directory}`))
console.log(chalk.blue("💡 Generate a server first with:"))
console.log(chalk.gray(" sigyl scan"))
process.exit(1)
}
if (!existsSync(tsconfigPath)) {
console.error(chalk.red(`❌ No tsconfig.json found in ${directory}`))
process.exit(1)
}
const { spawn } = await import("node:child_process")
const tscProcess = spawn("npx", ["tsc"], {
cwd: directory,
stdio: "inherit"
})
tscProcess.on("exit", (code) => {
if (code === 0) {
console.log(chalk.green("✅ Build completed successfully"))
} else {
console.error(chalk.red("❌ Build failed"))
process.exit(1)
}
})
} catch (error) {
console.error(chalk.red("❌ Build failed:"), error)
process.exit(1)
}
})
// ============================================================================
// WIZARD Command - Launch interactive setup wizard
// ============================================================================
program
.command("wizard")
.description("Launch interactive setup wizard")
.action(runWizard)
// ============================================================================
// ERROR HANDLING
// ============================================================================
program.configureOutput({
writeErr: (str) => process.stderr.write(chalk.red(str)),
writeOut: (str) => process.stdout.write(str),
})
process.on("unhandledRejection", (reason, promise) => {
console.error(chalk.red("❌ Unhandled Rejection at:"), promise, chalk.red("reason:"), reason)
process.exit(1)
})
process.on("uncaughtException", (error) => {
console.error(chalk.red("❌ Uncaught Exception:"), error)
process.exit(1)
})
if (require.main === module) {
program.parse()
}
export default program