UNPKG

@kleosr/pe2-cli

Version:

Transform your prompts into optimized PE² format for better AI responses. Works with OpenAI, Anthropic, Google, OpenRouter, and Ollama.

1,260 lines (1,104 loc) 65.4 kB
#!/usr/bin/env node import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; import { Command } from 'commander'; import chalk from 'chalk'; import figlet from 'figlet'; import readline from 'readline'; import inquirer from 'inquirer'; import os from 'os'; // Optimized imports - only load what's actually used import { SessionManager, createProgressBar, displayStatusBar, COMMANDS, validatePrompt, copyToClipboard, createTable, ThemeManager, StatsTracker, UserPreferences, validateAndSuggestCommand } from './utils.js'; import { createOpenAIClient } from './providers/openai/client.js'; import { createAnthropicClient } from './providers/anthropic/client.js'; import { createGoogleClient } from './providers/google/client.js'; import { createOllamaClient } from './providers/ollama/client.js'; import { createOpenRouterClient } from './providers/openrouter/client.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Configuration management const CONFIG_DIR = path.join(os.homedir(), '.kleosr-pe2'); const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json'); // Initialize utilities const sessionManager = new SessionManager(); const themeManager = new ThemeManager(); const statsTracker = new StatsTracker(); const userPreferences = new UserPreferences(); // Global state let lastResult = null; // Global flag to track if we're processing a prompt let isProcessingPrompt = false; // Add prompts directory constant const PROMPTS_DIR = path.join(process.cwd(), 'pe2-prompts'); // Optimized caching for prompt results const promptCache = new Map(); const MAX_CACHE_SIZE = 50; function getCacheKey(prompt, iterations) { // Create a simple hash-like key from prompt content and parameters const hash = prompt.substring(0, 100) + '_' + iterations; return hash.replace(/[^a-zA-Z0-9_]/g, '').substring(0, 50); } function getCachedResult(prompt, iterations) { const key = getCacheKey(prompt, iterations); return promptCache.get(key); } function setCachedResult(prompt, iterations, result) { const key = getCacheKey(prompt, iterations); // Simple cache size management if (promptCache.size >= MAX_CACHE_SIZE) { const firstKey = promptCache.keys().next().value; promptCache.delete(firstKey); } promptCache.set(key, { result, timestamp: Date.now(), hits: 0 }); } // === BEGIN: Optimize by removing redundant stub classes === // These stub classes add unnecessary overhead and complexity // Replace with simple constants and direct function calls const DEFAULT_CONTEXT = { domain: 'general', history: [], avgComplexity: 0 }; const DEFAULT_STRATEGY = { iterations: 2, focus: 'optimization', adaptiveFeatures: [], template: '' }; const DEFAULT_EVALUATION = { scores: {}, overallScore: 8.0 }; // Simplified helper functions function getContext() { return DEFAULT_CONTEXT; } function selectStrategy() { return DEFAULT_STRATEGY; } async function evaluatePrompt() { return DEFAULT_EVALUATION; } // === END: Optimize by removing redundant stub classes === // Clear console with cross-platform support function clearConsole() { process.stdout.write(process.platform === 'win32' ? '\x1Bc' : '\x1B[2J\x1B[3J\x1B[H'); } // Set terminal title function setTerminalTitle(title) { process.stdout.write(`\x1b]0;${title}\x07`); } // Provider configurations const PROVIDERS = { openai: { name: 'OpenAI', baseURL: 'https://api.openai.com/v1', models: [ // GPT-4 family (latest and most capable) 'gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'gpt-4-turbo-2024-04-09', 'gpt-4', 'gpt-4-0613', 'gpt-4-32k', 'gpt-4-32k-0613', // GPT-3.5 family (cost-effective) 'gpt-3.5-turbo', 'gpt-3.5-turbo-0125', 'gpt-3.5-turbo-1106', 'gpt-3.5-turbo-16k', // o1 reasoning models 'o1-preview', 'o1-mini', // Legacy models (for compatibility) 'gpt-4-1106-preview', 'gpt-4-0125-preview' ], defaultModel: 'gpt-4o-mini', keyLabel: 'OpenAI API Key' }, anthropic: { name: 'Anthropic (Claude)', baseURL: 'https://api.anthropic.com/v1', models: [ // Claude 3.5 family (latest and most capable) 'claude-3-5-sonnet-20241022', 'claude-3-5-sonnet-20240620', 'claude-3-5-haiku-20241022', // Claude 3 family 'claude-3-opus-20240229', 'claude-3-sonnet-20240229', 'claude-3-haiku-20240307', // Legacy Claude models 'claude-2.1', 'claude-2.0', 'claude-instant-1.2' ], defaultModel: 'claude-3-5-sonnet-20241022', keyLabel: 'Anthropic API Key' }, google: { name: 'Google (Gemini)', baseURL: 'https://generativelanguage.googleapis.com/v1beta', models: [ // Gemini 1.5 family (latest and most capable) 'gemini-1.5-pro-latest', 'gemini-1.5-pro', 'gemini-1.5-pro-002', 'gemini-1.5-flash-latest', 'gemini-1.5-flash', 'gemini-1.5-flash-002', 'gemini-1.5-flash-8b-latest', 'gemini-1.5-flash-8b', // Gemini 1.0 family 'gemini-1.0-pro-latest', 'gemini-1.0-pro', 'gemini-1.0-pro-001', // Legacy models (for compatibility) 'gemini-pro', 'gemini-pro-vision' ], defaultModel: 'gemini-1.5-flash-latest', keyLabel: 'Google AI API Key' }, openrouter: { name: 'OpenRouter (Multi-Provider)', baseURL: 'https://openrouter.ai/api/v1', models: [ // OpenAI models via OpenRouter 'openai/gpt-4o', 'openai/gpt-4o-mini', 'openai/gpt-4-turbo', 'openai/gpt-4', 'openai/gpt-3.5-turbo', 'openai/o1-preview', 'openai/o1-mini', // Anthropic models via OpenRouter 'anthropic/claude-3-5-sonnet', 'anthropic/claude-3-5-haiku', 'anthropic/claude-3-opus', 'anthropic/claude-3-sonnet', 'anthropic/claude-3-haiku', // Google models via OpenRouter 'google/gemini-1.5-pro-latest', 'google/gemini-1.5-flash-latest', 'google/gemini-pro', // Meta Llama models 'meta-llama/llama-3.3-70b-instruct', 'meta-llama/llama-3.2-90b-instruct', 'meta-llama/llama-3.1-405b-instruct', 'meta-llama/llama-3.1-70b-instruct', 'meta-llama/llama-3.1-8b-instruct', 'meta-llama/llama-3-70b-instruct', 'meta-llama/llama-3-8b-instruct', // Mistral models 'mistralai/mistral-large', 'mistralai/mistral-small', 'mistralai/codestral', 'mistralai/mistral-7b-instruct', 'mistralai/mixtral-8x7b-instruct', 'mistralai/mixtral-8x22b-instruct', // DeepSeek models 'deepseek/deepseek-r1', 'deepseek/deepseek-v3', 'deepseek/deepseek-coder', // Qwen models 'qwen/qwen-2.5-72b-instruct', 'qwen/qwen-2.5-7b-instruct', // Other popular models 'perplexity/llama-3.1-sonar-large-128k-online', 'nvidia/llama-3.1-nemotron-70b-instruct' ], defaultModel: 'openai/gpt-4o-mini', keyLabel: 'OpenRouter API Key' }, ollama: { name: 'Ollama (Local)', baseURL: 'http://localhost:11434', models: [ // Popular Llama models 'llama3.2', 'llama3.1', 'llama3', 'llama2', // Mistral family 'mistral', 'mixtral', 'mistral-nemo', // Code-specific models 'codellama', 'deepseek-coder', 'starcoder2', // Lightweight models 'phi3', 'phi3.5', 'gemma2', 'qwen2.5', // Specialized models 'nomic-embed-text', 'all-minilm', 'custom' ], defaultModel: 'llama3.2', keyLabel: 'Ollama Base URL' } }; function ensureConfigDir() { if (!fs.existsSync(CONFIG_DIR)) { fs.mkdirSync(CONFIG_DIR, { recursive: true }); } } function loadConfig() { ensureConfigDir(); if (fs.existsSync(CONFIG_FILE)) { try { const configData = fs.readFileSync(CONFIG_FILE, 'utf-8'); return JSON.parse(configData); } catch (error) { console.log(chalk.yellow('Warning: Could not load config file, using defaults.')); return {}; } } return {}; } function saveConfig(config) { ensureConfigDir(); try { // Ensure sensitive data is stored with user-only permissions (0600) fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), { encoding: 'utf-8', mode: 0o600 }); // In case file existed previously with broader permissions fs.chmodSync(CONFIG_FILE, 0o600); return true; } catch (error) { console.log(chalk.red(`❌ Error saving config: ${error.message}`)); return false; } } function getDefaultConfig() { return { apiKey: null, model: 'openai/gpt-4o-mini', provider: 'openrouter' }; } // New banner rendering for Gemini-style CLI function renderAnsiShadowFiglet(text) { // Render figlet text in ANSI Shadow font, white with black shadow const figletText = figlet.textSync(text, { font: 'ANSI Shadow' }); // ANSI Shadow already includes shadow, so just color the text white return chalk.white(figletText); } // Enhanced banner with responsive design function displayBanner(interactive = false) { const title = interactive ? 'KleoSr PE2-CLI - Interactive Mode' : 'KleoSr PE2-CLI'; setTerminalTitle(title); clearConsole(); const terminalWidth = process.stdout.columns || 80; const isNarrow = terminalWidth < 60; if (isNarrow) { // Compact banner for narrow terminals console.log(themeManager.color('primary')('\n PE²-CLI')); console.log(themeManager.color('secondary')(' ⚡ Prompt Engineering 2.0')); } else { // Full banner for wider terminals console.log(renderAnsiShadowFiglet('KLEOSR PE2')); } console.log(); // Use user preferences for status bar display const useCompact = userPreferences.shouldUseCompactMode() || isNarrow; const showBorder = userPreferences.shouldShowBorders() && !isNarrow; displayStatusBar(loadConfig(), { compact: useCompact, showBorder }); console.log(); // Responsive command display if (isNarrow) { console.log(themeManager.color('info')('Quick commands: /help /settings /config')); } else { console.log(themeManager.color('info')('Essential Commands:')); console.log(themeManager.color('muted')(' /settings Configure API provider, model, and key')); console.log(themeManager.color('muted')(' /config View current settings')); console.log(themeManager.color('muted')(' /help Show all commands')); } console.log(); console.log(themeManager.color('text')('Type your prompt or use a command to begin.')); console.log(); } function displayInteractiveBanner() { displayBanner(true); } const DIFFICULTY_INDICATORS = { "NOVICE": "🟢", "INTERMEDIATE": "🟡", "ADVANCED": "🟠", "EXPERT": "🔴", "MASTER": "🟣" }; // --- Optimized PE2 Templates - Reduced Complexity --- const OPTIMIZED_INITIAL_TEMPLATE = (rawPrompt) => ` You are an expert prompt engineer specializing in PE² (Prompt Engineering 2.0) optimization. Transform the following raw prompt into a structured PE² format with exactly these 5 sections: Raw prompt to optimize: --- ${rawPrompt} --- Return ONLY a valid JSON object with these exact keys: { "context": "Comprehensive problem description and scope", "role": "Expert persona for the LLM to adopt", "task": "Step-by-step breakdown of required actions", "constraints": "Rules, limitations, and boundaries", "output": "Expected format and structure of the response" } CRITICAL: Return ONLY the JSON object. No explanations or additional text. `; const OPTIMIZED_REFINEMENT_TEMPLATE = (currentPromptJson, iterationNum) => ` You are refining a PE² prompt for iteration ${iterationNum}. Current PE² Prompt: ${currentPromptJson} Analyze and improve the prompt by: 1. Enhancing clarity and specificity 2. Ensuring all PE² sections are well-balanced 3. Optimizing for better LLM performance 4. Maintaining logical consistency 5. Improving actionability Return ONLY the improved JSON object with the same 5 sections. `; // Simplified template selection const getInitialTemplate = (rawPrompt) => OPTIMIZED_INITIAL_TEMPLATE(rawPrompt); const getRefinementTemplate = (promptJson, iteration) => OPTIMIZED_REFINEMENT_TEMPLATE(promptJson, iteration); // Optimized complexity analysis with reduced computational overhead function analyzePromptComplexity(rawPrompt) { // Pre-compute commonly used values const length = rawPrompt.length; const words = rawPrompt.split(/\s+/).length; const promptLower = rawPrompt.toLowerCase(); let score = 0; // 1. Optimized length analysis (single calculation) score += words > 400 ? 4 : words > 250 ? 3 : words > 120 ? 2 : words > 60 ? 1 : 0; // 2. Optimized keyword matching (reduced keyword set for performance) const techIndicators = ['api', 'algorithm', 'framework', 'database', 'ml', 'ai', 'docker', 'python', 'javascript']; const domainIndicators = ['compliance', 'strategy', 'analytics', 'finance', 'healthcare', 'enterprise']; score += Math.min(techIndicators.filter(kw => promptLower.includes(kw)).length, 4); score += Math.min(domainIndicators.filter(kw => promptLower.includes(kw)).length, 3); // 3. Simplified structural analysis (single regex pass) const structuralPattern = /(\n\s*\d+\.|\n\s*\-|```|#)/g; const structuralMatches = (rawPrompt.match(structuralPattern) || []).length; score += Math.min(structuralMatches, 4); // 4. Optimized logic word detection (reduced set) const logicPattern = /\b(if|then|when|unless|until|depending|while)\b/g; const logicMatches = (promptLower.match(logicPattern) || []).length; score += Math.min(logicMatches, 3); // 5. Fast special character counting const specialChars = (rawPrompt.match(/[;\{\[]/g) || []).length; score += specialChars >= 5 ? 2 : specialChars >= 2 ? 1 : 0; // Optimized difficulty mapping (lookup table approach) const difficultyMap = [ { max: 4, level: "NOVICE", iterations: 1 }, { max: 8, level: "INTERMEDIATE", iterations: 2 }, { max: 12, level: "ADVANCED", iterations: 3 }, { max: 16, level: "EXPERT", iterations: 4 }, { max: Infinity, level: "MASTER", iterations: 5 } ]; const result = difficultyMap.find(d => score <= d.max); return { difficulty: result.level, iterations: result.iterations, score }; } function displayComplexityAnalysis(difficulty, iterations, score, rawPrompt) { const indicator = DIFFICULTY_INDICATORS[difficulty]; const terminalWidth = Math.min(process.stdout.columns || 80, 100); const separatorLength = Math.min(50, terminalWidth - 10); console.log(themeManager.color('info')('\n🔍 PROMPT COMPLEXITY ANALYSIS')); console.log(themeManager.color('muted')('─'.repeat(separatorLength))); console.log(themeManager.color('text')(`📊 Complexity Score: ${score}/20`)); console.log(themeManager.color('text')(`🎚️ Difficulty Level: ${indicator} ${difficulty}`)); console.log(themeManager.color('text')(`🔄 Recommended Iterations: ${iterations}`)); console.log(themeManager.color('text')(`📝 Word Count: ${rawPrompt.split(/\s+/).length} words`)); console.log(themeManager.color('muted')('─'.repeat(separatorLength))); const explanations = { "NOVICE": "Simple, straightforward request with clear objectives", "INTERMEDIATE": "Moderate complexity with some technical requirements", "ADVANCED": "Complex task requiring domain expertise and multiple steps", "EXPERT": "Highly technical with intricate requirements and constraints", "MASTER": "Extremely complex, multi-domain, enterprise-level requirements" }; // Wrap explanation text for better readability const explanation = explanations[difficulty]; const maxExplanationWidth = terminalWidth - 15; const wrappedExplanation = explanation.length > maxExplanationWidth ? explanation.substring(0, maxExplanationWidth - 3) + '...' : explanation; console.log(themeManager.color('warning')(`💡 Analysis: ${wrappedExplanation}`)); console.log(); } function getOpenRouterClient(apiKey) { return createOpenAIClient(apiKey, 'https://openrouter.ai/api/v1'); } function getProviderClient(provider, apiKey) { switch (provider) { case 'openai': return createOpenAIClient(apiKey, PROVIDERS.openai.baseURL); case 'openrouter': return createOpenRouterClient({ apiKey, baseURL: PROVIDERS.openrouter.baseURL }); case 'anthropic': return createAnthropicClient(apiKey); case 'google': return createGoogleClient(apiKey); case 'ollama': // For Ollama the apiKey parameter is actually the base URL; fall back to default if empty return createOllamaClient(apiKey || PROVIDERS.ollama.baseURL); default: throw new Error(`Unsupported provider: ${provider}`); } } async function promptForConfig(rl) { console.log(chalk.hex('#FFD93D')('\n🔧 Configuration Setup')); console.log(chalk.hex('#B19CD9')('Let\'s configure your AI provider and API settings.\n')); try { // Provider selection const { provider } = await inquirer.prompt([ { type: 'list', name: 'provider', message: 'Select your AI provider:', choices: [ { name: `${PROVIDERS.openai.name} - Direct OpenAI API`, value: 'openai' }, { name: `${PROVIDERS.anthropic.name} - Direct Anthropic API`, value: 'anthropic' }, { name: `${PROVIDERS.google.name} - Direct Google AI API`, value: 'google' }, { name: `${PROVIDERS.openrouter.name} - Access multiple providers`, value: 'openrouter' }, { name: `${PROVIDERS.ollama.name} - Local Ollama`, value: 'ollama' } ], default: 'openrouter' } ]); const providerConfig = PROVIDERS[provider]; // === Provider-specific connection details === let apiKey = ''; if (provider === 'ollama') { const { baseURL } = await inquirer.prompt([ { type: 'input', name: 'baseURL', message: 'Enter your Ollama base URL (press Enter for default):', default: providerConfig.baseURL } ]); apiKey = baseURL.trim(); } else { const resp = await inquirer.prompt([ { type: 'password', name: 'apiKey', message: `Enter your ${providerConfig.keyLabel}:`, mask: '*', validate: (input) => { if (!input.trim()) { return 'API key is required'; } return true; } } ]); apiKey = resp.apiKey.trim(); } // Model selection const { model } = await inquirer.prompt([ { type: 'list', name: 'model', message: 'Select a model:', choices: [ ...providerConfig.models.map(model => ({ name: model === providerConfig.defaultModel ? `${model} (recommended)` : model, value: model })), { name: '📝 Enter Custom Model', value: 'custom' } ], default: providerConfig.defaultModel } ]); let finalModel = model; if (model === 'custom') { const { customModel } = await inquirer.prompt([ { type: 'input', name: 'customModel', message: 'Enter custom model name:', validate: (input) => { if (!input.trim()) { return 'Model name is required'; } return true; } } ]); finalModel = customModel.trim(); } const config = { provider, apiKey, model: finalModel }; if (saveConfig(config)) { console.log(chalk.hex('#50E3C2')(`\n✅ Configuration saved!`)); console.log(chalk.hex('#B19CD9')(`🌐 Provider: ${providerConfig.name}`)); console.log(chalk.hex('#B19CD9')(`📝 Model: ${config.model}`)); console.log(chalk.hex('#B19CD9')(`🔑 API Key: ${config.apiKey.substring(0, 8)}...`)); console.log(chalk.hex('#B19CD9')(`📁 Config saved to: ${CONFIG_FILE}\n`)); return config; } else { console.log(chalk.red('❌ Failed to save configuration.')); return null; } } catch (error) { if (error.isTtyError) { console.log(chalk.red('❌ Interactive prompts are not supported in this environment.')); console.log(chalk.yellow('Please run this in a proper terminal.')); } else { console.log(chalk.red(`❌ Configuration error: ${error.message}`)); } return null; } } // Optimized prompt generation with improved efficiency async function generateInitialPrompt(client, rawPrompt, model) { try { const response = await client.chat.completions.create({ model: model, messages: [{ role: "system", content: getInitialTemplate(rawPrompt) }], max_tokens: 1024, // Reduced for efficiency temperature: 0.3, // Lower temperature for consistency }); const content = response.choices[0].message.content; // Debug logging if (process.env.DEBUG) { console.log(chalk.gray('Raw response content:')); console.log(chalk.gray(content.substring(0, 500) + '...')); } // Clean up the content to extract valid JSON try { // Try to parse as-is first return { prompt: JSON.parse(content), edits: "Initial prompt generation." }; } catch (jsonError) { // If that fails, try to extract JSON from the content // Robust brace extraction: take substring from first '{' to matching last '}' const firstBrace = content.indexOf('{'); const lastBrace = content.lastIndexOf('}'); if (firstBrace !== -1 && lastBrace !== -1 && lastBrace > firstBrace) { try { let jsonStr = content.slice(firstBrace, lastBrace + 1); jsonStr = jsonStr.replace(/,(\s*[}\]])/g, '$1'); const parsed = JSON.parse(jsonStr); // Validate that all required fields are present const requiredFields = ['context', 'role', 'task', 'constraints', 'output']; const hasAllFields = requiredFields.every(field => parsed.hasOwnProperty(field)); if (hasAllFields) { return { prompt: parsed, edits: "Initial prompt generation." }; } else { // If fields are missing, try to construct them const validPrompt = { context: parsed.context || "No context provided", role: parsed.role || "Expert assistant", task: parsed.task || "Complete the requested task", constraints: parsed.constraints || "Follow best practices", output: parsed.output || "Provide appropriate output" }; return { prompt: validPrompt, edits: "Initial prompt generation with field validation." }; } } catch (parseError) { console.log(chalk.yellow(`Warning: JSON extraction failed: ${parseError.message}`)); } } // If all else fails, try to extract individual fields const extractField = (fieldName) => { const patterns = [ new RegExp(`"${fieldName}"\\s*:\\s*"([^"]*)"`, 'i'), new RegExp(`'${fieldName}'\\s*:\\s*'([^']*)'`, 'i'), new RegExp(`${fieldName}\\s*:\\s*"([^"]*)"`, 'i'), new RegExp(`\\*\\*${fieldName}\\*\\*:?\\s*([^\\n\\*]+)`, 'i'), new RegExp(`${fieldName}:?\\s*([^\\n]+)`, 'i') ]; for (const pattern of patterns) { const match = content.match(pattern); if (match && match[1]) { return match[1].trim(); } } return null; }; // Try to construct prompt from extracted fields const context = extractField('context'); const role = extractField('role'); const task = extractField('task'); const constraints = extractField('constraints'); const output = extractField('output'); if (context || role || task) { return { prompt: { context: context || "Context based on: " + rawPrompt.substring(0, 200) + "...", role: role || "Expert assistant specialized in the given domain", task: task || "Complete the task as described in the user's prompt", constraints: constraints || "Ensure accuracy, clarity, and adherence to best practices", output: output || "Deliver a comprehensive and well-structured response" }, edits: "Initial prompt generation with field extraction fallback." }; } // Ultimate fallback - create a basic structure return { prompt: { context: `The user wants to: ${rawPrompt.substring(0, 500)}${rawPrompt.length > 500 ? '...' : ''}`, role: "Expert assistant with deep knowledge in the relevant domain", task: "1. Understand the user's requirements\n2. Provide a comprehensive solution\n3. Ensure clarity and completeness", constraints: "- Be accurate and thorough\n- Follow best practices\n- Provide clear explanations", output: "A well-structured response that fully addresses the user's needs" }, edits: "Initial prompt generation with automatic structuring." }; } } catch (error) { console.log(chalk.red(`❌ Error during initial prompt generation: ${error.message}`)); return { prompt: null, edits: null }; } } // Optimized refinement with caching and improved efficiency async function refinePrompt(client, currentPromptJson, refinementHistory, model, iterationNum) { // Check cache first const cachedResult = getCachedResult(currentPromptJson, iterationNum); if (cachedResult) { cachedResult.hits++; return cachedResult.result; } try { const systemPrompt = getRefinementTemplate( currentPromptJson, iterationNum ); const response = await client.chat.completions.create({ model: model, messages: [ { role: "system", content: systemPrompt, }, ], headers: { "HTTP-Referer": "https://pe2-cli-tool.local", "X-Title": "KleoSr PE2-CLI Tool", }, }); const content = response.choices[0].message.content; try { // Try to parse as-is first const refinedPromptJson = JSON.parse(content); const editsSummary = `Refined prompt based on PE2 principles (Iteration ${iterationNum}).`; return { prompt: refinedPromptJson, edits: editsSummary }; } catch (jsonError) { // If that fails, try to extract JSON from the content // Robust brace extraction: take substring from first '{' to matching last '}' const firstBrace = content.indexOf('{'); const lastBrace = content.lastIndexOf('}'); if (firstBrace !== -1 && lastBrace !== -1 && lastBrace > firstBrace) { try { let jsonStr = content.slice(firstBrace, lastBrace + 1); jsonStr = jsonStr.replace(/,(\s*[}\]])/g, '$1'); const parsed = JSON.parse(jsonStr); // Validate that all required fields are present const requiredFields = ['context', 'role', 'task', 'constraints', 'output']; const hasAllFields = requiredFields.every(field => parsed.hasOwnProperty(field)); if (hasAllFields) { const result = { prompt: parsed, edits: "Refined prompt generation." }; // Cache successful results setCachedResult(currentPromptJson, iterationNum, result); return result; } else { // If fields are missing, try to construct them const validPrompt = { context: parsed.context || "No context provided", role: parsed.role || "Expert assistant", task: parsed.task || "Complete the requested task", constraints: parsed.constraints || "Follow best practices", output: parsed.output || "Provide appropriate output" }; const result = { prompt: validPrompt, edits: "Refined prompt generation with field validation." }; setCachedResult(currentPromptJson, iterationNum, result); return result; } } catch (parseError) { if (process.env.DEBUG) { console.log(chalk.yellow(`Warning: JSON extraction failed: ${parseError.message}`)); } } } // If no valid JSON, return null if (process.env.DEBUG) { console.log(chalk.yellow(`Warning: Could not parse refinement JSON, keeping current version`)); } return { prompt: null, edits: null }; } } catch (error) { if (process.env.DEBUG) { console.log(chalk.red(`❌ Error during prompt refinement: ${error.message}`)); } return { prompt: null, edits: null }; } } function formatMarkdownOutput(pe2Prompt, history, metrics, difficulty, complexityScore) { const indicator = DIFFICULTY_INDICATORS[difficulty]; const markdown = ` # PE²-Optimized Prompt ${indicator} **Difficulty Level:** ${difficulty} | **Complexity Score:** ${complexityScore}/20 | **Generated:** ${new Date().toLocaleString()} ## Agentic Analysis - **Domain Focus:** ${metrics.domain_focus || 'general'} - **Adaptive Features:** ${metrics.adaptive_features || 'standard'} - **Overall Quality:** ${metrics.overall_quality || 'N/A'} ### Quality Scores ${metrics.quality_scores ? Object.entries(metrics.quality_scores) .map(([key, value]) => `- **${key.charAt(0).toUpperCase() + key.slice(1)}:** ${value.toFixed(1)}/10`) .join('\n') : '- Not available'} ## Context ${pe2Prompt.context || 'N/A'} ## Role ${pe2Prompt.role || 'N/A'} ## Task ${pe2Prompt.task || 'N/A'} ## Constraints ${pe2Prompt.constraints || 'N/A'} ## Output ${pe2Prompt.output || 'N/A'} --- # Refinement History ${history.map(item => `### Iteration ${item.iteration}\n- ${item.edits}\n`).join('\n')} --- # Performance Metrics - **Estimated Accuracy Gain**: ${metrics.accuracy_gain || 'N/A'} - **Complexity Analysis**: ${difficulty} level prompt with ${complexityScore}/20 complexity score - **Optimization Level**: ${history.length} iterations applied - **Generated by**: KleoSr PE²-CLI v3.1 (Agentic Edition) --- *Generated with ❤️ by KleoSr PE²-CLI - Adaptive Intelligence for Prompt Engineering* `; return markdown; } // Improved API key display function function formatApiKeyDisplay(apiKey, showFullKey = false) { if (!apiKey) return 'Not set'; if (showFullKey) return apiKey; const keyLength = apiKey.length; if (keyLength <= 12) { return apiKey.substring(0, 4) + '•'.repeat(Math.max(4, keyLength - 8)) + apiKey.substring(keyLength - 4); } return apiKey.substring(0, 8) + '•'.repeat(8) + apiKey.substring(keyLength - 4); } // Improved content preview function function formatContentPreview(content, maxLength = 200, showFullLength = false) { if (!content) return ''; if (content.length <= maxLength || showFullLength) { return content; } const truncated = content.substring(0, maxLength); const lastSpace = truncated.lastIndexOf(' '); const cleanTruncated = lastSpace > maxLength * 0.7 ? truncated.substring(0, lastSpace) : truncated; return `${cleanTruncated}... [${content.length - cleanTruncated.length} more characters]`; } // Improved prompt processing display function formatProcessingPromptDisplay(prompt, maxLength = 100) { if (!prompt) return ''; if (prompt.length <= maxLength) { return prompt; } const truncated = prompt.substring(0, maxLength); const lastSpace = truncated.lastIndexOf(' '); const cleanTruncated = lastSpace > maxLength * 0.7 ? truncated.substring(0, lastSpace) : truncated; return `${cleanTruncated}... [${prompt.length} chars total]`; } async function handleCommand(command, rl, config) { switch (command) { case '/settings': setTerminalTitle('KleoSr PE2-CLI - Settings Configuration'); config = await promptForConfig(rl); setTerminalTitle('KleoSr PE2-CLI - Interactive Mode'); return config; case '/config': setTerminalTitle('KleoSr PE2-CLI - Current Configuration'); console.log('\n' + themeManager.color('info')('Current Configuration:')); const configTerminalWidth = process.stdout.columns || 80; const useMinimalConfig = configTerminalWidth < 70; const configTable = createTable( ['Setting', 'Value'], [ ['Provider', config.provider || 'Not set'], ['Model', config.model || 'Not set'], ['API Key', formatApiKeyDisplay(config.apiKey)], ['Theme', themeManager.currentTheme] ], { minimal: useMinimalConfig, compact: true } ); console.log(configTable); // Add contextual tips if (config.apiKey) { console.log(themeManager.color('muted')('\n💡 Tips:')); console.log(themeManager.color('muted')(' • Use /showkey to reveal full API key')); console.log(themeManager.color('muted')(' • Use /model to quickly switch models')); console.log(themeManager.color('muted')(' • Use /theme to toggle light/dark mode')); } else { console.log(themeManager.color('warning')('\n⚠️ No API key configured. Use /settings to configure.')); } setTerminalTitle('KleoSr PE2-CLI - Interactive Mode'); break; case '/model': // Clear any potential lingering output process.stdout.write('\r\x1b[K'); setTerminalTitle('KleoSr PE2-CLI - Model Selection'); const { quickModel } = await inquirer.prompt([ { type: 'input', name: 'quickModel', message: 'Enter model name (or press Enter to select from list):', } ]); if (quickModel.trim()) { config.model = quickModel.trim(); saveConfig(config); console.log(themeManager.color('success')(`✓ Model changed to: ${config.model}`)); } else { // Show model selection const providerConfig = PROVIDERS[config.provider]; if (providerConfig) { const { selectedModel } = await inquirer.prompt([ { type: 'list', name: 'selectedModel', message: 'Select a model:', choices: [ ...providerConfig.models.map(m => ({ name: m, value: m })), { name: '📝 Enter Custom Model', value: 'custom' } ] } ]); if (selectedModel === 'custom') { const { customModel } = await inquirer.prompt([ { type: 'input', name: 'customModel', message: 'Enter custom model name:', validate: (input) => input.trim() ? true : 'Model name required' } ]); config.model = customModel.trim(); } else { config.model = selectedModel; } saveConfig(config); console.log(themeManager.color('success')(`✓ Model changed to: ${config.model}`)); } } setTerminalTitle('KleoSr PE2-CLI - Interactive Mode'); return config; case '/showkey': if (config.apiKey) { console.log('\n' + themeManager.color('warning')('⚠️ Full API Key:')); console.log(themeManager.color('text')(config.apiKey)); console.log(themeManager.color('muted')('(Keep this secure and don\'t share it)')); } else { console.log(themeManager.color('warning')('No API key configured.')); } break; case '/clear': clearConsole(); displayInteractiveBanner(); break; case '/history': const sessions = sessionManager.loadHistory(); if (sessions.length === 0) { console.log(themeManager.color('warning')('No history found.')); } else { console.log('\n' + themeManager.color('info')('Recent Sessions:')); sessions.slice(0, 5).forEach((session, idx) => { console.log(`\n${idx + 1}. Session ${session.id} - ${new Date(session.timestamp).toLocaleString()}`); console.log(` Prompts: ${session.prompts.length}`); }); } break; case '/export': const exportPath = path.join(process.cwd(), `pe2-export-${Date.now()}.json`); fs.writeFileSync(exportPath, JSON.stringify(sessionManager.currentSession, null, 2)); console.log(themeManager.color('success')(`✓ Session exported to: ${exportPath}`)); break; case '/import': const { importPath } = await inquirer.prompt([ { type: 'input', name: 'importPath', message: 'Enter file path to import:', validate: (input) => { if (!input.trim()) return 'Path required'; if (!fs.existsSync(input)) return 'File not found'; return true; } } ]); try { const content = fs.readFileSync(importPath, 'utf-8'); console.log(themeManager.color('success')('✓ File imported successfully')); console.log(themeManager.color('info')(`📄 Content preview: ${formatContentPreview(content, 300)}`)); return content; } catch (error) { console.log(themeManager.color('error')(`✗ Import failed: ${error.message}`)); } break; case '/theme': const newTheme = themeManager.currentTheme === 'dark' ? 'light' : 'dark'; themeManager.setTheme(newTheme); userPreferences.set('theme', newTheme); console.log(themeManager.color('success')(`✓ Theme changed to: ${newTheme}`)); return config; case '/preferences': case '/prefs': console.log('\n' + themeManager.color('info')('User Preferences:')); const prefsTerminalWidth = process.stdout.columns || 80; const useMinimalPrefs = prefsTerminalWidth < 70; const prefsTable = createTable( ['Setting', 'Value'], [ ['Theme', userPreferences.get('theme')], ['Compact Mode', userPreferences.get('compactMode') ? 'Yes' : 'No'], ['Show Borders', userPreferences.get('showBorders') ? 'Yes' : 'No'], ['Auto Save', userPreferences.get('autoSave') ? 'Yes' : 'No'], ['Max History', userPreferences.get('maxHistoryItems')], ['Default Provider', userPreferences.get('defaultProvider')] ], { minimal: useMinimalPrefs, compact: true } ); console.log(prefsTable); console.log(themeManager.color('muted')('\n💡 Tips:')); console.log(themeManager.color('muted')(' • Use /theme to toggle theme')); console.log(themeManager.color('muted')(' • Use /compact to toggle compact mode')); console.log(themeManager.color('muted')(' • Preferences auto-save when changed')); break; case '/compact': const currentCompact = userPreferences.get('compactMode'); userPreferences.set('compactMode', !currentCompact); console.log(themeManager.color('success')(`✓ Compact mode ${!currentCompact ? 'enabled' : 'disabled'}`)); // Refresh UI to show changes clearConsole(); displayInteractiveBanner(); break; case '/batch': const { batchPath } = await inquirer.prompt([ { type: 'input', name: 'batchPath', message: 'Enter file path containing prompts (one per line):', validate: (input) => { if (!input.trim()) return 'Path required'; if (!fs.existsSync(input)) return 'File not found'; return true; } } ]); try { const prompts = fs.readFileSync(batchPath, 'utf-8').split('\n').filter(p => p.trim()); console.log(themeManager.color('info')(`Found ${prompts.length} prompts to process.`)); // Show preview of prompts if (prompts.length > 0) { console.log(themeManager.color('muted')('\n📝 Prompt previews:')); prompts.slice(0, 3).forEach((prompt, idx) => { console.log(` ${idx + 1}. ${formatProcessingPromptDisplay(prompt, 80)}`); }); if (prompts.length > 3) { console.log(` ... and ${prompts.length - 3} more prompts`); } } return { batch: prompts }; } catch (error) { console.log(themeManager.color('error')(`✗ Batch load failed: ${error.message}`)); } break; case '/copy': if (lastResult) { await copyToClipboard(lastResult); } else { console.log(themeManager.color('warning')('No result to copy.')); } break; case '/clearall': if (fs.existsSync(PROMPTS_DIR)) { fs.readdirSync(PROMPTS_DIR).forEach(f => { fs.unlinkSync(path.join(PROMPTS_DIR, f)); }); console.log(themeManager.color('success')('✓ All saved prompts cleared.')); } else { console.log(themeManager.color('warning')('No prompts folder to clear.')); } break; case '/help': default: console.log('\n' + themeManager.color('info')('Available Commands:')); const helpTerminalWidth = process.stdout.columns || 80; const useMinimalHelp = helpTerminalWidth < 70; const helpTable = createTable( ['Command', 'Description'], Object.entries(COMMANDS).map(([cmd, desc]) => [ themeManager.color('primary')(cmd), useMinimalHelp && desc.length > 30 ? desc.substring(0, 27) + '...' : desc ]), { minimal: useMinimalHelp, compact: true } ); console.log(helpTable); console.log(themeManager.color('muted')('\n💡 Quick tips:')); console.log(themeManager.color('muted')(' • Type any prompt to start optimization')); console.log(themeManager.color('muted')(' • Use /exit or /quit to leave')); console.log(themeManager.color('muted')(' • Results are auto-saved to pe2-prompts/')); break; } return config; } async function interactiveMode(initialInput = null, cliOptions = {}) { displayInteractiveBanner(); const rl = readline.createInterface({ input: process.stdin, output: process.stdout, prompt: chalk.hex('#4A90E2')('> ') }); // Load or create configuration let config = { ...getDefaultConfig(), ...loadConfig() }; if (!config.apiKey) { console.log(themeManager.color('warning')('⚠️ First time setup required.')); console.log(themeManager.color('muted')('Please configure your API provider and key to continue.\n')); config = await promptForConfig(rl); if (!config || !config.apiKey) { console.log(themeManager.color('error')('\n✗ Configuration cancelled or incomplete.')); rl.close(); return; } } // Initialize client with error handling let client; try { const apiKeyInitial = resolveApiKey(config.provider, config.apiKey); client = config.provider ? getProviderClient(config.provider, apiKeyInitial) : getOpenRouterClient(apiKeyInitial); } catch (error) { console.log(themeManager.color('error')(`\n✗ Failed to initialize API client: ${error.message}`)); console.log(themeManager.color('muted')('Please check your configuration with /settings')); rl.close(); return; } let sessionCounter = 1; // If a prompt was supplied on the command line, handle it immediately. if (initialInput) { let rawPrompt = initialInput; let inputSource = 'direct text'; // Detect file vs direct text (reuse earlier logic) if (!cliOptions.text && !cliOptions.file && fs.existsSync(initialInput)) { rawPrompt = fs.readFileSync(initialInput, 'utf-8').trim(); inputSource = `file: ${initialInput}`; } else if (cliOptions.file) { if (fs.existsSync(initialInput)) { rawPrompt = fs.readFileSync(initialInput, 'utf-8').trim(); inputSource = `file: ${initialInput}`; } else { console.log(themeManager.color('error')(`❌ Error: File not found at ${init