UNPKG

@clduab11/gemini-flow

Version:

Revolutionary AI agent swarm coordination platform with Google Services integration, multimedia processing, and production-ready monitoring. Features 8 Google AI services, quantum computing capabilities, and enterprise-grade security.

668 lines (588 loc) 18.2 kB
/** * Gemini CLI * * Simplified command structure matching official Gemini CLI * Primary commands: chat, generate, list-models, auth */ import { Command } from "commander"; import chalk from "chalk"; import ora from "ora"; import * as fs from "fs"; import { SimpleAuth } from "../core/simple-auth.js"; import { SimpleInteractive } from "./simple-interactive.js"; import { Logger } from "../utils/logger.js"; export interface GeminiCLIOptions { model?: string; temperature?: number; maxTokens?: number; verbose?: boolean; json?: boolean; system?: string; file?: string; key?: string; test?: boolean; help?: boolean; } export interface GenerateResponse { text: string; usage: { promptTokens: number; completionTokens: number; totalTokens: number; }; model?: string; latency?: number; } export interface ModelInfo { name: string; description: string; maxTokens?: number; supportedFeatures?: string[]; } export class GeminiCLI { private program: Command; private auth: SimpleAuth; private logger: Logger; // Available models private readonly availableModels = [ { name: "gemini-1.5-flash", description: "Fast and versatile performance for diverse tasks", maxTokens: 1000000, supportedFeatures: ["text", "image", "audio", "video"], }, { name: "gemini-1.5-pro", description: "Complex reasoning tasks requiring more intelligence", maxTokens: 2000000, supportedFeatures: ["text", "image", "audio", "video", "code"], }, { name: "gemini-1.0-pro", description: "Natural language tasks, multi-turn text and code chat", maxTokens: 30720, supportedFeatures: ["text", "code"], }, ]; constructor() { this.program = new Command(); this.auth = new SimpleAuth(); this.logger = new Logger("GeminiCLI"); this.setupProgram(); this.setupCommands(); } /** * Setup main program configuration */ private setupProgram(): void { this.program .name("gemini") .description("Google Gemini AI CLI - Interactive AI Assistant") .version("1.1.0") .addHelpText( "before", chalk.cyan(` ╔══════════════════════════════════════════════════════════╗ ║ 🤖 Gemini AI CLI ║ ║ Powered by Google Generative AI ║ ╚══════════════════════════════════════════════════════════╝ `), ); // Global options this.program .option("-m, --model <model>", "model to use", "gemini-1.5-flash") .option("-t, --temperature <temp>", "temperature (0-2)", parseFloat, 0.7) .option("--max-tokens <tokens>", "maximum tokens", parseInt, 1000000) .option("-v, --verbose", "verbose output") .option("--json", "JSON output format") .option("-s, --system <instruction>", "system instruction"); } /** * Setup CLI commands */ private setupCommands(): void { this.setupChatCommand(); this.setupGenerateCommand(); this.setupListModelsCommand(); this.setupAuthCommand(); } /** * Setup chat command (interactive mode) */ private setupChatCommand(): void { const chatCommand = this.program .command("chat") .alias("c") .description("Start interactive conversation mode") .argument("[prompt]", "optional initial prompt") .option("--session <id>", "session ID for persistence") .option("--no-context", "disable context persistence") .action(async (prompt, options) => { const globalOptions = this.program.opts(); const mergedOptions = { ...globalOptions, ...options }; try { await this.executeChatCommand(prompt, mergedOptions); } catch (error) { this.handleError(error, mergedOptions); } }); chatCommand.addHelpText( "after", ` Examples: gemini chat # Start interactive mode gemini chat "Hello, how are you?" # Single prompt gemini chat --model gemini-1.5-pro # Use specific model gemini chat --session my-session # Resume/create session `, ); } /** * Setup generate command (one-shot generation) */ private setupGenerateCommand(): void { const generateCommand = this.program .command("generate") .alias("g") .description("Generate content from prompt") .argument("[prompt]", "text prompt to generate from") .option("-f, --file <path>", "read prompt from file") .option("--count <n>", "number of responses to generate", parseInt, 1) .action(async (prompt, options) => { const globalOptions = this.program.opts(); const mergedOptions = { ...globalOptions, ...options }; try { const result = await this.executeGenerateCommand( prompt, mergedOptions, ); console.log(this.formatOutput(result.text, result, mergedOptions)); } catch (error) { this.handleError(error, mergedOptions); } }); generateCommand.addHelpText( "after", ` Examples: gemini generate "Write a haiku about AI" gemini generate --file prompt.txt gemini generate "Explain quantum computing" --model gemini-1.5-pro gemini generate "Create JSON schema" --json `, ); } /** * Setup list-models command */ private setupListModelsCommand(): void { this.program .command("list-models") .alias("models") .description("List available models") .option("--detailed", "show detailed model information") .action(async (options) => { const globalOptions = this.program.opts(); const mergedOptions = { ...globalOptions, ...options }; try { const models = await this.listModels(); console.log(this.formatModelsList(models, mergedOptions)); } catch (error) { this.handleError(error, mergedOptions); } }); } /** * Setup auth command */ private setupAuthCommand(): void { const authCommand = this.program .command("auth") .description("Manage authentication") .option("--key <apikey>", "set API key") .option("--test", "test current API key") .option("--status", "show authentication status") .option("--clear", "clear authentication") .action(async (options) => { try { const result = await this.executeAuthCommand(options); if (result) { console.log(result); } } catch (error) { this.handleError(error, options); } }); authCommand.addHelpText( "after", ` Examples: gemini auth --status # Show auth status gemini auth --key AIzaSy... # Set API key gemini auth --test # Test current API key gemini auth --clear # Clear authentication Get your API key from: https://aistudio.google.com/app/apikey `, ); } /** * Execute chat command */ private async executeChatCommand( prompt?: string, options: GeminiCLIOptions = {}, ): Promise<void> { if (!this.auth.isAuthenticated()) { throw new Error( 'Authentication required. Use "gemini auth --key YOUR_API_KEY" to set your API key.', ); } // If prompt provided, handle as one-shot if (prompt) { const response = await this.generateContent(prompt, options); console.log(this.formatOutput(response.text, response, options)); return; } // Start interactive mode const interactiveMode = new SimpleInteractive({ model: options.model || "gemini-1.5-flash", maxTokens: options.maxTokens || 1000000, temperature: options.temperature || 0.7, sessionId: (options as any).session, verbose: options.verbose, }); await interactiveMode.start(); } /** * Execute generate command */ private async executeGenerateCommand( prompt?: string, options: GeminiCLIOptions = {}, ): Promise<GenerateResponse> { if (!this.auth.isAuthenticated()) { throw new Error( 'Authentication required. Use "gemini auth --key YOUR_API_KEY" to set your API key.', ); } let finalPrompt = prompt; // Read from file if specified if (options.file) { if (!fs.existsSync(options.file)) { throw new Error(`File not found: ${options.file}`); } finalPrompt = fs.readFileSync(options.file, "utf8"); } if (!finalPrompt) { throw new Error( "Prompt is required. Provide prompt as argument or use --file option.", ); } return await this.generateContent(finalPrompt, options); } /** * Execute auth command */ private async executeAuthCommand(options: any): Promise<string | void> { if (options.key) { const success = this.auth.setApiKey(options.key); if (success) { await this.auth.saveConfig(); return chalk.green("✅ API key set successfully"); } else { throw new Error( 'Invalid API key format. Key should start with "AIza" and be at least 35 characters long.', ); } } if (options.test) { if (!this.auth.isAuthenticated()) { throw new Error("No API key found. Set one with --key option."); } const spinner = ora("Testing API key...").start(); try { const isValid = await this.auth.testApiKey(); if (isValid) { spinner.succeed("API key is valid and working"); } else { spinner.fail("API key test failed"); } } catch (error) { spinner.fail( `API key test failed: ${error instanceof Error ? error.message : "Unknown error"}`, ); } return; } if (options.clear) { this.auth.clearAuth(); return chalk.yellow("Authentication cleared"); } // Default: show status const status = this.auth.getAuthStatus(); let output = chalk.blue("\n🔐 Authentication Status:\n\n"); output += `Status: ${status.isAuthenticated ? chalk.green("✅ Authenticated") : chalk.red("❌ Not authenticated")}\n`; if (status.isAuthenticated) { output += `Source: ${status.source}\n`; output += `Key: ${status.keyPrefix}\n`; output += `Format: ${status.keyFormat === "valid" ? chalk.green("Valid") : chalk.red("Invalid")}\n`; } if (!status.isAuthenticated) { output += "\n" + this.auth.getAuthHelpMessage(); } return output; } /** * Generate content using Gemini API */ async generateContent( prompt: string, options: GeminiCLIOptions = {}, ): Promise<GenerateResponse> { const startTime = performance.now(); try { const { GoogleGenerativeAI } = await import("@google/generative-ai"); const genAI = new GoogleGenerativeAI(this.auth.getApiKey()!); // Model configuration const modelConfig: any = { model: options.model || "gemini-1.5-flash", generationConfig: { temperature: options.temperature || 0.7, maxOutputTokens: Math.min(4096, (options.maxTokens || 1000000) / 4), }, }; // Add system instruction if provided if (options.system) { modelConfig.systemInstruction = options.system; } const model = genAI.getGenerativeModel(modelConfig); // Generate content const result = await model.generateContent(prompt); const response = await result.response; const text = response.text(); const endTime = performance.now(); const latency = endTime - startTime; // Estimate token usage (approximation) const promptTokens = this.estimateTokens(prompt); const completionTokens = this.estimateTokens(text); return { text, usage: { promptTokens, completionTokens, totalTokens: promptTokens + completionTokens, }, model: options.model || "gemini-1.5-flash", latency, }; } catch (error) { this.logger.error("Content generation failed", error); if (error instanceof Error) { if (error.message.includes("API_KEY")) { throw new Error( 'Invalid or missing API key. Use "gemini auth --key YOUR_API_KEY" to set your key.', ); } if (error.message.includes("QUOTA")) { throw new Error( "API quota exceeded. Please check your usage limits.", ); } if (error.message.includes("RATE_LIMIT")) { throw new Error( "Rate limit exceeded. Please wait a moment and try again.", ); } } throw error; } } /** * List available models */ async listModels(): Promise<ModelInfo[]> { return this.availableModels; } /** * Format output for display */ formatOutput( text: string, response: GenerateResponse, options: GeminiCLIOptions = {}, ): string { if (options.json) { return JSON.stringify( { response: text, usage: response.usage, model: response.model, latency: response.latency, }, null, 2, ); } let output = text; if (options.verbose) { output += chalk.gray(`\n\n--- Details ---`); output += chalk.gray(`\nModel: ${response.model}`); output += chalk.gray(`\nLatency: ${response.latency?.toFixed(2)}ms`); output += chalk.gray(`\nPrompt tokens: ${response.usage.promptTokens}`); output += chalk.gray( `\nCompletion tokens: ${response.usage.completionTokens}`, ); output += chalk.gray(`\nTotal tokens: ${response.usage.totalTokens}`); } else { output += chalk.gray(`\n\nTokens used: ${response.usage.totalTokens}`); } return output; } /** * Format models list for display */ private formatModelsList( models: ModelInfo[], options: GeminiCLIOptions = {}, ): string { if (options.json) { return JSON.stringify(models, null, 2); } let output = chalk.blue("\n📋 Available Models:\n\n"); models.forEach((model) => { output += chalk.cyan(`${model.name}\n`); output += chalk.gray(` ${model.description}\n`); if (options.verbose || (options as any).detailed) { output += chalk.gray( ` Max tokens: ${model.maxTokens?.toLocaleString()}\n`, ); output += chalk.gray( ` Features: ${model.supportedFeatures?.join(", ")}\n`, ); } output += "\n"; }); return output; } /** * Estimate token count (approximation) */ private estimateTokens(text: string): number { return Math.ceil(text.length / 4); } /** * Parse command line options */ parseOptions(args: string[]): GeminiCLIOptions { const options: GeminiCLIOptions = {}; for (let i = 0; i < args.length; i++) { const arg = args[i]; switch (arg) { case "--model": case "-m": options.model = args[++i]; break; case "--temperature": case "-t": { const temp = parseFloat(args[++i]); if (isNaN(temp) || temp < 0 || temp > 2) { throw new Error( "Invalid temperature value. Must be between 0 and 2.", ); } options.temperature = temp; break; } case "--max-tokens": { const tokens = parseInt(args[++i]); if (isNaN(tokens) || tokens <= 0) { throw new Error( "Invalid max-tokens value. Must be a positive integer.", ); } options.maxTokens = tokens; break; } case "--verbose": case "-v": options.verbose = true; break; case "--json": options.json = true; break; case "--system": case "-s": options.system = args[++i]; break; case "--file": case "-f": options.file = args[++i]; break; } } return options; } /** * Execute command programmatically */ async executeCommand( command: string, args: string[], options: GeminiCLIOptions = {}, ): Promise<string> { switch (command) { case "chat": case "c": await this.executeChatCommand(args[0], options); return "Interactive mode started"; case "generate": case "g": { const response = await this.executeGenerateCommand(args[0], options); return this.formatOutput(response.text, response, options); } case "list-models": case "models": { const models = await this.listModels(); return this.formatModelsList(models, options); } case "auth": { const result = await this.executeAuthCommand(options); return result || ""; } default: throw new Error(`Unknown command: ${command}`); } } /** * Handle errors */ private handleError(error: any, options: GeminiCLIOptions = {}): void { const message = error instanceof Error ? error.message : "Unknown error occurred"; if (options.json) { console.error(JSON.stringify({ error: message }, null, 2)); } else { console.error(chalk.red("Error:"), message); if (options.verbose && error instanceof Error && error.stack) { console.error(chalk.gray(error.stack)); } } process.exit(1); } /** * Get the commander program instance */ getProgram(): Command { return this.program; } /** * Parse and execute CLI */ async run(): Promise<void> { try { await this.program.parseAsync(process.argv); } catch (error) { this.handleError(error, this.program.opts()); } } }