behemoth-cli
Version:
š BEHEMOTH CLIv3.760.4 - Level 50+ POST-SINGULARITY Intelligence Trading AI
376 lines (324 loc) ⢠15.2 kB
text/typescript
import { Command } from 'commander';
import chalk from 'chalk';
import { render } from 'ink';
import React from 'react';
import { Agent } from './agent.js';
import App from '../ui/App.js';
import { initializeBehemothMCP } from '../tools/behemoth-tools.js';
import { CLI_FEATURES } from '../utils/cli-config.js';
import { parseNaturalLanguage, generateParsingResponse } from './nlp.js';
import { MultiProviderConfigManager } from '../utils/multi-provider-config.js';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import * as readline from 'readline';
const program = new Command();
/**
* Prompt user for input
*/
function askQuestion(question: string): Promise<string> {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
return new Promise((resolve) => {
rl.question(question, (answer) => {
rl.close();
resolve(answer.trim());
});
});
}
/**
* Ensure first-run setup is completed
*/
async function ensureSetup(): Promise<void> {
const configDir = path.join(os.homedir(), '.cliv2');
const providerConfigPath = path.join(configDir, 'providers.json');
// Check if config exists or needs updating
let needsSetup = false;
let existingConfig = null;
if (!fs.existsSync(providerConfigPath)) {
needsSetup = true;
} else {
// Check if existing config needs API key prompts
try {
const configData = fs.readFileSync(providerConfigPath, 'utf8');
existingConfig = JSON.parse(configData);
// Check if we need to prompt for API keys (empty keys or old default model)
const hasEmptyKeys = Object.values(existingConfig.providers || {}).some(
(provider: any) => provider.apiKey === ""
);
const hasOldDefault = existingConfig.providers?.openrouter?.defaultModel !== 'deepseek/deepseek-chat-v3-0324:free';
if (hasEmptyKeys || hasOldDefault) {
console.log(chalk.cyan('š§ Updating BEHEMOTH CLI configuration...'));
needsSetup = true;
}
} catch (error) {
needsSetup = true;
}
}
if (needsSetup) {
console.log(chalk.cyan('š First run detected - setting up BEHEMOTH CLI...'));
console.log(chalk.yellow('š Quick setup - you can change these later with /keys and /model commands'));
console.log('');
// Create config directory
if (!fs.existsSync(configDir)) {
fs.mkdirSync(configDir, { recursive: true });
}
// Ask for preferred provider
console.log(chalk.cyan('š§ Choose your preferred AI provider:'));
console.log('1. OpenRouter (recommended - includes many free models)');
console.log('2. Groq (fast, good free tier)');
console.log('3. DeepSeek (direct access, good free tier)');
console.log('');
const providerChoice = await askQuestion('Enter choice (1-3) or press Enter for OpenRouter: ');
let defaultProvider = 'openrouter';
let needsApiKey = false;
switch (providerChoice) {
case '2':
defaultProvider = 'groq';
needsApiKey = true;
break;
case '3':
defaultProvider = 'deepseek';
needsApiKey = true;
break;
default:
defaultProvider = 'openrouter';
break;
}
// Create or update config
const defaultConfig = existingConfig || {
defaultProvider,
providers: {
groq: { apiKey: "", defaultModel: "llama-3.3-70b-versatile", enabled: true },
openrouter: { apiKey: "", defaultModel: "deepseek/deepseek-chat-v3-0324:free", enabled: true },
deepseek: { apiKey: "", defaultModel: "deepseek-chat", enabled: true }
},
exchanges: {}
};
// Update default provider if user made a choice
if (defaultProvider) {
defaultConfig.defaultProvider = defaultProvider;
}
// Ensure all providers exist and update OpenRouter default model
if (!defaultConfig.providers) defaultConfig.providers = {};
if (!defaultConfig.providers.groq) defaultConfig.providers.groq = { apiKey: "", defaultModel: "llama-3.3-70b-versatile", enabled: true };
if (!defaultConfig.providers.openrouter) defaultConfig.providers.openrouter = { apiKey: "", defaultModel: "deepseek/deepseek-chat-v3-0324:free", enabled: true };
if (!defaultConfig.providers.deepseek) defaultConfig.providers.deepseek = { apiKey: "", defaultModel: "deepseek-chat", enabled: true };
// Always update OpenRouter default model
defaultConfig.providers.openrouter.defaultModel = "deepseek/deepseek-chat-v3-0324:free";
// Ask for API key based on selected provider
if (needsApiKey) {
console.log('');
console.log(chalk.yellow('š API Key Setup:'));
console.log(chalk.cyan(`š§ ${defaultProvider.charAt(0).toUpperCase() + defaultProvider.slice(1)} API Key:`));
if (defaultProvider === 'groq') {
console.log('Get your free Groq API key at: https://console.groq.com/keys');
} else if (defaultProvider === 'deepseek') {
console.log('Get your free DeepSeek API key at: https://platform.deepseek.com/');
}
const apiKey = await askQuestion(`Enter your ${defaultProvider} API key (or press Enter to skip): `);
if (apiKey) {
if (defaultProvider === 'groq') {
defaultConfig.providers.groq.apiKey = apiKey;
} else if (defaultProvider === 'deepseek') {
defaultConfig.providers.deepseek.apiKey = apiKey;
}
}
} else {
console.log('');
console.log(chalk.green('⨠OpenRouter selected - free models available without API key'));
console.log(chalk.yellow('š” For premium models, get an API key at: https://openrouter.ai/keys'));
// Ask if they want to add OpenRouter API key for premium models
const wantApiKey = await askQuestion('Add OpenRouter API key now for premium models? (y/n): ');
if (wantApiKey.toLowerCase() === 'y' || wantApiKey.toLowerCase() === 'yes') {
const apiKey = await askQuestion('Enter your OpenRouter API key: ');
if (apiKey) {
defaultConfig.providers.openrouter.apiKey = apiKey;
}
}
}
fs.writeFileSync(providerConfigPath, JSON.stringify(defaultConfig, null, 2));
console.log('');
console.log(chalk.green('ā
Configuration initialized'));
console.log(chalk.yellow('š” Run "/howto setup" for complete setup instructions'));
console.log(chalk.cyan('š§ Use /keys and /model commands to update configuration anytime'));
}
}
/**
* Execute a single prompt and exit - useful for testing MCP tools
*/
async function executePrompt(
prompt: string,
temperature: number,
system: string | null,
debug?: boolean
): Promise<void> {
console.log(chalk.cyan('š§ BEHEMOTH MCP Tool Testing Mode'));
console.log(chalk.gray('ā'.repeat(60)));
console.log(chalk.white(`š Prompt: ${prompt}`));
console.log(chalk.gray('ā'.repeat(60)));
try {
// Ensure setup is completed
await ensureSetup();
// Initialize BEHEMOTH MCP servers
console.log(chalk.cyan('š Initializing BEHEMOTH MCP servers...'));
const mcpInitialized = await initializeBehemothMCP();
if (mcpInitialized) {
console.log(chalk.green('ā
BEHEMOTH MCP servers initialized successfully'));
} else {
console.log(chalk.yellow('ā ļø BEHEMOTH MCP initialization failed - some tools may not be available'));
}
// Create agent
const configManager = new MultiProviderConfigManager();
const defaultProvider = configManager.getDefaultProvider();
const defaultModel = configManager.getProviderModel(defaultProvider);
const agent = await Agent.create(defaultModel, defaultProvider, temperature, system, debug);
// Parse natural language to see if it maps to specific commands
console.log(chalk.cyan('š§ Processing natural language...'));
const parsed = await parseNaturalLanguage(prompt);
if (parsed.confidence > 0.6) {
console.log(chalk.green(`ā
NLP Parsing successful (confidence: ${(parsed.confidence * 100).toFixed(1)}%)`));
console.log(chalk.blue(`šÆ Interpreted as: ${parsed.command}`));
console.log(chalk.gray(generateParsingResponse(parsed)));
} else {
console.log(chalk.yellow(`ā ļø Low confidence NLP parsing (${(parsed.confidence * 100).toFixed(1)}%)`));
console.log(chalk.gray('Will process as general conversation...'));
}
console.log(chalk.gray('ā'.repeat(60)));
console.log(chalk.cyan('š¤ BEHEMOTH AI Response:'));
console.log(chalk.gray('ā'.repeat(60)));
// Set up response handlers for single prompt mode
let responseText = '';
let toolCallCount = 0;
agent.setToolCallbacks({
onThinkingText: (text: string, title?: string) => {
if (title) {
console.log(chalk.magenta(`\nš ${title}:`));
}
console.log(chalk.gray(text));
},
onToolStart: (toolName: string, args: any) => {
toolCallCount++;
console.log(chalk.blue(`\nš§ Tool Call #${toolCallCount}: ${toolName}`));
if (Object.keys(args).length > 0) {
console.log(chalk.gray(` Args: ${JSON.stringify(args, null, 2)}`));
}
},
onToolEnd: (toolName: string, result: any) => {
console.log(chalk.green(`ā
Tool Result: ${toolName}`));
if (result && typeof result === 'object') {
console.log(chalk.gray(` Result: ${JSON.stringify(result, null, 2).substring(0, 200)}...`));
} else {
console.log(chalk.gray(` Result: ${String(result).substring(0, 200)}...`));
}
},
onStreamUpdate: (content: string, isFinal: boolean) => {
process.stdout.write(chalk.white(content));
responseText += content;
},
onFinalMessage: (content: string) => {
console.log(chalk.white(content));
responseText += content;
},
onApiUsage: (usage) => {
console.log(chalk.blue(`\nš API Usage: ${usage.prompt_tokens} prompt + ${usage.completion_tokens} completion = ${usage.total_tokens} total tokens`));
}
});
// Execute the prompt
await agent.chat(prompt);
console.log(chalk.gray('\nā'.repeat(60)));
console.log(chalk.green(`ā
Prompt execution completed`));
console.log(chalk.blue(`š Tools called: ${toolCallCount}`));
console.log(chalk.blue(`š Response length: ${responseText.length} characters`));
console.log(chalk.gray('ā'.repeat(60)));
} catch (error) {
console.log(chalk.red(`ā Error executing prompt: ${error}`));
throw error; // Allow caller to handle exit
}
// Successful completion - no need for process.exit()
}
async function startChat(
temperature: number,
system: string | null,
debug?: boolean
): Promise<void> {
// Beautiful BEHEMOTH ASCII art with blue to red gradient
console.log(chalk.hex('#0066FF').bold(`
āāāāāāā āāāāāāāāāāā āāāāāāāāāāāāāāā āāāā āāāāāāā āāāāāāāāāāāā āāā`));
console.log(chalk.hex('#3377FF').bold(`
āāāāāāāāāāāāāāāāāāā āāāāāāāāāāāāāāāā āāāāāāāāāāāāāāāāāāāāāāāāāā āāā`));
console.log(chalk.hex('#6688FF').bold(`
āāāāāāāāāāāāāā āāāāāāāāāāāāāā āāāāāāāāāāāāāā āāā āāā āāāāāāāā`));
console.log(chalk.hex('#9999FF').bold(`
āāāāāāāāāāāāāā āāāāāāāāāāāāāā āāāāāāāāāāāāāā āāā āāā āāāāāāāā`));
console.log(chalk.hex('#CC66FF').bold(`
āāāāāāāāāāāāāāāāāāā āāāāāāāāāāāāāā āāā āāāāāāāāāāāā āāā āāā āāā`));
console.log(chalk.hex('#FF3333').bold(`
āāāāāāā āāāāāāāāāāā āāāāāāāāāāāāāā āāā āāāāāāā āāā āāā āāā`));
console.log(chalk.hex('#FFD700').bold(` š COSMIC CRYPTO TRADING CLI š`));
console.log(chalk.hex('#FF6B35').bold(` š 200+ Professional Trading Tools | AI Multi-Agent Analysis š`));
console.log(chalk.hex('#8A2BE2').bold(` ā” Quantum Market Intelligence | Real-Time Trading ā”`));
console.log(chalk.cyan.bold(` ⨠BEHEMOTH v2.2.4 - Production Ready āØ`));
console.log(chalk.gray.italic(` Built by fR3k@mcpintelligence.com.au`));
console.log('');
// Ensure setup is completed
await ensureSetup();
const configManager = new MultiProviderConfigManager();
const defaultProvider = configManager.getDefaultProvider();
const defaultModel = configManager.getProviderModel(defaultProvider);
try {
// Create agent (API key will be checked on first message)
const agent = await Agent.create(defaultModel, defaultProvider, temperature, system, debug);
// Initialize BEHEMOTH MCP servers for crypto trading tools
console.log(chalk.cyan('š Initializing BEHEMOTH MCP servers...'));
const mcpInitialized = await initializeBehemothMCP();
if (mcpInitialized) {
console.log(chalk.green('ā
BEHEMOTH MCP servers initialized successfully'));
} else {
console.log(chalk.yellow('ā ļø BEHEMOTH MCP initialization failed - trading tools may not be available'));
}
// CLI Configuration Notice
console.log(chalk.magenta('āļø CLI Mode: Streaming responses disabled for optimal terminal performance'));
if (CLI_FEATURES.ENABLE_ASCII_CHARTS) {
console.log(chalk.cyan('š Interactive ASCII charts available - try /chart --dashboard'));
}
console.log('');
render(React.createElement(App, { agent }), { stdin: process.stdin });
} catch (error) {
console.log(chalk.red(`Error initializing agent: ${error}`));
throw error; // Allow caller to handle exit
}
}
program
.name('behemoth')
.description('Behemoth Crypto Trading CLI - AI-Powered Cryptocurrency Analysis & Trading')
.version('2.2.4')
.option('-t, --temperature <temperature>', 'Temperature for generation', parseFloat, 1.0)
.option('-s, --system <message>', 'Custom system message')
.option('-d, --debug', 'Enable debug logging to debug-agent.log in current directory')
.option('-p, --prompt <prompt>', 'Execute a single prompt and exit (useful for testing MCP tools)')
.action(async (options) => {
try {
if (options.prompt) {
await executePrompt(
options.prompt,
options.temperature,
options.system || null,
options.debug
);
} else {
await startChat(
options.temperature,
options.system || null,
options.debug
);
}
} catch (error) {
console.error(chalk.red(`Fatal error: ${error}`));
process.exitCode = 1; // Set exit code without forcing immediate exit
}
});
program.parse();