UNPKG

gemini-cli-chat

Version:

Gemini AI CLI with markdown and colorful UX

192 lines (172 loc) 5.36 kB
import inquirer from "inquirer"; import chalk from "chalk"; import ora from "ora"; import { GoogleGenerativeAI } from "@google/generative-ai"; import { getApiKey, setConfig } from "./config.js"; import { marked } from "marked"; import TerminalRenderer from "marked-terminal"; import wrap from "word-wrap"; // MARKDOWN RENDERING SETUP marked.setOptions({ renderer: new TerminalRenderer({ reflowText: true, strong: chalk.yellow.bold, listItem: chalk.green, heading: chalk.cyan.bold, codespan: chalk.magenta, code: chalk.magenta, link: chalk.blue.underline, }), }); function colorizeDiagrams(text) { return text .split("\n") .map((line) => (line.match(/[+|>-]/) ? chalk.cyan(line) : line)) .join("\n"); } function enhanceBold(text) { return text.replace(/\*\*(.*?)\*\*/g, chalk.yellow.bold("$1")); } function renderMarkdownResponse(response) { // Wrap text to fit terminal width before applying markdown const wrappedText = wrap(response, { width: process.stdout.columns ? process.stdout.columns - 2 : 80, indent: "", trim: true, }); const markdownOutput = marked(wrappedText); const diagramColored = colorizeDiagrams(markdownOutput); return enhanceBold(diagramColored); } // PAGINATED DISPLAY async function displayPaginatedResponse(text) { const lines = text.split("\n"); const pageSize = process.stdout.rows - 3; // Leave space for prompt and margin let currentPage = 0; while (currentPage * pageSize < lines.length) { const pageLines = lines.slice( currentPage * pageSize, (currentPage + 1) * pageSize ); console.log(pageLines.join("\n")); const remainingLines = lines.length - (currentPage + 1) * pageSize; if (remainingLines > 0) { await inquirer.prompt([ { type: "input", name: "continuePrompt", message: chalk.dim(`-- More (${remainingLines} lines remaining) --`), }, ]); // Clear the "More" prompt line before showing the next page process.stdout.write("\x1B[1A\x1B[K"); } else { console.log(); // Add a newline at the end of the response break; } currentPage++; } } // API KEY SETUP async function ensureApiKey() { let apiKey = getApiKey(); if (!apiKey) { console.log(chalk.yellow.bold("\nNo API key found!")); console.log(chalk.cyan("Follow these steps to get your Gemini API key:\n")); console.log( chalk.green("1.") + " Go to " + chalk.blue.underline("https://aistudio.google.com/app/apikey") ); console.log(chalk.green("2.") + " Sign in with your Google account."); console.log( chalk.green("3.") + " Click " + chalk.bold('"Create API key"') + " and copy the generated key." ); console.log( chalk.green("4.") + " The key will look like " + chalk.magenta("AIzaSy...") ); console.log(chalk.green("5.") + " Paste it below when prompted.\n"); try { const { key } = await inquirer.prompt([ { type: "input", name: "key", message: "Enter your Gemini API Key:", validate: (input) => { if (!input || input.trim() === "") return "API key cannot be empty!"; return true; }, }, ]); setConfig("API_KEY", key); apiKey = key; } catch (err) { if (err.name === "ExitPromptError") { console.log(chalk.yellow("\nGoodbye! (Ctrl+C)")); process.exit(0); } throw err; } } return apiKey; } // VALIDATE API KEY async function validateApiKey(key) { try { const genAI = new GoogleGenerativeAI({ apiKey: key }); const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" }); await model.generateContent("ping"); return true; } catch (err) { return false; } } // MAIN CHAT LOOP export async function startChat() { console.clear(); console.log( chalk.cyanBright.bold("Welcome to Gemini CLI!") + " " + chalk.green("Ask me anything...\n") ); const apiKey = await ensureApiKey(); if (!(await validateApiKey(apiKey))) { console.log(chalk.red("Invalid API Key! Please check and try again.")); process.exit(1); } // Initialize Gemini model (FIX for correct constructor args) const genAI = new GoogleGenerativeAI({ apiKey }); const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" }); console.log(chalk.green('Type "exit" or ctrl+c to quit\n')); while (true) { try { const { userInput } = await inquirer.prompt([ { type: "input", name: "userInput", message: chalk.blue("You:") }, ]); if (userInput.trim() === "") { continue; } if (userInput.toLowerCase() === "exit") { console.log(chalk.yellow("Goodbye!")); break; } const spinner = ora("Thinking...").start(); const result = await model.generateContent(userInput); spinner.stop(); const responseText = renderMarkdownResponse(result.response.text()); await displayPaginatedResponse(responseText); } catch (err) { if (err.name === "ExitPromptError") { console.log(chalk.yellow("\nGoodbye! (Ctrl+C)")); process.exit(0); } console.log(chalk.red("Error:"), err.message); } } }