/ archive / python / kamaji / cli.py
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()