@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
JavaScript
#!/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