@kleosr/pe2-cli
Version:
🚀 KleoSr PE2-CLI: Convert raw prompts to PE2-optimized prompts using adaptive intelligence
949 lines (824 loc) • 43.1 kB
JavaScript
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';
import { SessionManager, createProgressBar, displayStatusBar, COMMANDS, validatePrompt, copyToClipboard, createTable, ThemeManager, StatsTracker, UserPreferences, validateAndSuggestCommand } from './utils.js';
import { PROVIDERS, getProviderClient } from './providers/index.js';
import { CONFIG_DIR, CONFIG_FILE, getDefaultConfig, loadConfig, saveConfig, resolveApiKey } from './config.js';
import { analyzePromptComplexity } from './analysis.js';
import { promptForConfig } from './configPrompt.js';
import { displayBanner, displayInteractiveBanner, formatApiKeyDisplay, formatContentPreview, formatProcessingPromptDisplay, displayComplexityAnalysis, setTerminalTitle } from './ui.js';
import { processPrompt as runEngine } from './engine.js';
import { PROMPT_LIMITS, DEFAULT_CONTEXT, DEFAULT_STRATEGY, DEFAULT_EVALUATION, UI_CONFIG, PROGRESS_PERCENTAGES, PERFORMANCE_METRICS } from './constants.js';
import { formatMarkdownOutput } from './templates.js';
import { setupGlobalErrorHandlers, handleError, CLIError, ValidationError, ConfigError, ProviderError } from './errorHandler.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const sessionManager = new SessionManager();
const themeManager = new ThemeManager();
const statsTracker = new StatsTracker();
const userPreferences = new UserPreferences();
setupGlobalErrorHandlers();
let lastResult = null;
let isProcessingPrompt = false;
const PROMPTS_DIR = path.join(process.cwd(), 'pe2-prompts');
const promptCache = new Map();
function getCacheKey(prompt, iterations) {
return (prompt.substring(0, PROMPT_LIMITS.cacheKeyPrefixLength) + '_' + iterations)
.replace(/[^a-zA-Z0-9_]/g, '')
.substring(0, PROMPT_LIMITS.cacheKeyMaxLength);
}
function getCachedResult(prompt, iterations) {
return promptCache.get(getCacheKey(prompt, iterations));
}
function setCachedResult(prompt, iterations, result) {
const key = getCacheKey(prompt, iterations);
if (promptCache.size >= PROMPT_LIMITS.maxCacheSize) {
promptCache.delete(promptCache.keys().next().value);
}
promptCache.set(key, { result, timestamp: Date.now(), hits: 0 });
}
function getContext() {
return DEFAULT_CONTEXT;
}
function selectStrategy() {
return DEFAULT_STRATEGY;
}
async function evaluatePrompt() {
return DEFAULT_EVALUATION;
}
// promptForConfig is imported from configPrompt.js
// Optimized prompt generation with improved efficiency
async function generateInitialPrompt(client, rawPrompt, model) {
try {
const systemContent = 'You are a precise prompt optimizer. Follow the instructions and return JSON only.';
const userContent = getInitialTemplate(rawPrompt);
const response = await client.chat.completions.create({
model: model,
messages: buildMessages({ system: systemContent, user: userContent }),
max_tokens: 1024,
temperature: 0.3,
});
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 systemContent = 'You are a precise prompt optimizer. Return JSON only.';
const userContent = getRefinementTemplate(currentPromptJson, iterationNum);
const response = await client.chat.completions.create({
model: model,
messages: buildMessages({ system: systemContent, user: userContent }),
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 };
}
}
// formatMarkdownOutput is imported from templates.js
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 || UI_CONFIG.terminalWidth.default;
const useMinimalConfig = configTerminalWidth < UI_CONFIG.terminalWidth.compactThreshold;
const configTable = createTable(
['Setting', 'Value'],
[
['Provider', config.provider || 'Not set'],
['Model', config.model || 'Not set'],
[config.provider === 'ollama' ? 'Base URL' : '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'));
const previewLength = 300;
console.log(themeManager.color('info')(`📄 Content preview: ${formatContentPreview(content, previewLength)}`));
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 || UI_CONFIG.terminalWidth.default;
const useMinimalPrefs = prefsTerminalWidth < UI_CONFIG.terminalWidth.compactThreshold;
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 = {}) {
// Fast path: if input provided with --auto-difficulty, compute and exit without requiring config
if (initialInput && cliOptions.autoDifficulty) {
const rawPrompt = (() => {
try {
if (!cliOptions.text && !cliOptions.file && fs.existsSync(initialInput)) {
return fs.readFileSync(initialInput, 'utf-8').trim();
}
} catch {}
return initialInput;
})();
const { difficulty, iterations: recIter, score: compScore } = analyzePromptComplexity(rawPrompt);
displayBanner({ themeManager, userPreferences, config: loadConfig(), interactive: false });
console.log(themeManager.color('info')(`📝 Input: ${formatProcessingPromptDisplay(rawPrompt, PROMPT_LIMITS.processingDisplayMaxLength)}`));
displayComplexityAnalysis({ themeManager }, difficulty, recIter, compScore, rawPrompt);
return;
}
displayInteractiveBanner({ themeManager, userPreferences, config: loadConfig() });
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
prompt: chalk.hex('#4A90E2')('> ')
});
// Load or create configuration
let config = { ...getDefaultConfig(), ...loadConfig() };
// Apply CLI overrides for provider/model
if (cliOptions.provider) {
config.provider = cliOptions.provider;
}
if (cliOptions.model) {
config.model = cliOptions.model;
}
// Attempt to resolve API key from env if not set
let resolvedApiKey = resolveApiKey(config.provider, config.apiKey);
if (!resolvedApiKey && config.provider === 'ollama') {
resolvedApiKey = PROVIDERS.ollama.baseURL;
}
if (resolvedApiKey) {
config.apiKey = resolvedApiKey;
}
if (!config.apiKey) {
// If one-shot and not interactive, do not prompt; fail fast
if (initialInput && !cliOptions.interactive) {
console.log(themeManager.color('error')('✗ No API key configured. Set the proper environment variable or run with --config.'));
rl.close();
return;
}
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);
if (!apiKeyInitial && config.provider !== 'ollama') {
throw new ConfigError('API key is required. Please configure with /settings or set environment variable.');
}
client = getProviderClient(config.provider || 'openrouter', apiKeyInitial);
} catch (error) {
if (error instanceof ConfigError) {
console.log(themeManager.color('error')(`\n✗ ${error.message}`));
} else {
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 ${initialInput}`));
}
}
if (rawPrompt) {
console.log(themeManager.color('info')(`📝 Initial input: ${inputSource}`));
const { difficulty, iterations: recIter, score: compScore } = analyzePromptComplexity(rawPrompt);
displayComplexityAnalysis(difficulty, recIter, compScore, rawPrompt);
// One-shot behavior when not explicitly interactive
if (cliOptions.autoDifficulty) {
rl.close();
return;
}
if (!cliOptions.interactive) {
await processPrompt(rawPrompt, client, { ...config, _cliOptions: cliOptions }, sessionCounter++);
rl.close();
return;
}
// Fall back to interactive if flag is set
await processPrompt(rawPrompt, client, { ...config, _cliOptions: cliOptions }, sessionCounter++);
}
}
rl.prompt();
rl.on('line', async (input) => {
const trimmedInput = input.trim();
// Handle empty input
if (trimmedInput === '') {
rl.prompt();
return;
}
// Handle commands
if (trimmedInput.startsWith('/')) {
const command = trimmedInput.toLowerCase().split(' ')[0];
// Validate command and provide suggestions
const validation = validateAndSuggestCommand(command);
if (!validation.valid && validation.isCommand) {
console.log(themeManager.color('error')(`✗ ${validation.message}`));
rl.prompt();
return;
}
// Track command usage for personalized suggestions
if (validation.valid) {
userPreferences.trackCommand(command);
}
// Special handling for exit
if (command === '/exit' || command === '/quit') {
console.log(themeManager.color('success')('\n✨ Thanks for using KleoSr PE2-CLI! Goodbye!'));
rl.close();
return;
}
// Clear any potential progress bar remnants
process.stdout.write('\r\x1b[K');
// Handle other commands
const result = await handleCommand(command, rl, config);
if (result && typeof result === 'object') {
// Configuration was updated
config = { ...config, ...result };
if (result.provider || result.apiKey) {
const newApiKey = resolveApiKey(config.provider, config.apiKey);
const newClient = config.provider ? getProviderClient(config.provider, newApiKey) : getOpenRouterClient(newApiKey);
client = newClient;
}
// Refresh UI banner with updated status line
displayInteractiveBanner({ themeManager, userPreferences, config: loadConfig() });
}
// Handle special returns (imported content or batch)
if (typeof result === 'string') {
await processPrompt(result, client, config, sessionCounter++);
} else if (result && result.batch) {
for (const prompt of result.batch) {
console.log(`\n${themeManager.color('info')('Processing prompt:')} ${formatProcessingPromptDisplay(prompt, 80)}`);
await processPrompt(prompt, client, config, sessionCounter++);
}
}
rl.prompt();
return;
}
// Old command handling for backward compatibility
if (trimmedInput === 'exit' || trimmedInput === 'quit') {
console.log(themeManager.color('success')('\n✨ Thanks for using KleoSr PE2-CLI! Goodbye!'));
rl.close();
return;
}
if (trimmedInput === 'help') {
await handleCommand('/help', rl, config);
rl.prompt();
return;
}
if (trimmedInput === 'config') {
await handleCommand('/settings', rl, config);
rl.prompt();
return;
}
if (trimmedInput === 'status') {
await handleCommand('/config', rl, config);
rl.prompt();
return;
}
// Process as prompt
await processPrompt(trimmedInput, client, config, sessionCounter++);
rl.prompt();
});
rl.on('close', () => {
console.log(themeManager.color('success')('\n✨ Session ended. Have a great day!'));
process.exit(0);
});
}
async function processPrompt(prompt, client, config, sessionId) {
let progressBar = null;
isProcessingPrompt = true;
try {
setTerminalTitle(`KleoSr PE2-CLI - Processing Session ${sessionId}`);
// Validate prompt
const validationError = validatePrompt(prompt);
if (validationError) {
throw new ValidationError(validationError);
}
// Optimized context and strategy retrieval
const context = getContext();
const strategy = selectStrategy();
console.log(themeManager.color('info')(`\n⚡ Processing Session ${sessionId} (${prompt.length} chars)...`));
// Analyze prompt complexity with context awareness
const { difficulty, iterations: baseIterations, score: complexityScore } = analyzePromptComplexity(prompt);
// Determine iterations: CLI override > analysis > strategy fallback
const cliIterations = config._cliOptions?.iterations;
let recommendedIterations = Number.isInteger(cliIterations) && cliIterations > 0 ? cliIterations : baseIterations;
if (!recommendedIterations) recommendedIterations = strategy.iterations || 2;
console.log(themeManager.color('info')('📊 Adaptive Analysis:'));
console.log(` Domain: ${context.domain}`);
const { DIFFICULTY_INDICATORS } = await import('./ui.js');
console.log(` Difficulty: ${DIFFICULTY_INDICATORS[difficulty]} ${difficulty}`);
const complexityScoreMax = PERFORMANCE_METRICS.complexityScoreMax;
console.log(` Score: ${complexityScore}/${complexityScoreMax}`);
console.log(` Iterations: ${recommendedIterations} (adapted for ${context.domain})`);
if (strategy.adaptiveFeatures.length > 0) {
console.log(` Features: ${strategy.adaptiveFeatures.join(', ')}`);
}
console.log();
const result = await runEngine({
prompt,
client,
config,
sessionId,
themeManager,
statsTracker,
userPreferences,
getContext,
selectStrategy
});
if (!result.success) {
return;
}
const { outputFile: resultOutputFile } = result;
const resultContent = fs.readFileSync(resultOutputFile, 'utf-8');
lastResult = resultContent;
console.log(themeManager.color('success')(`\n✓ PE²-optimized prompt saved to ${resultOutputFile}`));
const refinementCount = result.refinementHistory?.length || 0;
console.log(themeManager.color('info')(`📊 ${context.domain} domain | ${difficulty} complexity | ${refinementCount} iterations | ${complexityScore}/${complexityScoreMax} score`));
console.log(themeManager.color('info')(`🎯 Strategy: ${strategy.focus} optimization\n`));
console.log(themeManager.color('muted')('Tip: Use /copy to copy the result to clipboard'));
setTerminalTitle('KleoSr PE²-CLI - Interactive Mode');
isProcessingPrompt = false;
} catch (error) {
// Ensure progress bar is stopped on error
if (progressBar) {
progressBar.stop();
progressBar = null;
}
const exitCode = handleError(error, themeManager);
setTerminalTitle('KleoSr PE²-CLI - Interactive Mode');
isProcessingPrompt = false;
if (exitCode !== 0 && !(error instanceof ValidationError)) {
throw error;
}
}
}
async function main() {
setTerminalTitle('KleoSr PE2-CLI');
const program = new Command();
program
.name('pe2-cli')
.description('🚀 KleoSr PE2-CLI: Convert raw prompts to PE2-optimized prompts using adaptive intelligence.')
.version('3.4.5')
.argument('[input]', 'Text prompt or path to file (optional - if not provided, starts interactive mode)')
.option('--model <model>', 'OpenRouter model name (overrides config)')
.option('--iterations <number>', 'Number of PE2 refinement rounds (auto-detected if not specified)', parseInt)
.option('--output-file <file>', 'Path to the output markdown file')
.option('--auto-difficulty', 'Show complexity analysis and exit without processing')
.option('-i, --interactive', 'Start in interactive mode')
.option('--config', 'Configure API key and model settings')
.option('--text', 'Force input to be treated as text (not file path)')
.option('--file', 'Force input to be treated as file path')
.option('--provider <provider>', 'Override provider for this run (openai|anthropic|google|openrouter|ollama)')
.showHelpAfterError('(add --help for additional information)')
.configureOutput({
outputError: (str, write) => write(themeManager.color('error')(str))
})
.configureHelp({
afterAll: `
Examples:
npx @kleosr/pe2-cli # Start interactive mode (auto-config)
npx @kleosr/pe2-cli --config # Configure API key and model
npx @kleosr/pe2-cli --interactive # Start interactive mode
npx @kleosr/pe2-cli "Write a Python function" # Process text directly
npx @kleosr/pe2-cli input.txt # Process file with saved config
npx @kleosr/pe2-cli input.txt --model openai/gpt-4o # Override model for this run
npx @kleosr/pe2-cli "Complex prompt" --iterations 3 # Direct text with specific iterations
npx @kleosr/pe2-cli "Test prompt" --auto-difficulty # Show complexity analysis for text
npx @kleosr/pe2-cli "Complex prompt" --provider ollama --model llama3 # Override provider for this run
Configuration:
First run will prompt for API key and model selection.
Settings are saved to ~/.kleosr-pe2/config.json
Use --config to reconfigure anytime.
Input Methods:
1. Direct text: npx @kleosr/pe2-cli "Your prompt here"
2. File path: npx @kleosr/pe2-cli input.txt
3. Interactive: npx @kleosr/pe2-cli (no arguments)
4. Force text: --text flag treats input as text even if it looks like a file
5. Force file: --file flag treats input as file path
`
});
try {
await program.parseAsync(process.argv);
const options = program.opts();
const input = program.args[0];
// Check if we should configure
if (options.config) {
setTerminalTitle('KleoSr PE2-CLI - Configuration');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
displayBanner({ themeManager, userPreferences, config: loadConfig(), interactive: false });
console.log(themeManager.color('success')(`🔧 Configuration Mode | v3.4.5 | ${new Date().toLocaleString()}`));
console.log(themeManager.color('primary')('='.repeat(78)));
await promptForConfig(rl);
rl.close();
return;
}
// Always interactive: banner is rendered inside interactiveMode
await interactiveMode(input, options);
} catch (error) {
if (error.code === 'commander.helpDisplayed' || error.code === 'commander.version') {
return;
}
console.error(themeManager.color('error')(`❌ Error: ${error.message}`));
if (error.stack && process.env.DEBUG) {
console.error(error.stack);
}
process.exit(error.exitCode || 1);
}
}
main().catch(error => {
console.error(themeManager.color('error')(`❌ Unexpected error: ${error.message}`));
if (error.stack && process.env.DEBUG) {
console.error(error.stack);
}
process.exit(1);
});