UNPKG

research-cli

Version:

AI-powered research assistant with web search capabilities and beautiful terminal UI

313 lines 12 kB
/** * Interactive UI components using terminal-kit * Provides beautiful, user-friendly prompts for CLI interactions */ import termkit from "terminal-kit"; const { terminal } = termkit; // Configure terminal for better behavior const term = terminal; export class InteractiveUI { /** * Display a welcome banner for easymode setup */ static showWelcomeBanner() { term.clear(); term.moveTo(1, 1); // Beautiful header with colors term.bold.cyan("╭─────────────────────────────────────────────────────────────╮\n"); term.bold.cyan("│ │\n"); term.bold.cyan("│ "); term.bold.white("🚀 Research CLI - Easy Setup Mode"); term.bold.cyan(" │\n"); term.bold.cyan("│ │\n"); term.bold.cyan("│ "); term.white("Let's get you set up with AI research in just a few steps!"); term.bold.cyan(" │\n"); term.bold.cyan("│ │\n"); term.bold.cyan("╰─────────────────────────────────────────────────────────────╯\n\n"); } /** * Show provider selection menu with descriptions */ static async selectProvider() { const providers = [ { name: "openai", displayName: "OpenAI", description: "GPT-4, GPT-4o, ChatGPT models", url: "https://platform.openai.com/api-keys", }, { name: "claude", displayName: "Anthropic Claude", description: "Claude 3.5 Sonnet, Haiku models", url: "https://console.anthropic.com/", }, { name: "perplexity", displayName: "Perplexity", description: "Research-focused with web search", url: "https://www.perplexity.ai/settings/api", }, { name: "gemini", displayName: "Google Gemini", description: "Gemini Pro, Ultra models", url: "https://makersuite.google.com/app/apikey", }, ]; term.bold.yellow("\n📋 Choose your AI provider:\n\n"); // Create menu items with rich descriptions const items = providers.map((provider) => `${provider.displayName.padEnd(20)} ${term.str?.dim(provider.description) ?? term.dim(provider.description)}`); try { const response = await term.singleColumnMenu(items, { style: term.cyan, selectedStyle: term.bold.blue.bgWhite, leftPadding: " ", extraLines: 1, }).promise; if (!response || response.selectedIndex === undefined) { return null; } return providers[response.selectedIndex]?.name || null; } catch (error) { if (error && typeof error === "object" && "code" in error && error.code === "USER_ABORT") { return null; // User pressed Ctrl+C } throw error; } } /** * Interactive API key input with validation hints */ static async getApiKey(provider, providerUrl) { term.bold.green(`\n🔑 Setting up ${provider.toUpperCase()} API Key\n\n`); // Show instructions term.white("To get your API key:\n"); term.cyan(` 1. Visit: ${providerUrl}\n`); term.cyan(" 2. Sign up or log in to your account\n"); term.cyan(" 3. Generate a new API key\n"); term.cyan(" 4. Copy it and paste it below\n\n"); // Show expected format hint const formatHints = { openai: 'Format: sk-... (starts with "sk-")', claude: 'Format: sk-ant-... (starts with "sk-ant-")', perplexity: 'Format: pplx-... (starts with "pplx-")', gemini: "Format: 39 character string", }; term.dim(`${formatHints[provider] || "Check provider documentation for format"}\n\n`); try { term.bold.white("Enter your API key: "); const apiKey = await term.inputField({ echoChar: "*", // Hide the key for security autoComplete: [], autoCompleteMenu: false, }).promise; if (!apiKey || apiKey.trim().length === 0) { term.red("\n❌ API key is required\n"); return null; } return apiKey.trim(); } catch (error) { if (error && typeof error === "object" && "code" in error && error.code === "USER_ABORT") { return null; // User pressed Ctrl+C } throw error; } } /** * Ask if user wants to save the key securely */ static async confirmSaveKey() { term.bold.yellow("\n🔐 Save API key securely?\n\n"); term.white("This will store your API key in your system keychain\n"); term.white("(recommended for security and convenience)\n\n"); try { const result = await term.yesOrNo({ yes: ["y", "Y", "ENTER"], no: ["n", "N"], }).promise; return result || false; } catch (error) { if (error && typeof error === "object" && "code" in error && error.code === "USER_ABORT") { return false; // User pressed Ctrl+C, default to not saving } throw error; } } /** * Show loading spinner while testing API key */ static async testApiKeyWithSpinner(testFunction) { term.bold.yellow("\n🧪 Testing API connection...\n"); // Create and show spinner const spinner = await term.spinner("unboxing-color"); term(" Testing..."); try { const result = await testFunction(); // Stop spinner and clear line spinner.animate(false); term.eraseLine(); try { // getCursorLocation() requires a callback in terminal-kit term.getCursorLocation((error, _, y) => { if (!error && y !== undefined) { term.moveTo(1, y); } }); } catch { // Ignore cursor positioning errors } return result; } catch (error) { // Stop spinner on error spinner.animate(false); term.eraseLine(); try { // getCursorLocation() requires a callback in terminal-kit term.getCursorLocation((error, _, y) => { if (!error && y !== undefined) { term.moveTo(1, y); } }); } catch { // Ignore cursor positioning errors } return { valid: false, error: error instanceof Error ? error.message : "Unknown error", }; } } /** * Show success message with next steps */ static showSuccessMessage(provider) { term.bold.green("\n✅ Setup Complete!\n\n"); term.white(`Your ${provider} API key has been configured successfully.\n\n`); term.bold.cyan("🚀 Try it out:\n"); term.white(` research-cli "What is the latest in AI research?"\n`); term.white(` research-cli "Explain quantum computing" --web\n\n`); term.bold.cyan("💡 Useful commands:\n"); term.white(" research-cli auth list - View all configured providers\n"); term.white(" research-cli auth test - Test API connections\n"); term.white(" research-cli --help - See all available options\n\n"); } /** * Show error message with helpful suggestions */ static showErrorMessage(error, provider) { term.bold.red("\n❌ Setup Failed\n\n"); term.white(`Error: ${error}\n\n`); if (provider) { term.bold.yellow("💡 Troubleshooting:\n"); term.white(` • Double-check your API key from the provider\n`); term.white(` • Make sure you have sufficient credits/quota\n`); term.white(` • Try running: research-cli auth login ${provider}\n\n`); } term.white("Need help? Check the documentation or try again.\n"); } /** * Simple yes/no prompt with custom message */ static async confirmAction(message, defaultYes = false) { term.white(`${message} `); term.dim(defaultYes ? "(Y/n): " : "(y/N): "); try { const result = await term.yesOrNo({ yes: defaultYes ? ["y", "Y", "ENTER"] : ["y", "Y"], no: defaultYes ? ["n", "N"] : ["n", "N", "ENTER"], }).promise; return result || false; } catch (error) { if (error && typeof error === "object" && "code" in error && error.code === "USER_ABORT") { return false; // User pressed Ctrl+C } throw error; } } /** * Enhanced text input with history and autocomplete */ static async textInput(prompt, options = {}) { term.white(prompt); try { const input = await term.inputField({ history: options.history || [], autoComplete: options.autoComplete || [], autoCompleteMenu: !!options.autoComplete?.length, }).promise; return input?.trim() || null; } catch (error) { if (error && typeof error === "object" && "code" in error && error.code === "USER_ABORT") { return null; // User pressed Ctrl+C } throw error; } } /** * Show a progress indicator for multi-step processes */ static async showProgress(currentStep, totalSteps, stepDescription) { const progress = currentStep / totalSteps; const barWidth = 40; const filledWidth = Math.round(barWidth * progress); // Simple approach - just erase line without cursor positioning // to avoid complex terminal-kit API issues term.eraseLine(); term.bold.cyan("["); // Use string repeat instead of term.str.repeat term("█".repeat(filledWidth)); term("░".repeat(barWidth - filledWidth)); term.bold.cyan("]"); term.white(` ${currentStep}/${totalSteps} - ${stepDescription}\n`); } /** * Handle Ctrl+C gracefully with cleanup */ static setupGracefulExit() { term.grabInput({ mouse: "button" }); term.on("key", (name) => { if (name === "CTRL_C") { term("\n\n"); term.bold.yellow("👋 Setup cancelled by user\n"); term.white("You can run "); term.cyan("research-cli easymode"); term.white(" again anytime!\n\n"); term.grabInput(false); process.exit(0); } }); } /** * Cleanup terminal state */ static cleanup() { term.grabInput(false); term.styleReset(); } } //# sourceMappingURL=interactive.js.map