cli.py
1 """ 2 Main CLI entry point for Kamaji. 3 """ 4 5 import sys 6 import argparse 7 from kamaji import __version__ 8 from kamaji.config import show_config, set_config_value, get_config_value 9 from kamaji.commands import run_chat, run_ask, run_rag, run_agent, run_interactive, run_work 10 from kamaji.commands.mature import run_mature 11 from kamaji.update import run_update 12 13 14 def main(): 15 """Main CLI entry point.""" 16 parser = argparse.ArgumentParser( 17 description="Kamaji - A powerful CLI for interacting with LLMs", 18 formatter_class=argparse.RawDescriptionHelpFormatter, 19 epilog=""" 20 Examples: 21 kamaji tui Start beautiful TUI chat interface 22 kamaji tui --agent Start TUI with agent tools enabled 23 kamaji interactive Start agent with file/shell tools 24 kamaji chat Start interactive chat 25 kamaji ask "What is Python?" Ask a single question 26 kamaji rag "What is this about?" docs.txt Query documents 27 kamaji agent "Calculate 10 factorial" Run agent with tools 28 kamaji mature Analyze codebase and suggest improvements 29 kamaji work Work on internal task list 30 kamaji work "<prompt>" Work based on specific prompt 31 kamaji work <path> Work on specific path/project 32 kamaji queue "task1, task2, task3" Add tasks to queue 33 kamaji tasks Show current task list 34 kamaji do Select and execute a task 35 kamaji update Update Kamaji to latest version 36 kamaji config Show configuration 37 kamaji config set model llama2 Set configuration value 38 """ 39 ) 40 41 parser.add_argument('--version', action='version', version=f'Kamaji {__version__}') 42 43 subparsers = parser.add_subparsers(dest='command', help='Command to run') 44 45 # TUI command (always in agent mode now) 46 tui_parser = subparsers.add_parser('tui', help='Start beautiful TUI with agent tools (can execute commands & modify files)') 47 tui_parser.add_argument( 48 '-t', '--temperature', 49 type=float, 50 help='Temperature for response generation (0.0 - 1.0)' 51 ) 52 tui_parser.add_argument( 53 '-f', '--files', 54 nargs='+', 55 help='Files to load into RAG context' 56 ) 57 58 # Interactive command (agent with tools) 59 interactive_parser = subparsers.add_parser('interactive', help='Start agent chat with filesystem and shell tools') 60 interactive_parser.add_argument( 61 '-t', '--temperature', 62 type=float, 63 help='Temperature for response generation (0.0 - 1.0)' 64 ) 65 interactive_parser.add_argument( 66 '-v', '--verbose', 67 action='store_true', 68 help='Show detailed agent reasoning' 69 ) 70 interactive_parser.add_argument( 71 '-f', '--files', 72 nargs='+', 73 help='Files to load into RAG context' 74 ) 75 76 # Chat command 77 chat_parser = subparsers.add_parser('chat', help='Start interactive chat with memory') 78 chat_parser.add_argument( 79 '-t', '--temperature', 80 type=float, 81 help='Temperature for response generation (0.0 - 1.0)' 82 ) 83 chat_parser.add_argument( 84 '-f', '--files', 85 nargs='+', 86 help='Files to load into conversation context' 87 ) 88 89 # Ask command 90 ask_parser = subparsers.add_parser('ask', help='Ask a single question') 91 ask_parser.add_argument('question', nargs='*', help='Question to ask') 92 ask_parser.add_argument( 93 '-t', '--temperature', 94 type=float, 95 help='Temperature for response generation (0.0 - 1.0)' 96 ) 97 ask_parser.add_argument( 98 '--no-stream', 99 action='store_true', 100 help='Disable streaming output' 101 ) 102 ask_parser.add_argument( 103 '-f', '--files', 104 nargs='+', 105 help='Files to include in context' 106 ) 107 108 # RAG command 109 rag_parser = subparsers.add_parser('rag', help='Query documents using RAG') 110 rag_parser.add_argument('query', nargs='*', help='Query to ask about the documents') 111 rag_parser.add_argument( 112 '-f', '--files', 113 nargs='+', 114 required=False, 115 help='Files to query' 116 ) 117 rag_parser.add_argument( 118 '-t', '--temperature', 119 type=float, 120 help='Temperature for response generation (0.0 - 1.0)' 121 ) 122 123 # Agent command 124 agent_parser = subparsers.add_parser('agent', help='Run agent with tools') 125 agent_parser.add_argument('task', nargs='*', help='Task for the agent') 126 agent_parser.add_argument( 127 '-t', '--temperature', 128 type=float, 129 help='Temperature for response generation (0.0 - 1.0)' 130 ) 131 agent_parser.add_argument( 132 '-v', '--verbose', 133 action='store_true', 134 help='Show agent reasoning process' 135 ) 136 137 # Work command (self-improvement with multiple modes) 138 work_parser = subparsers.add_parser('work', help='Work on tasks - supports prompts, paths, or internal task list') 139 work_parser.add_argument( 140 'target', 141 nargs='*', 142 help='What to work on (prompt, path, argument, or nothing for task list)' 143 ) 144 145 # Mature command (auto-analyze and suggest improvements) 146 mature_parser = subparsers.add_parser('mature', help='Analyze codebase and suggest improvements') 147 148 # Update command 149 update_parser = subparsers.add_parser('update', help='Update Kamaji to latest version') 150 update_parser.add_argument( 151 '-v', '--verbose', 152 action='store_true', 153 help='Show detailed output during update' 154 ) 155 156 # Queue command 157 queue_parser = subparsers.add_parser('queue', help='Add tasks to the task list') 158 queue_parser.add_argument('tasks', nargs='+', help='Comma-separated tasks to add') 159 160 # Tasks command 161 tasks_parser = subparsers.add_parser('tasks', help='Show the task list') 162 163 # Do command 164 do_parser = subparsers.add_parser('do', help='Select and execute a task from the list') 165 166 # Config command 167 config_parser = subparsers.add_parser('config', help='Manage configuration') 168 config_subparsers = config_parser.add_subparsers(dest='config_command') 169 170 # Config show (default) 171 config_subparsers.add_parser('show', help='Show current configuration') 172 173 # Config set 174 config_set_parser = config_subparsers.add_parser('set', help='Set configuration value') 175 config_set_parser.add_argument('key', help='Configuration key') 176 config_set_parser.add_argument('value', help='Configuration value') 177 178 # Config get 179 config_get_parser = config_subparsers.add_parser('get', help='Get configuration value') 180 config_get_parser.add_argument('key', help='Configuration key') 181 182 args = parser.parse_args() 183 184 # Handle no command 185 if not args.command: 186 parser.print_help() 187 sys.exit(0) 188 189 # Route to appropriate command 190 try: 191 if args.command == 'tui': 192 import subprocess 193 import os 194 # Change to project root where Cargo.toml is located 195 project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 196 os.chdir(project_root) 197 # Add cargo to PATH and run Rust TUI 198 env = os.environ.copy() 199 env["PATH"] = env.get("PATH", "") + ":~/.cargo/bin" 200 subprocess.run(["cargo", "run"], env=env) 201 202 elif args.command == 'interactive': 203 run_interactive(temperature=args.temperature, verbose=args.verbose, files=args.files) 204 205 elif args.command == 'chat': 206 run_chat(temperature=args.temperature, files=args.files) 207 208 elif args.command == 'ask': 209 question = ' '.join(args.question) if args.question else None 210 if not question: 211 print("❌ Error: Please provide a question", file=sys.stderr) 212 ask_parser.print_help() 213 sys.exit(1) 214 run_ask(question, temperature=args.temperature, stream=not args.no_stream, files=args.files) 215 216 elif args.command == 'rag': 217 # Handle query and files 218 if args.files: 219 # Format: kamaji rag -f file1.txt file2.txt "query here" 220 query = ' '.join(args.query) if args.query else None 221 files = args.files 222 else: 223 # Format: kamaji rag "query here" file1.txt file2.txt 224 # Split query and files 225 all_args = args.query if args.query else [] 226 if len(all_args) < 2: 227 print("❌ Error: Please provide a query and at least one file", file=sys.stderr) 228 rag_parser.print_help() 229 sys.exit(1) 230 231 # Find where files start (look for first arg that looks like a file) 232 query_parts = [] 233 files = [] 234 found_file = False 235 for arg in all_args: 236 if not found_file and (arg.endswith('.txt') or arg.endswith('.md') or '/' in arg or '\\' in arg): 237 found_file = True 238 if found_file: 239 files.append(arg) 240 else: 241 query_parts.append(arg) 242 243 query = ' '.join(query_parts) if query_parts else None 244 245 if not query or not files: 246 print("❌ Error: Please provide both a query and files", file=sys.stderr) 247 rag_parser.print_help() 248 sys.exit(1) 249 250 run_rag(query, files, temperature=args.temperature) 251 252 elif args.command == 'agent': 253 task = ' '.join(args.task) if args.task else None 254 if not task: 255 print("❌ Error: Please provide a task", file=sys.stderr) 256 agent_parser.print_help() 257 sys.exit(1) 258 run_agent(task, temperature=args.temperature, verbose=args.verbose) 259 260 elif args.command == 'work': 261 # Determine what kind of work target we have 262 target = ' '.join(args.target) if args.target else None 263 264 if target: 265 # Check if it's a path or a prompt 266 from pathlib import Path 267 if Path(target).exists() or '/' in target or '\\' in target: 268 # Looks like a path 269 exit_code = run_work(path=target) 270 else: 271 # Treat as a prompt 272 exit_code = run_work(prompt=target) 273 else: 274 # No target - use internal task list 275 exit_code = run_work() 276 277 sys.exit(exit_code) 278 279 elif args.command == 'mature': 280 exit_code = run_mature() 281 sys.exit(exit_code) 282 283 elif args.command == 'update': 284 exit_code = run_update(verbose=args.verbose) 285 sys.exit(exit_code) 286 287 elif args.command == 'queue': 288 from kamaji.commands.queue import run_queue 289 # Join all arguments and split by comma 290 tasks_str = ' '.join(args.tasks) 291 tasks = [task.strip() for task in tasks_str.split(',') if task.strip()] 292 run_queue(tasks) 293 294 elif args.command == 'tasks': 295 from kamaji.commands.tasks import run_tasks 296 run_tasks() 297 298 elif args.command == 'do': 299 from kamaji.commands.do import run_do 300 run_do() 301 302 elif args.command == 'config': 303 if not args.config_command or args.config_command == 'show': 304 show_config() 305 elif args.config_command == 'set': 306 set_config_value(args.key, args.value) 307 print(f"\n✓ Set {args.key} = {args.value}\n") 308 elif args.config_command == 'get': 309 value = get_config_value(args.key) 310 if value is not None: 311 print(f"\n{args.key} = {value}\n") 312 else: 313 print(f"\n❌ Key '{args.key}' not found\n", file=sys.stderr) 314 sys.exit(1) 315 316 except KeyboardInterrupt: 317 print("\n\n👋 Interrupted by user\n") 318 sys.exit(0) 319 except Exception as e: 320 print(f"\n❌ Error: {e}\n", file=sys.stderr) 321 sys.exit(1) 322 323 324 if __name__ == '__main__': 325 main()