| |
| """ |
| Stack 2.9 - CLI Entry Point |
| A complete CLI and agent interface showcasing Stack 2.9 capabilities. |
| """ |
|
|
| import os |
| import sys |
| import cmd |
| import json |
| import argparse |
| import asyncio |
| import subprocess |
| from pathlib import Path |
| from typing import Any, Dict, List, Optional |
|
|
| from .agent import create_agent, StackAgent |
| from .context import create_context_manager |
|
|
|
|
| |
| |
| |
|
|
| def print_banner(): |
| """Print the Stack 2.9 banner.""" |
| banner = r""" |
| ____ _ _ _ |
| | _ \ ___ _ __ __| |_ __ ___ (_)_ __ | | __ |
| | |_) / _ \ '_ \ / _` | '__/ _ \| | '_ \ | |/ / |
| | _ < __/ | | | (_| | | | (_) | | | | | | < |
| |_| \_\___|_| |_|\__,_|_| \___/|_|_| |_| |_|\_\ |
| |
| Stack 2.9 CLI & Agent Interface |
| """ |
| print(banner) |
|
|
|
|
| def print_colored(text: str, color: str = "white", bold: bool = False): |
| """Print colored text.""" |
| colors = { |
| "red": "\033[91m", |
| "green": "\033[92m", |
| "yellow": "\033[93m", |
| "blue": "\033[94m", |
| "magenta": "\033[95m", |
| "cyan": "\033[96m", |
| "white": "\033[97m", |
| "reset": "\033[0m" |
| } |
| |
| if bold: |
| text = f"\033[1m{text}\033[0m" |
| |
| sys.stdout.write(colors.get(color, "white")) |
| print(text) |
| sys.stdout.write(colors["reset"]) |
|
|
|
|
| def format_output(data: Any, format: str = "text") -> str: |
| """Format output for display.""" |
| if format == "json": |
| return json.dumps(data, indent=2) |
| elif isinstance(data, dict): |
| return "\n".join(f" {k}: {v}" for k, v in data.items()) |
| elif isinstance(data, list): |
| return "\n".join(f" - {item}" for item in data) |
| else: |
| return str(data) |
|
|
|
|
| |
| |
| |
|
|
| class ChatMode(cmd.Cmd): |
| """Interactive chat mode with the agent.""" |
| |
| intro = """ |
| Welcome to Stack 2.9 Interactive Chat! |
| Type your queries or use commands: |
| /tools - List available tools |
| /schema - Show tool schemas |
| /context - Show current context |
| /history - Show conversation history |
| /clear - Clear chat history |
| /exit - Exit chat mode |
| |
| Just type your question or task to get started! |
| """ |
| prompt = "\n[Stack]> " |
| |
| def __init__(self, agent: StackAgent): |
| super().__init__() |
| self.agent = agent |
| self.history: List[Dict[str, Any]] = [] |
| self.show_tool_calls = False |
| self.output_format = "text" |
| self.voice_input = False |
| |
| def default(self, line: str): |
| """Handle user input as a query to the agent.""" |
| if line.startswith('/'): |
| return |
| |
| print_colored("\nThinking...", "cyan") |
| |
| try: |
| response = self.agent.process(line) |
| self.history.append({ |
| "query": line, |
| "response": response.content, |
| "tool_calls": [tc.tool_name for tc in response.tool_calls], |
| "timestamp": "now" |
| }) |
| |
| |
| print("\n" + "="*50) |
| print_colored("Response:", "green", bold=True) |
| print(response.content) |
| |
| |
| if self.show_tool_calls and response.tool_calls: |
| print("\n" + "-"*50) |
| print_colored("Tools Used:", "yellow") |
| for tc in response.tool_calls: |
| status = "✓" if tc.success else "✗" |
| print(f" {status} {tc.tool_name}") |
| if not tc.success and tc.error: |
| print(f" Error: {tc.error}") |
| |
| if response.needs_clarification: |
| print_colored(f"\nNote: {response.clarification_needed}", "yellow") |
| |
| except KeyboardInterrupt: |
| print_colored("\nInterrupted.", "red") |
| except Exception as e: |
| print_colored(f"\nError: {e}", "red") |
| |
| def do_tools(self, arg): |
| """List all available tools.""" |
| tools = self.agent.context_manager.session.tools_used |
| if tools: |
| print(f"\nUsed {len(tools)} tools this session:") |
| for tool in set(tools): |
| print(f" - {tool}") |
| else: |
| print("\nNo tools used yet this session.") |
| |
| def do_schema(self, arg): |
| """Show tool schemas for tool calling.""" |
| schemas = self.agent.get_schemas() |
| print(f"\nTool Schemas ({len(schemas)} tools):") |
| for schema in schemas[:10]: |
| print(f"\n {schema['name']}: {schema['description']}") |
| print(f"\n ... and {len(schemas) - 10} more") |
| |
| def do_context(self, arg): |
| """Show current context.""" |
| context = self.agent.get_context() |
| print("\n" + context) |
| |
| def do_history(self, arg): |
| """Show conversation history.""" |
| print(f"\nChat History ({len(self.history)} messages):") |
| for i, entry in enumerate(self.history[-10:], 1): |
| print(f"\n{i}. Query: {entry['query'][:50]}...") |
| print(f" Tools: {', '.join(entry['tool_calls'])}") |
| |
| def do_clear(self, arg): |
| """Clear chat history.""" |
| self.history.clear() |
| self.agent.context_manager.session.messages.clear() |
| print_colored("Chat history cleared.", "green") |
| |
| def do_voice(self, arg): |
| """Toggle voice input.""" |
| self.voice_input = not self.voice_input |
| status = "enabled" if self.voice_input else "disabled" |
| print_colored(f"Voice input {status} (note: requires additional setup)", "cyan") |
| |
| def do_exit(self, arg): |
| """Exit chat mode.""" |
| print_colored("Goodbye!", "green") |
| return True |
| |
| def do_EOF(self, arg): |
| """Handle Ctrl+D.""" |
| print() |
| return self.do_exit(arg) |
| |
| def postcmd(self, stop: bool, line: str) -> bool: |
| """Save session after each command.""" |
| return stop |
| |
| def run(self): |
| """Run the chat loop.""" |
| print_banner() |
| print(self.intro) |
| |
| try: |
| self.cmdloop() |
| except KeyboardInterrupt: |
| print("\nExiting...") |
|
|
|
|
| |
| |
| |
|
|
| class CommandMode: |
| """Execute single commands.""" |
| |
| def __init__(self, agent: StackAgent): |
| self.agent = agent |
| self.context_manager = create_context_manager() |
| |
| def execute( |
| self, |
| query: str, |
| output_file: Optional[str] = None, |
| output_format: str = "text" |
| ) -> str: |
| """Execute a query and return result.""" |
| print(f"Executing: {query}") |
| |
| response = self.agent.process(query) |
| |
| result = format_output(response.content, output_format) |
| |
| |
| if output_file: |
| with open(output_file, 'w') as f: |
| f.write(result) |
| print(f"Output saved to: {output_file}") |
| |
| return result |
| |
| def execute_tools( |
| self, |
| tools: List[str], |
| output_file: Optional[str] = None |
| ) -> str: |
| """Execute specific tools directly.""" |
| print(f"Executing tools: {', '.join(tools)}") |
| |
| response = self.agent.process_with_tools("", tools) |
| |
| if output_file: |
| with open(output_file, 'w') as f: |
| f.write(response.content) |
| print(f"Output saved to: {output_file}") |
| |
| return response.content |
|
|
|
|
| |
| |
| |
|
|
| class VoiceInterface: |
| """Voice input/output interface (requires additional setup).""" |
| |
| def __init__(self): |
| self.available = self._check_availability() |
| |
| def _check_availability(self) -> bool: |
| """Check if voice dependencies are available.""" |
| try: |
| import speech_recognition as sr |
| import pyttsx3 |
| return True |
| except ImportError: |
| return False |
| |
| def listen(self) -> Optional[str]: |
| """Listen for voice input.""" |
| if not self.available: |
| print("Voice recognition not available. Install with: pip install SpeechRecognition pyttsx3 pyaudio") |
| return None |
| |
| try: |
| import speech_recognition as sr |
| |
| r = sr.Recognizer() |
| with sr.Microphone() as source: |
| print("Listening... (speak now)") |
| audio = r.listen(source, timeout=5) |
| |
| text = r.recognize_google(audio) |
| print(f"Heard: {text}") |
| return text |
| except Exception as e: |
| print(f"Voice error: {e}") |
| return None |
| |
| def speak(self, text: str): |
| """Speak text output.""" |
| if not self.available: |
| print(f"(TTS not available): {text}") |
| return |
| |
| try: |
| import pyttsx3 |
| |
| engine = pyttsx3.init() |
| engine.say(text) |
| engine.runAndWait() |
| except Exception as e: |
| print(f"TTS error: {e}") |
|
|
|
|
| |
| |
| |
|
|
| class StackCLI: |
| """Main CLI entry point.""" |
| |
| def __init__(self): |
| self.agent = create_agent() |
| self.chat_mode = ChatMode(self.agent) |
| self.command_mode = CommandMode(self.agent) |
| self.voice = VoiceInterface() |
| |
| def run_interactive(self): |
| """Run interactive chat mode.""" |
| self.chat_mode.run() |
| |
| def run_command(self, query: str, out_file: Optional[str] = None, fmt: str = "text"): |
| """Run a single command.""" |
| result = self.command_mode.execute(query, out_file, fmt) |
| print(result) |
| return result |
| |
| def run_tools(self, tools: List[str], out_file: Optional[str] = None): |
| """Run specific tools.""" |
| result = self.command_mode.execute_tools(tools, out_file) |
| print(result) |
| return result |
|
|
| def run_eval(self, benchmark: str, provider: str = 'ollama', model: str = None): |
| """Run evaluation benchmarks.""" |
| print_colored(f"\n=== Running {benchmark} benchmark ===", "blue") |
|
|
| import sys |
| from pathlib import Path |
| eval_dir = Path(__file__).parent.parent / "stack-2.9-eval" |
| if eval_dir.exists(): |
| sys.path.insert(0, str(eval_dir)) |
|
|
| try: |
| if benchmark == 'mbpp': |
| from benchmarks.mbpp import MBPP |
| b = MBPP(model_provider=provider, model_name=model) |
| elif benchmark == 'human_eval': |
| from benchmarks.human_eval import HumanEval |
| b = HumanEval(model_provider=provider, model_name=model) |
| elif benchmark == 'gsm8k': |
| from benchmarks.gsm8k import GSM8K |
| b = GSM8K(model_provider=provider, model_name=model) |
| elif benchmark == 'all': |
| from benchmarks.mbpp import MBPP |
| from benchmarks.human_eval import HumanEval |
| from benchmarks.gsm8k import GSM8K |
| for name, Benchmark in [('MBPP', MBPP), ('HumanEval', HumanEval), ('GSM8K', GSM8K)]: |
| print_colored(f"\n--- {name} ---", "yellow") |
| b = Benchmark(model_provider=provider, model_name=model) |
| results = b.evaluate() |
| print(f" Accuracy: {results['accuracy']*100:.1f}%") |
| return |
|
|
| results = b.evaluate() |
| print_colored(f"\nResults:", "green") |
| print(f" Accuracy: {results['accuracy']*100:.1f}%") |
| print(f" Passed: {results['pass_at_1']}/{results['total_cases']}") |
| print(f" Model: {results['model']}") |
| except Exception as e: |
| print_colored(f"Error: {e}", "red") |
|
|
| def run_patterns(self, action: str): |
| """Manage learned patterns.""" |
| print_colored(f"\n=== Pattern Management ===", "blue") |
|
|
| import sys |
| from pathlib import Path |
| train_dir = Path(__file__).parent.parent / "stack-2.9-training" |
| if train_dir.exists(): |
| sys.path.insert(0, str(train_dir)) |
|
|
| try: |
| from pattern_miner import PatternMiner |
| miner = PatternMiner() |
|
|
| if action == 'list': |
| patterns = miner.get_relevant_patterns(limit=20) |
| print_colored(f"\nStored Patterns:", "yellow") |
| for p in patterns: |
| print(f" [{p.pattern_type}] {p.code_snippet[:50]}...") |
| elif action == 'stats': |
| stats = miner.get_statistics() |
| print_colored(f"\nStatistics:", "yellow") |
| print(f" Total Feedback: {stats['total_feedback']}") |
| print(f" Success Rate: {stats['success_rate']:.1%}") |
| print(f" Total Patterns: {stats['total_patterns']}") |
| except Exception as e: |
| print_colored(f"Error: {e}", "red") |
|
|
| def run_voice(self): |
| """Run voice mode loop.""" |
| if not self.voice.available: |
| print_colored("Voice interface not available.", "red") |
| return |
| |
| print_banner() |
| print("Voice Mode - Say 'exit' to quit") |
| |
| try: |
| while True: |
| text = self.voice.listen() |
| if not text: |
| continue |
| |
| if "exit" in text.lower(): |
| print("Exiting...") |
| break |
| |
| |
| response = self.agent.process(text) |
| print(f"\nResponse: {response.content[:200]}") |
| |
| |
| self.voice.speak(response.content[:200]) |
| |
| except KeyboardInterrupt: |
| print("\nExiting...") |
|
|
|
|
| def main(): |
| """Main entry point.""" |
| parser = argparse.ArgumentParser( |
| description="Stack 2.9 CLI and Agent Interface", |
| formatter_class=argparse.RawDescriptionHelpFormatter, |
| epilog=""" |
| Examples: |
| %(prog)s # Interactive chat mode |
| %(prog)s -c "read README.md" # Execute single command |
| %(prog)s -t read write # Execute specific tools |
| %(prog)s -v # Voice mode |
| """ |
| ) |
| |
| parser.add_argument( |
| '-c', '--command', |
| help="Execute a single query/command" |
| ) |
| |
| parser.add_argument( |
| '-t', '--tools', |
| nargs='+', |
| help="Execute specific tools" |
| ) |
| |
| parser.add_argument( |
| '-o', '--output', |
| help="Output file for results" |
| ) |
| |
| parser.add_argument( |
| '-f', '--format', |
| choices=['text', 'json'], |
| default='text', |
| help="Output format" |
| ) |
| |
| parser.add_argument( |
| '-v', '--voice', |
| action='store_true', |
| help="Enable voice mode" |
| ) |
| |
| parser.add_argument( |
| '-w', '--workspace', |
| default="/Users/walidsobhi/.openclaw/workspace", |
| help="Workspace path" |
| ) |
|
|
| |
| parser.add_argument( |
| '-e', '--eval', |
| choices=['mbpp', 'human_eval', 'gsm8k', 'all'], |
| help="Run evaluation benchmark" |
| ) |
|
|
| parser.add_argument( |
| '--eval-provider', |
| default='ollama', |
| choices=['ollama', 'openai', 'anthropic', 'together'], |
| help="Model provider for evaluation" |
| ) |
|
|
| parser.add_argument( |
| '--eval-model', |
| type=str, |
| help="Model name for evaluation" |
| ) |
|
|
| |
| parser.add_argument( |
| '--patterns', |
| choices=['list', 'stats', 'clear'], |
| help="Manage learned patterns" |
| ) |
|
|
| |
| parser.add_argument( |
| '--train', |
| action='store_true', |
| help="Run LoRA training" |
| ) |
|
|
| args = parser.parse_args() |
| |
| try: |
| |
| cli = StackCLI() |
|
|
| |
| if args.eval: |
| cli.run_eval(args.eval, args.eval_provider, args.eval_model) |
| return |
|
|
| |
| if args.patterns: |
| cli.run_patterns(args.patterns) |
| return |
|
|
| |
| if args.train: |
| cli.run_train() |
| return |
|
|
| if args.voice: |
| cli.run_voice() |
| elif args.tools: |
| cli.run_tools(args.tools, args.output) |
| elif args.command: |
| exit(0 if cli.run_command(args.command, args.output, args.format) else 1) |
| else: |
| |
| cli.run_interactive() |
| |
| except KeyboardInterrupt: |
| print("\nGoodbye!") |
| sys.exit(0) |
| except Exception as e: |
| print(f"Error: {e}", file=sys.stderr) |
| sys.exit(1) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|