@kya-os/cli
Version:
CLI for MCP-I setup and management
251 lines ⢠13.4 kB
JavaScript
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