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()