diff --git a/package-lock.json b/package-lock.json index b8d69f3..aef493a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,17 @@ { "name": "mcpctl", - "version": "0.0.3", + "version": "0.0.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mcpctl", - "version": "0.0.3", + "version": "0.0.6", "license": "MIT", "dependencies": { + "@supabase/supabase-js": "^2.49.4", "chalk": "^5.4.1", + "dotenv": "^16.5.0", "fuzzy": "^0.1.3", "inquirer": "^12.5.2", "yargs": "^17.7.2" @@ -1114,6 +1116,92 @@ "win32" ] }, + "node_modules/@supabase/auth-js": { + "version": "2.69.1", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.69.1.tgz", + "integrity": "sha512-FILtt5WjCNzmReeRLq5wRs3iShwmnWgBvxHfqapC/VoljJl+W8hDAyFmf1NVw3zH+ZjZ05AKxiKxVeb0HNWRMQ==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/functions-js": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.4.4.tgz", + "integrity": "sha512-WL2p6r4AXNGwop7iwvul2BvOtuJ1YQy8EbOd0dhG1oN1q8el/BIRSFCFnWAMM/vJJlHWLi4ad22sKbKr9mvjoA==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/node-fetch": { + "version": "2.6.15", + "resolved": "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.15.tgz", + "integrity": "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/@supabase/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/@supabase/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/@supabase/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/@supabase/postgrest-js": { + "version": "1.19.4", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.19.4.tgz", + "integrity": "sha512-O4soKqKtZIW3olqmbXXbKugUtByD2jPa8kL2m2c1oozAO11uCcGrRhkZL0kVxjBLrXHE0mdSkFsMj7jDSfyNpw==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.11.2.tgz", + "integrity": "sha512-u/XeuL2Y0QEhXSoIPZZwR6wMXgB+RQbJzG9VErA3VghVt7uRfSVsjeqd7m5GhX3JR6dM/WRmLbVR8URpDWG4+w==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14", + "@types/phoenix": "^1.5.4", + "@types/ws": "^8.5.10", + "ws": "^8.18.0" + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.7.1.tgz", + "integrity": "sha512-asYHcyDR1fKqrMpytAS1zjyEfvxuOIp1CIXX7ji4lHHcJKqyk+sLl/Vxgm4sN6u8zvuUtae9e4kDxQP2qrwWBA==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.49.4", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.49.4.tgz", + "integrity": "sha512-jUF0uRUmS8BKt37t01qaZ88H9yV1mbGYnqLeuFWLcdV+x1P4fl0yP9DGtaEhFPZcwSom7u16GkLEH9QJZOqOkw==", + "dependencies": { + "@supabase/auth-js": "2.69.1", + "@supabase/functions-js": "2.4.4", + "@supabase/node-fetch": "2.6.15", + "@supabase/postgrest-js": "1.19.4", + "@supabase/realtime-js": "2.11.2", + "@supabase/storage-js": "2.7.1" + } + }, "node_modules/@types/estree": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", @@ -1124,11 +1212,23 @@ "version": "22.14.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.0.tgz", "integrity": "sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==", - "devOptional": true, "dependencies": { "undici-types": "~6.21.0" } }, + "node_modules/@types/phoenix": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.6.tgz", + "integrity": "sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==" + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/yargs": { "version": "17.0.33", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", @@ -1411,6 +1511,17 @@ } } }, + "node_modules/dotenv": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", + "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -2305,8 +2416,7 @@ "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "devOptional": true + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==" }, "node_modules/webidl-conversions": { "version": "4.0.2", @@ -2431,6 +2541,26 @@ "node": ">=8" } }, + "node_modules/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index 282da18..e1ccf87 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,9 @@ "typescript": "^5.8.3" }, "dependencies": { + "@supabase/supabase-js": "^2.49.4", "chalk": "^5.4.1", + "dotenv": "^16.5.0", "fuzzy": "^0.1.3", "inquirer": "^12.5.2", "yargs": "^17.7.2" diff --git a/src/index.ts b/src/index.ts index 334a667..426ac4f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,40 +4,22 @@ import yargs from "yargs"; import { hideBin } from "yargs/helpers"; import inquirer from "inquirer"; import fuzzy from "fuzzy"; -import { packages } from "./packages"; + import { spawn } from "child_process"; import { readClaudeConfig, writeClaudeConfig } from "./claude_config"; import { term } from "./term"; import { InputType } from "./create_package"; +import { RegistryManager } from './registry/registry_manager'; +import { PackageMetadata } from './types/registry'; +const registryManager = new RegistryManager(); -// Search functionality -function searchPackages(term: string) { - const packageNames = Object.keys(packages); - const results = fuzzy - .filter(term, packageNames) - .map((result) => result.string); - - // Include aliases in search - Object.entries(packages).forEach(([name, pkg]) => { - if (!results.includes(name)) { - const matchesAlias = pkg.aliases.some( - (alias) => fuzzy.match(term, alias) !== null - ); - if (matchesAlias) results.push(name); - } - }); - - return results; -} - -// Format output -function formatPackage(name: string) { - const pkg = packages[name as keyof typeof packages]; +async function formatPackage(pkg: PackageMetadata) { + if (!pkg) return ''; return ` -${term.bold(pkg.name)} ${term.gray(`(${pkg.aliases.join(", ")})`)} +${term.bold(pkg.name)} ${term.gray(`(${pkg.version})`)} ${term.dim("Dependencies:")} ${ - pkg.dependsOn.length ? pkg.dependsOn.join(", ") : "None" + pkg.dependencies.length ? pkg.dependencies.join(", ") : "None" } ${term.dim("Inputs:")} ${pkg.inputs @@ -70,28 +52,15 @@ ${term.green(name)} }); } async function installPackage(packageName: string, serverName?: string) { - // Find package - const pkg = packages[packageName as keyof typeof packages]; - if (!pkg) { - const suggestions = searchPackages(packageName); - if (suggestions.length > 0) { - console.log(term.red(`Package "${packageName}" not found.`)); - console.log(term.yellow(`Did you mean: ${suggestions.join(", ")}?`)); - } else { - console.log(term.red(`Package "${packageName}" not found.`)); - } - return; - } + let packages = await registryManager.listPackages(packageName,true); + const pkg = packages[0]; - // Check dependencies - if (pkg.dependsOn.length > 0) { + if (pkg.dependencies.length > 0) { console.log( - term.yellow(`Checking dependencies: ${pkg.dependsOn.join(", ")}`) + term.yellow(`Checking dependencies: ${pkg.dependencies.join(", ")}`) ); - // Implement dependency checking logic } - // Use provided server name or prompt for one let finalServerName = serverName; if (!finalServerName) { const answers = await inquirer.prompt([ @@ -107,14 +76,12 @@ async function installPackage(packageName: string, serverName?: string) { finalServerName = answers.serverName; } - // Collect inputs + const inputs: Record = {}; for (const input of pkg.inputs) { - // Define the inquirer prompt type based on input.type - // Use type assertion to help TypeScript recognize all possible values + const inputType = input.type as InputType; - // Map InputType to inquirer prompt type let promptType: string; switch (inputType) { case "boolean": @@ -129,23 +96,23 @@ async function installPackage(packageName: string, serverName?: string) { break; } - // Set default value if provided + const promptConfig: any = { type: promptType, name: "value", message: `${input.description}${input.required ? " (required)" : ""}:`, }; - // Only add default if it's defined + if ((input as any).default) { promptConfig.default = (input as any).default; } - // Add appropriate validation + promptConfig.validate = (value: any) => { if (input.required) { if (inputType === "boolean") { - // Boolean values are always valid in a confirm prompt + return true; } else if ( value === undefined || @@ -161,10 +128,10 @@ async function installPackage(packageName: string, serverName?: string) { inputs[input.name] = answers.value; } - // Build config - const mcpConfig = pkg.buildConfig(inputs as any); - // Update Claude config + const mcpConfig = await registryManager.buildConfig(packageName, inputs); + + await updateClaudeConfig({ name: finalServerName, command: mcpConfig.command, @@ -304,22 +271,16 @@ async function startServer(serverName: string) { } async function listPackages(searchTerm?: string) { - let packageNames = Object.keys(packages); - - if (searchTerm) { - packageNames = searchPackages(searchTerm); - if (packageNames.length === 0) { - console.log(term.yellow(`No packages found matching "${searchTerm}"`)); - return; - } - console.log(term.bold(`\nPackages matching "${searchTerm}":`)); - } else { - console.log(term.bold("\nAvailable MCP Packages:")); + let packages = await registryManager.listPackages(searchTerm); + if (packages.length ==0){ + console.log(term.red(`No packages found`)); + return; } - - packageNames.forEach((name) => { - console.log(formatPackage(name)); - }); + + for (const pkg of packages) { + console.log(await formatPackage(pkg)); + } + console.log(term.green(`Found ${packages.length} packages`)); } // Main CLI definition diff --git a/src/registry/registry_manager.ts b/src/registry/registry_manager.ts new file mode 100644 index 0000000..5511b99 --- /dev/null +++ b/src/registry/registry_manager.ts @@ -0,0 +1,74 @@ +import { PackageMetadata } from '../types/registry'; +require('dotenv').config(); + +export class RegistryManager { + + async listPackages(searchTerm?: string, hard_search?: boolean): Promise { + try { + const params = new URLSearchParams(); + + if (searchTerm) params.append("package_name", searchTerm); + if (hard_search !== undefined) params.append("hard_search", String(hard_search)); + + const url = `${process.env.BACKEND_URL || ''}/get-package?${params.toString()}`; + const response = await fetch(url); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.json(); + const packages = data.packages; + + if (!packages) return []; + + return packages.map(pkg => ({ + name: pkg.name, + version: pkg.version, + description: pkg.description, + repository: pkg.repository, + maintainer: pkg.maintainer, + inputs: pkg.inputs?.map(input => input.meta) || [], + buildConfig: pkg.build_config, + dependencies: pkg.dependencies?.map(d => d.dependency_name) || [] + })); + } catch (error) { + console.error('Error fetching packages:', error); + return []; + } + } + + async buildConfig(name: string, inputs: Record): Promise<{ + command: string; + args: string[]; + env: Record; + }> { + let all_packages =await this.listPackages(name, true); + const pkg = all_packages[0] + if (!pkg || !pkg.buildConfig) { + throw new Error(`No build configuration found for package ${name}`); + } + + const config = { + command: pkg.buildConfig.command, + args: [...pkg.buildConfig.args], + env: {} as Record + }; + + + if (pkg.buildConfig.configOptions) { + for (const [key, flag] of Object.entries(pkg.buildConfig.configOptions)) { + if (inputs[key]) { + config.args.push(flag as string); + } + } + } + + + pkg.inputs.forEach(input => { + if (inputs[input.name]) { + config.env[input.name] = inputs[input.name]; + } + }); + + return config; + } +} \ No newline at end of file diff --git a/src/types/registry.ts b/src/types/registry.ts new file mode 100644 index 0000000..5fcb044 --- /dev/null +++ b/src/types/registry.ts @@ -0,0 +1,30 @@ +export interface PackageInput { + name: string; + type: 'string' | 'boolean' | 'number'; + required: boolean; + description: string; + } + +export interface BuildConfig { + command: string; + args: string[]; + configOptions?: Record; +} + +export interface PackageMetadata { + name: string; + aliases: string[]; + version: string; + description: string; + repository: string; + maintainer: string; + inputs: PackageInput[]; + dependencies: string[]; + buildConfig: BuildConfig; + } + + export interface Registry { + registryVersion: string; + lastUpdated: string; + packages: Record; + } \ No newline at end of file