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_tui, run_interactive, run_work, run_aide 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 # Aide command 167 aide_parser = subparsers.add_parser('aide', help='Launch aider with Ollama gpt-oss backend') 168 169 # Config command 170 config_parser = subparsers.add_parser('config', help='Manage configuration') 171 config_subparsers = config_parser.add_subparsers(dest='config_command') 172 173 # Config show (default) 174 config_subparsers.add_parser('show', help='Show current configuration') 175 176 # Config set 177 config_set_parser = config_subparsers.add_parser('set', help='Set configuration value') 178 config_set_parser.add_argument('key', help='Configuration key') 179 config_set_parser.add_argument('value', help='Configuration value') 180 181 # Config get 182 config_get_parser = config_subparsers.add_parser('get', help='Get configuration value') 183 config_get_parser.add_argument('key', help='Configuration key') 184 185 args = parser.parse_args() 186 187 # Handle no command 188 if not args.command: 189 parser.print_help() 190 sys.exit(0) 191 192 # Route to appropriate command 193 try: 194 if args.command == 'tui': 195 run_tui(temperature=args.temperature, files=args.files) 196 197 elif args.command == 'interactive': 198 run_interactive(temperature=args.temperature, verbose=args.verbose, files=args.files) 199 200 elif args.command == 'chat': 201 run_chat(temperature=args.temperature, files=args.files) 202 203 elif args.command == 'ask': 204 question = ' '.join(args.question) if args.question else None 205 if not question: 206 print("❌ Error: Please provide a question", file=sys.stderr) 207 ask_parser.print_help() 208 sys.exit(1) 209 run_ask(question, temperature=args.temperature, stream=not args.no_stream, files=args.files) 210 211 elif args.command == 'rag': 212 # Handle query and files 213 if args.files: 214 # Format: kamaji rag -f file1.txt file2.txt "query here" 215 query = ' '.join(args.query) if args.query else None 216 files = args.files 217 else: 218 # Format: kamaji rag "query here" file1.txt file2.txt 219 # Split query and files 220 all_args = args.query if args.query else [] 221 if len(all_args) < 2: 222 print("❌ Error: Please provide a query and at least one file", file=sys.stderr) 223 rag_parser.print_help() 224 sys.exit(1) 225 226 # Find where files start (look for first arg that looks like a file) 227 query_parts = [] 228 files = [] 229 found_file = False 230 for arg in all_args: 231 if not found_file and (arg.endswith('.txt') or arg.endswith('.md') or '/' in arg or '\\' in arg): 232 found_file = True 233 if found_file: 234 files.append(arg) 235 else: 236 query_parts.append(arg) 237 238 query = ' '.join(query_parts) if query_parts else None 239 240 if not query or not files: 241 print("❌ Error: Please provide both a query and files", file=sys.stderr) 242 rag_parser.print_help() 243 sys.exit(1) 244 245 run_rag(query, files, temperature=args.temperature) 246 247 elif args.command == 'agent': 248 task = ' '.join(args.task) if args.task else None 249 if not task: 250 print("❌ Error: Please provide a task", file=sys.stderr) 251 agent_parser.print_help() 252 sys.exit(1) 253 run_agent(task, temperature=args.temperature, verbose=args.verbose) 254 255 elif args.command == 'aide': 256 run_aide() 257 258 elif args.command == 'work': 259 # Determine what kind of work target we have 260 target = ' '.join(args.target) if args.target else None 261 262 if target: 263 # Check if it's a path or a prompt 264 from pathlib import Path 265 if Path(target).exists() or '/' in target or '\\' in target: 266 # Looks like a path 267 exit_code = run_work(path=target) 268 else: 269 # Treat as a prompt 270 exit_code = run_work(prompt=target) 271 else: 272 # No target - use internal task list 273 exit_code = run_work() 274 275 sys.exit(exit_code) 276 277 elif args.command == 'mature': 278 exit_code = run_mature() 279 sys.exit(exit_code) 280 281 elif args.command == 'update': 282 exit_code = run_update(verbose=args.verbose) 283 sys.exit(exit_code) 284 285 elif args.command == 'queue': 286 from kamaji.commands.queue import run_queue 287 # Join all arguments and split by comma 288 tasks_str = ' '.join(args.tasks) 289 tasks = [task.strip() for task in tasks_str.split(',') if task.strip()] 290 run_queue(tasks) 291 292 elif args.command == 'tasks': 293 from kamaji.commands.tasks import run_tasks 294 run_tasks() 295 296 elif args.command == 'do': 297 from kamaji.commands.do import run_do 298 run_do() 299 300 elif args.command == 'config': 301 if not args.config_command or args.config_command == 'show': 302 show_config() 303 elif args.config_command == 'set': 304 set_config_value(args.key, args.value) 305 print(f"\n✓ Set {args.key} = {args.value}\n") 306 elif args.config_command == 'get': 307 value = get_config_value(args.key) 308 if value is not None: 309 print(f"\n{args.key} = {value}\n") 310 else: 311 print(f"\n❌ Key '{args.key}' not found\n", file=sys.stderr) 312 sys.exit(1) 313 314 except KeyboardInterrupt: 315 print("\n\n👋 Interrupted by user\n") 316 sys.exit(0) 317 except Exception as e: 318 print(f"\n❌ Error: {e}\n", file=sys.stderr) 319 sys.exit(1) 320 321 322 if __name__ == '__main__': 323 main()