UNPKG

@kya-os/cli

Version:

CLI for MCP-I setup and management

251 lines • 13.4 kB
import chalk from "chalk"; import ora from "ora"; import { detectPlatform, getPlatformSpecificConfig, } from "../utils/platform-detector.js"; import { cliEffects, EffectTrigger } from "../effects/cli-integration.js"; import { pollForClaimStatus } from "../utils/claim-polling.js"; import { EnvManager } from "../utils/env-manager.js"; // Dynamic import to handle CI validation where the package might not be built yet // Using async loading to handle ESM/CommonJS boundary let enableMCPIdentityCLI = null; /** * Initialize XMCP-I in the current project * Requirements: 2.7 (init command), 4.1-4.4 (identity management) */ export async function init(options = {}) { console.log(chalk.cyan("\nšŸš€ Initializing XMCP-I Agent\n")); try { // Detect platform if not specified const platformInfo = options.platform ? { platform: options.platform } : detectPlatform(); const platform = typeof platformInfo === "string" ? platformInfo : platformInfo.platform; const platformConfig = getPlatformSpecificConfig(platform); if (options.verbose) { console.log(chalk.gray(`Detected platform: ${platform}`)); console.log(chalk.gray(`Platform config:`, platformConfig)); } console.log(chalk.bold("šŸ“‹ Agent Configuration")); console.log(` Name: ${options.name || "Auto-generated"}`); console.log(` Platform: ${platform}`); if (options.description) { console.log(` Description: ${options.description}`); } if (options.repository) { console.log(` Repository: ${options.repository}`); } // Load the MCP-I module dynamically to handle ESM/CommonJS boundary if (!enableMCPIdentityCLI) { try { const mcpModule = await import("@kya-os/mcp-i"); enableMCPIdentityCLI = mcpModule.enableMCPIdentityCLI; } catch (error) { console.error(chalk.red("Error: MCP-I module not available. Please ensure @kya-os/mcp-i is installed.")); console.error(chalk.yellow("Run: npm install @kya-os/mcp-i")); process.exit(1); } } // Initialize identity using the CLI adapter const spinner = ora("Setting up agent identity...").start(); try { // Use the CLI adapter to enable identity with real key generation const result = await enableMCPIdentityCLI({ name: options.name, description: options.description, repository: options.repository, endpoint: options.local ? "http://localhost:3000" : options.endpoint, logLevel: options.verbose ? "info" : "silent", skipRegistration: options.skipRegistration, onProgress: async (event) => { // Update spinner based on progress switch (event.stage) { case "checking_existing": spinner.text = "Checking for existing identity..."; break; case "generating_keys": spinner.text = "Generating cryptographic keys..."; // Show agent identity creation effect await cliEffects.showEffect(EffectTrigger.AGENT_IDENTITY_CREATE, `šŸ¤– Generating Agent Identity\nCreating cryptographic keys...`, { skipExitPrompt: true }); break; case "registering": spinner.text = "Registering with Know-That-AI..."; // Show DID generation effect for registration await cliEffects.showEffect(EffectTrigger.DID_GENERATION, `šŸ” Registering Agent\nConnecting to Know-That-AI...`, { skipExitPrompt: true }); break; case "saving": spinner.text = "Saving identity..."; break; case "complete": spinner.succeed("Identity created successfully!"); // Success effect shown after result is available break; } }, }); // Show success effect for new identities (after result is available) if (result.metadata.isNewIdentity) { await cliEffects.showEffect(EffectTrigger.OPERATION_SUCCESS, `āœ… Agent Identity Created\n\nDID: ${result.identity.did}`, { skipExitPrompt: true }); } // Save environment variables including agent metadata if (result.metadata.isNewIdentity && !options.skipRegistration) { try { const envManager = new EnvManager(); await envManager.updateEnvFile(".env.local", { AGENT_PRIVATE_KEY: result.identity.privateKey, AGENT_DID: result.identity.did, AGENT_PUBLIC_KEY: result.identity.publicKey, AGENT_KEY_ID: result.identity.kid, KYA_VOUCHED_API_KEY: "", // Will be set when agent is claimed ...(result.metadata.agentId && { AGENT_ID: result.metadata.agentId }), ...(result.metadata.agentSlug && { AGENT_SLUG: result.metadata.agentSlug }), }); if (options.verbose) { console.log(chalk.gray("\nāœ… Environment variables saved to .env.local")); } } catch (error) { if (options.verbose) { console.warn(chalk.yellow("\nāš ļø Could not save environment variables:", error.message)); } // Non-fatal, continue with initialization } } // Handle claim flow if we have a claim URL if (result.metadata.claimUrl && !options.skipRegistration) { console.log(`\n${chalk.bold("šŸŽ« Claim Your Agent:")}`); console.log(` ${chalk.cyan(result.metadata.claimUrl)}`); // Only prompt for Enter if in interactive terminal (TTY) // In non-interactive environments (CI/CD, Docker, pipes), proceed automatically if (process.stdin.isTTY) { console.log(`\n ${chalk.gray("Press Enter to open in browser...")}`); // Wait for user to press Enter with timeout const cleanup = () => { process.stdin.pause(); process.stdin.removeAllListeners('data'); }; await Promise.race([ new Promise((resolve) => { process.stdin.once('data', () => { cleanup(); resolve(); }); process.stdin.resume(); }), // Timeout after 30 seconds if no input new Promise((resolve) => setTimeout(() => { cleanup(); resolve(); }, 30000)), ]); } else { // Non-interactive mode: proceed automatically after brief delay console.log(chalk.gray("\n Non-interactive mode detected, opening browser...")); await new Promise((resolve) => setTimeout(resolve, 1000)); } // Open browser automatically try { const open = (await import("open")).default; await open(result.metadata.claimUrl); console.log(chalk.green("\nāœ… Browser opened!")); } catch (error) { console.log(chalk.yellow("\nāš ļø Could not open browser automatically")); console.log(chalk.cyan(` Please visit: ${result.metadata.claimUrl}`)); } // Poll for claim status unless user wants to skip if (!options.skipClaimCheck) { console.log("\n"); const agentSlug = result.metadata.agentSlug || result.metadata.agentId || options.name || "unknown"; const apiEndpoint = options.local ? "http://localhost:3000" : (options.endpoint || "https://knowthat.ai"); const claimed = await pollForClaimStatus(agentSlug, result.identity.did, apiEndpoint, { verbose: options.verbose }); if (claimed) { // Clear screen for clean final display, then show success console.clear(); console.log(chalk.green("šŸŽ‰ Your agent is now claimed and ready to use!\n")); } } else { console.log(chalk.gray("\n Skipping claim check...")); console.log(chalk.gray(" You can claim your agent anytime at the URL above.")); } } // Show final success message with complete identity information console.log(`${chalk.bold("šŸ”‘ Identity Information:")}`); console.log(` DID: ${chalk.gray(result.identity.did)}`); console.log(` Key ID: ${chalk.gray(result.identity.kid)}`); if (result.metadata.agentId) { console.log(` Agent ID: ${chalk.gray(result.metadata.agentId)}`); } if (result.metadata.agentSlug) { console.log(` Agent Slug: ${chalk.gray(result.metadata.agentSlug)}`); } console.log(chalk.green("\nāœ… Agent initialized successfully!")); // Show next steps console.log(`\n${chalk.bold("šŸš€ Next Steps:")}`); console.log(` ${chalk.gray("1. Start development:")} ${chalk.cyan("mcpi dev")}`); if (!options.skipRegistration && result.metadata.claimUrl) { console.log(` ${chalk.gray("2. Check status:")} ${chalk.cyan("mcpi status")}`); } console.log(` ${chalk.gray("3. Build for production:")} ${chalk.cyan("mcpi build")}`); // TODO: Fix platform config types // if (platformConfig.deployCommand) { // console.log( // ` ${chalk.gray("5. Deploy:")} ${chalk.cyan(platformConfig.deployCommand)}` // ); // } // Show platform-specific environment variables // if (platformConfig.envVars && platformConfig.envVars.length > 0) { // console.log(`\n${chalk.bold("šŸ”§ Environment Variables:")}`); // console.log(` ${chalk.gray("For production deployment, set:")}`); // platformConfig.envVars.forEach((envVar) => { // console.log(` ${chalk.cyan(envVar)}`); // }); // console.log( // `\n ${chalk.gray("Use")} ${chalk.cyan("mcpi env show")} ${chalk.gray("for platform-specific names")}` // ); // } console.log(`\n${chalk.gray("Documentation:")} ${chalk.cyan("https://docs.kya-os.ai/xmcp-i")}`); // Ensure stdin is properly closed and exit cleanly if (process.stdin.isTTY) { process.stdin.pause(); process.stdin.removeAllListeners('data'); } // Exit successfully (skip in test environment) const isTestEnvironment = process.env.NODE_ENV === 'test' || !!process.env.VITEST; if (!isTestEnvironment) { process.exit(0); } } catch (error) { spinner.fail("Failed to set up identity"); throw error; } } catch (error) { console.log(chalk.red("\nāŒ Initialization failed")); console.log(chalk.red(` ${error.message}`)); if (options.verbose && error.stack) { console.log(chalk.gray("\nStack trace:")); console.log(chalk.gray(error.stack)); } // Provide helpful guidance console.log(`\n${chalk.gray("You can:")}`); console.log(`${chalk.gray("• Try again with --verbose for more details")}`); console.log(`${chalk.gray("• Check file permissions in current directory")}`); console.log(`${chalk.gray("• Ensure you're in a valid project directory")}`); // Ensure stdin is properly closed before exiting if (process.stdin.isTTY) { process.stdin.pause(); process.stdin.removeAllListeners('data'); } // Exit with error code (skip in test environment) const isTestEnvironment = process.env.NODE_ENV === 'test' || !!process.env.VITEST; if (!isTestEnvironment) { process.exit(21); // XMCP_I_ENOIDENTITY } // In test environment, re-throw error so tests can catch it throw error; } } //# sourceMappingURL=init.js.map