gitarsenal-cli 1.9.65 → 1.9.67

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/.venv_status.json +1 -1
  2. package/bin/gitarsenal.js +24 -0
  3. package/kill_claude/.claude/settings.local.json +9 -0
  4. package/kill_claude/README.md +265 -0
  5. package/kill_claude/__pycache__/bash_output_tool.cpython-313.pyc +0 -0
  6. package/kill_claude/__pycache__/bash_tool.cpython-313.pyc +0 -0
  7. package/kill_claude/__pycache__/claude_code_agent.cpython-313.pyc +0 -0
  8. package/kill_claude/__pycache__/edit_tool.cpython-313.pyc +0 -0
  9. package/kill_claude/__pycache__/exit_plan_mode_tool.cpython-313.pyc +0 -0
  10. package/kill_claude/__pycache__/glob_tool.cpython-313.pyc +0 -0
  11. package/kill_claude/__pycache__/grep_tool.cpython-313.pyc +0 -0
  12. package/kill_claude/__pycache__/kill_bash_tool.cpython-313.pyc +0 -0
  13. package/kill_claude/__pycache__/ls_tool.cpython-313.pyc +0 -0
  14. package/kill_claude/__pycache__/multiedit_tool.cpython-313.pyc +0 -0
  15. package/kill_claude/__pycache__/notebook_edit_tool.cpython-313.pyc +0 -0
  16. package/kill_claude/__pycache__/read_tool.cpython-313.pyc +0 -0
  17. package/kill_claude/__pycache__/task_tool.cpython-313.pyc +0 -0
  18. package/kill_claude/__pycache__/todo_write_tool.cpython-313.pyc +0 -0
  19. package/kill_claude/__pycache__/web_fetch_tool.cpython-313.pyc +0 -0
  20. package/kill_claude/__pycache__/web_search_tool.cpython-313.pyc +0 -0
  21. package/kill_claude/__pycache__/write_tool.cpython-313.pyc +0 -0
  22. package/kill_claude/claude_code_agent.py +848 -0
  23. package/kill_claude/prompts/claude-code-agent-prompts.md +65 -0
  24. package/kill_claude/prompts/claude-code-environment-context.md +100 -0
  25. package/kill_claude/prompts/claude-code-git-workflows.md +151 -0
  26. package/kill_claude/prompts/claude-code-hook-system.md +94 -0
  27. package/kill_claude/prompts/claude-code-response-formatting.md +79 -0
  28. package/kill_claude/prompts/claude-code-security-constraints.md +87 -0
  29. package/kill_claude/prompts/claude-code-system-prompt.md +136 -0
  30. package/kill_claude/prompts/claude-code-system-reminders.md +50 -0
  31. package/kill_claude/prompts/claude-code-task-workflows.md +114 -0
  32. package/kill_claude/prompts/claude-code-thinking-mode-prompts.md +39 -0
  33. package/kill_claude/prompts/claude-code-tool-prompts.md +339 -0
  34. package/kill_claude/prompts/claude-code-tool-usage-policies.md +87 -0
  35. package/kill_claude/requirements.txt +1 -0
  36. package/kill_claude/tools/__init__.py +1 -0
  37. package/kill_claude/tools/__pycache__/bash_output_tool.cpython-313.pyc +0 -0
  38. package/kill_claude/tools/__pycache__/bash_tool.cpython-313.pyc +0 -0
  39. package/kill_claude/tools/__pycache__/edit_tool.cpython-313.pyc +0 -0
  40. package/kill_claude/tools/__pycache__/exit_plan_mode_tool.cpython-313.pyc +0 -0
  41. package/kill_claude/tools/__pycache__/glob_tool.cpython-313.pyc +0 -0
  42. package/kill_claude/tools/__pycache__/grep_tool.cpython-313.pyc +0 -0
  43. package/kill_claude/tools/__pycache__/kill_bash_tool.cpython-313.pyc +0 -0
  44. package/kill_claude/tools/__pycache__/ls_tool.cpython-313.pyc +0 -0
  45. package/kill_claude/tools/__pycache__/multiedit_tool.cpython-313.pyc +0 -0
  46. package/kill_claude/tools/__pycache__/notebook_edit_tool.cpython-313.pyc +0 -0
  47. package/kill_claude/tools/__pycache__/read_tool.cpython-313.pyc +0 -0
  48. package/kill_claude/tools/__pycache__/task_tool.cpython-313.pyc +0 -0
  49. package/kill_claude/tools/__pycache__/todo_write_tool.cpython-313.pyc +0 -0
  50. package/kill_claude/tools/__pycache__/web_fetch_tool.cpython-313.pyc +0 -0
  51. package/kill_claude/tools/__pycache__/web_search_tool.cpython-313.pyc +0 -0
  52. package/kill_claude/tools/__pycache__/write_tool.cpython-313.pyc +0 -0
  53. package/kill_claude/tools/bash_output_tool.py +47 -0
  54. package/kill_claude/tools/bash_tool.py +79 -0
  55. package/kill_claude/tools/edit_tool.py +60 -0
  56. package/kill_claude/tools/exit_plan_mode_tool.py +42 -0
  57. package/kill_claude/tools/glob_tool.py +46 -0
  58. package/kill_claude/tools/grep_tool.py +105 -0
  59. package/kill_claude/tools/kill_bash_tool.py +38 -0
  60. package/kill_claude/tools/ls_tool.py +44 -0
  61. package/kill_claude/tools/multiedit_tool.py +111 -0
  62. package/kill_claude/tools/notebook_edit_tool.py +64 -0
  63. package/kill_claude/tools/read_tool.py +61 -0
  64. package/kill_claude/tools/task_tool.py +67 -0
  65. package/kill_claude/tools/todo_write_tool.py +114 -0
  66. package/kill_claude/tools/web_fetch_tool.py +55 -0
  67. package/kill_claude/tools/web_search_tool.py +58 -0
  68. package/kill_claude/tools/write_tool.py +46 -0
  69. package/lib/sandbox.js +3 -0
  70. package/package.json +1 -1
  71. package/python/test_modalSandboxScript.py +144 -295
  72. package/python/gitarsenal.py +0 -488
@@ -162,7 +162,10 @@ if token_id is None or token_secret is None:
162
162
  # Explicitly set the environment variables again to be sure
163
163
  os.environ["MODAL_TOKEN_ID"] = token_id
164
164
  os.environ["MODAL_TOKEN_SECRET"] = token_secret
165
- os.environ["OPENAI_API_KEY"] = openai_api_key
165
+ if openai_api_key:
166
+ os.environ["OPENAI_API_KEY"] = openai_api_key
167
+ if anthropic_api_key:
168
+ os.environ["ANTHROPIC_API_KEY"] = anthropic_api_key
166
169
  # Also set the old environment variable for backward compatibility
167
170
  os.environ["MODAL_TOKEN"] = token_id
168
171
 
@@ -198,7 +201,11 @@ def get_stored_credentials():
198
201
  # Now modify the create_modal_ssh_container function to use the PersistentShell
199
202
  def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_commands=None,
200
203
  volume_name=None, timeout_minutes=60, ssh_password=None, interactive=False, gpu_count=1):
201
- """Create a Modal SSH container with GPU support and tunneling"""
204
+ """Create a Modal SSH container with GPU support and intelligent repository setup.
205
+
206
+ When repo_url is provided, uses Claude Code Agent for intelligent repository setup.
207
+ The setup_commands parameter is maintained for backwards compatibility but ignored when using Claude Code Agent.
208
+ """
202
209
 
203
210
  # Use interactive mode if specified
204
211
  if interactive:
@@ -346,6 +353,8 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
346
353
 
347
354
  # Get the current directory path for mounting local Python sources
348
355
  current_dir = os.path.dirname(os.path.abspath(__file__))
356
+ # Get the gitarsenal-cli root directory for kill_claude files
357
+ gitarsenal_root = os.path.dirname(current_dir)
349
358
  # print(f"🔍 Current directory for mounting: {current_dir}")
350
359
 
351
360
  # Use a more stable CUDA base image and avoid problematic packages
@@ -378,12 +387,30 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
378
387
 
379
388
  # Set up a nice bash prompt
380
389
  "echo 'export PS1=\"\\[\\e[1;32m\\]modal:\\[\\e[1;34m\\]\\w\\[\\e[0m\\]$ \"' >> /root/.bashrc",
390
+
391
+ # Create directories for Claude Code Agent
392
+ "mkdir -p /python/kill_claude/tools /python/kill_claude/prompts",
381
393
  )
394
+ # Mount all local files (must be done after run_commands)
382
395
  .add_local_file(os.path.join(current_dir, "shell.py"), "/python/shell.py") # Mount shell.py
383
396
  .add_local_file(os.path.join(current_dir, "command_manager.py"), "/python/command_manager.py") # Mount command_manager.py
384
397
  .add_local_file(os.path.join(current_dir, "fetch_modal_tokens.py"), "/python/fetch_modal_tokens.py") # Mount fetch_modal_token.py
385
398
  .add_local_file(os.path.join(current_dir, "llm_debugging.py"), "/python/llm_debugging.py") # Mount llm_debugging.py
386
399
  .add_local_file(os.path.join(current_dir, "credentials_manager.py"), "/python/credentials_manager.py") # Mount credentials_manager.py
400
+ # Mount Claude Code Agent and its dependencies
401
+ .add_local_file(os.path.join(gitarsenal_root, "kill_claude", "claude_code_agent.py"), "/python/kill_claude/claude_code_agent.py")
402
+ # Mount the tools and prompts directories manually with key files
403
+ .add_local_file(os.path.join(gitarsenal_root, "kill_claude", "tools", "__init__.py"), "/python/kill_claude/tools/__init__.py")
404
+ .add_local_file(os.path.join(gitarsenal_root, "kill_claude", "tools", "bash_tool.py"), "/python/kill_claude/tools/bash_tool.py")
405
+ .add_local_file(os.path.join(gitarsenal_root, "kill_claude", "tools", "read_tool.py"), "/python/kill_claude/tools/read_tool.py")
406
+ .add_local_file(os.path.join(gitarsenal_root, "kill_claude", "tools", "write_tool.py"), "/python/kill_claude/tools/write_tool.py")
407
+ .add_local_file(os.path.join(gitarsenal_root, "kill_claude", "tools", "ls_tool.py"), "/python/kill_claude/tools/ls_tool.py")
408
+ .add_local_file(os.path.join(gitarsenal_root, "kill_claude", "tools", "edit_tool.py"), "/python/kill_claude/tools/edit_tool.py")
409
+ .add_local_file(os.path.join(gitarsenal_root, "kill_claude", "tools", "grep_tool.py"), "/python/kill_claude/tools/grep_tool.py")
410
+ .add_local_file(os.path.join(gitarsenal_root, "kill_claude", "tools", "glob_tool.py"), "/python/kill_claude/tools/glob_tool.py")
411
+ .add_local_file(os.path.join(gitarsenal_root, "kill_claude", "tools", "todo_write_tool.py"), "/python/kill_claude/tools/todo_write_tool.py")
412
+ .add_local_file(os.path.join(gitarsenal_root, "kill_claude", "prompts", "claude-code-system-prompt.md"), "/python/kill_claude/prompts/claude-code-system-prompt.md")
413
+ .add_local_file(os.path.join(gitarsenal_root, "kill_claude", "prompts", "claude-code-agent-prompts.md"), "/python/kill_claude/prompts/claude-code-agent-prompts.md")
387
414
 
388
415
  )
389
416
  print("✅ SSH image built successfully")
@@ -409,8 +436,8 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
409
436
  serialized=True,
410
437
  volumes=volumes_config if volumes_config else None,
411
438
  )
412
- def ssh_container_function(ssh_password=None, repo_url=None, repo_name=None, setup_commands=None, openai_api_key=None, stored_credentials=None):
413
- """Start SSH container with password authentication and optional setup."""
439
+ def ssh_container_function(ssh_password=None, repo_url=None, repo_name=None, setup_commands=None, openai_api_key=None, anthropic_api_key=None, stored_credentials=None):
440
+ """Start SSH container with password authentication and intelligent repository setup using Claude Code Agent."""
414
441
  import subprocess
415
442
  import time
416
443
  import os
@@ -487,273 +514,96 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
487
514
  # Start SSH service
488
515
  subprocess.run(["service", "ssh", "start"], check=True)
489
516
 
490
- # Preprocess setup commands using LLM to inject credentials
491
- if setup_commands:
492
- print(f"🔧 Preprocessing {len(setup_commands)} setup commands with LLM to inject credentials...")
493
- api_key = os.environ.get("OPENAI_API_KEY")
494
- processed_commands = preprocess_commands_with_llm(setup_commands, stored_credentials, api_key)
495
- print(f"⚙️ Running {len(processed_commands)} preprocessed setup commands with dynamic command list...")
496
-
497
- # Display all setup commands that will be executed
498
- print("\n" + "="*80)
499
- print("📋 SETUP COMMANDS TO BE EXECUTED")
500
- print("="*80)
501
- for i, cmd in enumerate(processed_commands, 1):
502
- print(f" {i:2d}. {cmd}")
503
- print("="*80 + "\n")
517
+ # Use Claude Code Agent for intelligent repository setup
518
+ if repo_url:
519
+ print("🤖 Using Claude Code Agent for intelligent repository setup...")
504
520
 
505
- # Create command list manager with processed commands
506
- cmd_manager = CommandListManager(processed_commands)
521
+ # Set up environment variables for the Claude Code Agent
522
+ if openai_api_key:
523
+ os.environ['OPENAI_API_KEY'] = openai_api_key
524
+ if anthropic_api_key:
525
+ os.environ['ANTHROPIC_API_KEY'] = anthropic_api_key
507
526
 
508
- # Create persistent shell instance starting in /root
509
- shell = PersistentShell(working_dir="/root", timeout=300)
527
+ # Set up Anthropic API key from stored credentials
528
+ anthropic_api_key = None
529
+ if stored_credentials:
530
+ # Look for Anthropic API key in various possible names
531
+ for key_name in ['ANTHROPIC_API_KEY', 'anthropic_api_key', 'anthropic-api-key']:
532
+ if key_name in stored_credentials:
533
+ anthropic_api_key = stored_credentials[key_name]
534
+ os.environ['ANTHROPIC_API_KEY'] = anthropic_api_key
535
+ print(f"✅ Set Anthropic API key from stored credentials")
536
+ break
537
+
538
+ if not anthropic_api_key:
539
+ print("⚠️ No Anthropic API key found in stored credentials")
540
+ print("💡 Claude Code Agent will require an Anthropic API key for operation")
510
541
 
511
542
  try:
512
- # Start the persistent shell
513
- shell.start()
543
+ print("🔧 Running Claude Code Agent for repository setup...")
514
544
 
515
- # Track how many commands have been executed since last analysis
516
- commands_since_analysis = 0
545
+ print("\n" + "="*80)
546
+ print("🤖 CLAUDE CODE AGENT REPOSITORY SETUP")
547
+ print("="*80)
548
+ print(f"Repository: {repo_url}")
549
+ print(f"Working Directory: /root")
550
+ if stored_credentials:
551
+ print(f"Available Credentials: {len(stored_credentials)} items")
552
+ print("="*80 + "\n")
517
553
 
518
- # Execute commands using the command list manager
519
- while cmd_manager.has_pending_commands():
520
- # Get next command to execute
521
- next_cmd, cmd_type = cmd_manager.get_next_command()
522
-
523
- if not next_cmd:
524
- break
525
-
526
- # Box everything printed in this iteration into one block
527
- with _boxed_capture():
528
- # Print status before executing
529
- cmd_manager.print_status()
530
-
531
- # Periodically analyze and update the command list
532
- if commands_since_analysis >= 3 and cmd_type == 'main':
533
- print("\n🔍 Periodic command list analysis...")
534
- api_key = os.environ.get("OPENAI_API_KEY")
535
- cmd_manager.update_command_list_with_llm(api_key)
536
- commands_since_analysis = 0
537
-
538
- # Execute the command
539
- if cmd_type == 'main':
540
- cmd_text = next_cmd['command']
541
- cmd_index = next_cmd['index']
542
- success, stdout, stderr, execution_time = _execute_with_box(
543
- shell, cmd_text, timeout=100,
544
- title_line=f"📋 Executing main command {cmd_index + 1}/{cmd_manager.total_commands}: {cmd_text}",
545
- box=False
546
- )
547
-
548
- # Check if the command was aborted due to waiting for input and an alternative was suggested
549
- if not success and "Command aborted - requires user input" in stderr and shell.suggested_alternative:
550
- alternative_cmd = shell.suggested_alternative
551
- print(f"🔄 Command aborted due to input requirement. Adding suggested alternative: {alternative_cmd}")
552
-
553
- # Add the alternative command with high priority
554
- cmd_manager.add_command_dynamically(alternative_cmd, priority='high')
555
-
556
- # Clear the suggested alternative
557
- shell.suggested_alternative = None
558
- # Check if the command should be removed as suggested by LLM
559
- elif not success and stderr.startswith("Command removed -"):
560
- reason = stderr.replace("Command removed -", "").strip()
561
- print(f"🚫 Removed command as suggested by LLM: {reason}")
562
- # We don't need to do anything else, just mark it as executed and move on
563
-
564
- # Mark command as executed
565
- cmd_manager.mark_command_executed(
566
- cmd_index, 'main', success, stdout, stderr, execution_time
567
- )
568
-
569
- # Increment counter for periodic analysis
570
- commands_since_analysis += 1
571
-
572
- if not success:
573
- print(f"⚠️ Command failed, attempting LLM debugging...")
574
-
575
- # Call LLM for debugging
576
- try:
577
- current_dir = shell.get_cwd()
578
-
579
- # Use unified LLM debugging function
580
- fix_command = call_llm_for_debug(cmd_text, stderr, current_dir=current_dir, sandbox=shell)
581
-
582
- if fix_command:
583
- # Get the current debug model to show the correct provider name
584
- from llm_debugging import get_current_debug_model
585
- current_model = get_current_debug_model()
586
- print(f"🔧 {current_model.capitalize()} suggested fix command: {fix_command}")
587
-
588
- # Add the fix to the command list manager
589
- fix_index = cmd_manager.add_suggested_fix(cmd_text, fix_command, "LLM suggested fix")
590
-
591
- # Execute the fix command
592
- fix_success, fix_stdout, fix_stderr, fix_execution_time = _execute_with_box(
593
- shell, fix_command, timeout=300,
594
- title_line=f"🔧 Executing suggested fix: {fix_command}",
595
- box=False
596
- )
597
-
598
- # Mark fix command as executed
599
- cmd_manager.mark_command_executed(
600
- fix_index, 'fix', fix_success, fix_stdout, fix_stderr, fix_execution_time
601
- )
602
-
603
- if fix_success:
604
- print(f"✅ Fix command succeeded")
605
-
606
- # Check if we should skip the original command
607
- api_key = os.environ.get("OPENAI_API_KEY")
608
- should_skip, skip_reason = cmd_manager.should_skip_original_command(
609
- cmd_text, fix_command, fix_stdout, fix_stderr, api_key
610
- )
611
-
612
- if should_skip:
613
- print(f"🔄 Skipping original command: {skip_reason}")
614
-
615
- # Mark the original command as successful without running it
616
- cmd_manager.mark_command_executed(
617
- cmd_index, 'main', True,
618
- f"Command skipped after successful fix: {skip_reason}",
619
- "", execution_time
620
- )
621
-
622
- print(f"✅ Original command marked as successful (skipped)")
623
-
624
- # After a successful fix and skipping the original command,
625
- # analyze and update the entire command list
626
- print("\n🔍 Analyzing and updating remaining commands based on fix results...")
627
- cmd_manager.update_command_list_with_llm(api_key)
628
- else:
629
- # Retry the original command
630
- print(f"🔄 Retrying original command: {cmd_text}")
631
- retry_success, retry_stdout, retry_stderr, retry_execution_time = _execute_with_box(
632
- shell, cmd_text, timeout=300,
633
- title_line=f"🔄 Retrying command: {cmd_text}",
634
- box=False
635
- )
636
-
637
- # Update the original command status
638
- cmd_manager.mark_command_executed(
639
- cmd_index, 'main', retry_success, retry_stdout, retry_stderr, retry_execution_time
640
- )
641
-
642
- if retry_success:
643
- print(f"✅ Original command succeeded after fix!")
644
-
645
- # After a successful fix and successful retry,
646
- # analyze and update the entire command list
647
- print("\n🔍 Analyzing and updating remaining commands based on fix results...")
648
- cmd_manager.update_command_list_with_llm(api_key)
649
- else:
650
- print(f"⚠️ Original command still failed after fix, continuing...")
651
- else:
652
- print(f"❌ Fix command failed: {fix_stderr}")
653
- print(f"⚠️ First fix attempt failed; continuing without web search...")
654
-
655
- # No web-search retry; continue
656
- print(f"⚠️ Continuing with remaining commands...")
657
- else:
658
- # Get the current debug model to show the correct provider name
659
- from llm_debugging import get_current_debug_model
660
- current_model = get_current_debug_model()
661
- print(f"❌ No fix suggested by {current_model.capitalize()}")
662
- print(f"⚠️ Continuing with remaining commands...")
663
-
664
- except Exception as debug_e:
665
- print(f"❌ LLM debugging failed: {debug_e}")
666
- print(f"⚠️ Continuing with remaining commands...")
667
-
668
- elif cmd_type == 'fix':
669
- cmd_text = next_cmd['fix_command']
670
- cmd_index = next_cmd['index']
671
- success, stdout, stderr, execution_time = _execute_with_box(
672
- shell, cmd_text, timeout=300,
673
- title_line=f"🔧 Executing fix command {cmd_index + 1}: {cmd_text}",
674
- box=False
675
- )
676
-
677
- # Check if the fix command was aborted due to waiting for input and an alternative was suggested
678
- if not success and "Command aborted - requires user input" in stderr and shell.suggested_alternative:
679
- alternative_cmd = shell.suggested_alternative
680
- print(f"🔄 Fix command aborted due to input requirement. Adding suggested alternative: {alternative_cmd}")
681
-
682
- # Add the alternative command with high priority
683
- cmd_manager.add_command_dynamically(alternative_cmd, priority='high')
684
-
685
- # Clear the suggested alternative
686
- shell.suggested_alternative = None
687
- # Check if the fix command should be removed as suggested by LLM
688
- elif not success and stderr.startswith("Command removed -"):
689
- reason = stderr.replace("Command removed -", "").strip()
690
- print(f"🚫 Removed fix command as suggested by LLM: {reason}")
691
- # We don't need to do anything else, just mark it as executed and move on
692
-
693
- # Mark fix command as executed
694
- cmd_manager.mark_command_executed(
695
- cmd_index, 'fix', success, stdout, stderr, execution_time
696
- )
554
+ # Call Claude Code Agent directly as subprocess with real-time output
555
+ claude_prompt = f"clone and setup {repo_url}"
556
+ print(f"🚀 Executing: python /python/kill_claude/claude_code_agent.py \"{claude_prompt}\"")
557
+ print("\n" + "="*60)
558
+ print("🎉 CLAUDE CODE AGENT OUTPUT (LIVE)")
559
+ print("="*60)
697
560
 
698
- # After all commands are processed, do a final batch analysis of any remaining failed commands
699
- failed_commands = cmd_manager.get_failed_commands_for_llm()
700
- if failed_commands:
701
- print(f"\n🔍 Final batch analysis of {len(failed_commands)} failed commands...")
702
- current_dir = shell.get_cwd()
561
+ # Use Popen for real-time output streaming
562
+ process = subprocess.Popen(
563
+ ["python", "/python/kill_claude/claude_code_agent.py", claude_prompt],
564
+ cwd="/root",
565
+ stdout=subprocess.PIPE,
566
+ stderr=subprocess.STDOUT, # Merge stderr into stdout
567
+ text=True,
568
+ bufsize=1, # Line buffered
569
+ universal_newlines=True
570
+ )
571
+
572
+ # Stream output in real-time
573
+ try:
574
+ while True:
575
+ output = process.stdout.readline()
576
+ if output == '' and process.poll() is not None:
577
+ break
578
+ if output:
579
+ print(output.rstrip())
703
580
 
704
- # Get the correct API key for the current debug model
705
- from llm_debugging import get_current_debug_model, get_api_key
706
- current_model = get_current_debug_model()
707
- api_key = get_api_key(current_model)
581
+ # Wait for process to complete and get return code
582
+ return_code = process.wait(timeout=600) # 10 minute timeout
708
583
 
709
- # Use batch analysis to get additional fixes
710
- additional_fixes = cmd_manager.analyze_failed_commands_with_llm(api_key, current_dir, shell)
584
+ print("\n" + "="*60)
585
+ if return_code == 0:
586
+ print("✅ Claude Code Agent completed successfully!")
587
+ else:
588
+ print(f"⚠️ Claude Code Agent exited with code: {return_code}")
589
+ print("="*60)
711
590
 
712
- if additional_fixes:
713
- print(f"🔧 Executing {len(additional_fixes)} additional fix commands...")
714
-
715
- # Execute the additional fix commands
716
- for fix_index in additional_fixes:
717
- fix_cmd = cmd_manager.suggested_fixes[fix_index]
718
- cmd_text = fix_cmd['fix_command']
719
- success, stdout, stderr, execution_time = _execute_with_box(
720
- shell, cmd_text, timeout=300,
721
- title_line=f"🔧 Executing additional fix: {cmd_text}",
722
- box=False
723
- )
724
-
725
- # Check if the fix command was aborted due to waiting for input and an alternative was suggested
726
- if not success and "Command aborted - requires user input" in stderr and shell.suggested_alternative:
727
- alternative_cmd = shell.suggested_alternative
728
- print(f"🔄 Additional fix command aborted due to input requirement. Adding suggested alternative: {alternative_cmd}")
729
-
730
- # Add the alternative command with high priority
731
- cmd_manager.add_command_dynamically(alternative_cmd, priority='high')
732
-
733
- # Clear the suggested alternative
734
- shell.suggested_alternative = None
735
- # Check if the additional fix command should be removed as suggested by LLM
736
- elif not success and stderr.startswith("Command removed -"):
737
- reason = stderr.replace("Command removed -", "").strip()
738
- print(f"🚫 Removed additional fix command as suggested by LLM: {reason}")
739
- # We don't need to do anything else, just mark it as executed and move on
740
-
741
- # Mark fix command as executed
742
- cmd_manager.mark_command_executed(
743
- fix_index, 'fix', success, stdout, stderr, execution_time
744
- )
745
-
746
- # Print final status
747
- print("\n" + "="*60)
748
- print("🎉 SETUP COMMANDS EXECUTION COMPLETED")
749
- print("="*60)
750
- cmd_manager.print_status()
591
+ except subprocess.TimeoutExpired:
592
+ print("\n⚠️ Claude Code Agent timed out after 10 minutes")
593
+ process.kill()
594
+ process.wait()
595
+ except Exception as stream_error:
596
+ print(f"\n⚠️ Error streaming output: {stream_error}")
597
+ process.kill()
598
+ process.wait()
751
599
 
752
600
  except Exception as e:
753
- print(f"❌ Error during setup command execution: {e}")
754
- finally:
755
- # Clean up the shell
756
- shell.cleanup()
601
+ print(f"❌ Error during repository setup: {e}")
602
+ print("⚠️ Proceeding without setup...")
603
+ import traceback
604
+ traceback.print_exc()
605
+ else:
606
+ print("⚠️ No repository URL provided, skipping setup")
757
607
 
758
608
  # Create SSH tunnel
759
609
  with modal.forward(22, unencrypted=True) as tunnel:
@@ -791,15 +641,17 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
791
641
  with app.run():
792
642
  # Get the API key from environment
793
643
  api_key = os.environ.get("OPENAI_API_KEY")
644
+ # print(f"🔐 API key: {api_key}")
794
645
 
795
646
  # Get stored credentials from local file
796
647
  stored_credentials = get_stored_credentials()
648
+ # print(f"🔐 Stored credentials: {stored_credentials}")
797
649
  if stored_credentials:
798
650
  print(f"🔐 Found {len(stored_credentials)} stored credentials to send to container")
799
651
  else:
800
652
  print("⚠️ No stored credentials found")
801
653
 
802
- ssh_container_function.remote(ssh_password, repo_url, repo_name, setup_commands, api_key, stored_credentials)
654
+ ssh_container_function.remote(ssh_password, repo_url, repo_name, setup_commands, api_key, anthropic_api_key, stored_credentials)
803
655
 
804
656
  # Clean up Modal token after container is successfully created
805
657
  cleanup_modal_token()
@@ -1256,17 +1108,12 @@ def show_usage_examples():
1256
1108
  print("│ gitarsenal --auth # Interactive auth management │")
1257
1109
  print("└────────────────────────────────────────────────────────────────────────────────────┘\n")
1258
1110
 
1259
- print("Basic Container Creation")
1111
+ print("Basic Container Creation with Claude Code Agent")
1260
1112
  print("┌────────────────────────────────────────────────────────────────────────┐")
1261
1113
  print("│ gitarsenal --gpu A10G --repo-url https://github.com/username/repo.git │")
1114
+ print("│ # Claude Code Agent will intelligently clone and setup the repository │")
1262
1115
  print("└────────────────────────────────────────────────────────────────────────┘\n")
1263
1116
 
1264
- print("With Setup Commands")
1265
- print("┌────────────────────────────────────────────────────────────────────────────────────────────────────┐")
1266
- print("│ gitarsenal --gpu A100 --repo-url https://github.com/username/repo.git \\ │")
1267
- print("│ --setup-commands \"pip install -r requirements.txt\" \"python setup.py install\" │")
1268
- print("└────────────────────────────────────────────────────────────────────────────────────────────────────┘\n")
1269
-
1270
1117
  print("With Persistent Storage")
1271
1118
  print("┌────────────────────────────────────────────────────────────────────────────────────┐")
1272
1119
  print("│ gitarsenal --gpu A10G --repo-url https://github.com/username/repo.git \\ │")
@@ -1279,21 +1126,16 @@ def show_usage_examples():
1279
1126
  print("│ --repo-url https://github.com/username/repo.git │")
1280
1127
  print("└────────────────────────────────────────────────────────────────────────────────────┘\n")
1281
1128
 
1282
- print("With GitIngest API (default)")
1129
+ print("Intelligent Repository Setup (default)")
1283
1130
  print("┌────────────────────────────────────────────────────────────────────────────────────┐")
1284
1131
  print("│ gitarsenal --gpu A10G --repo-url https://github.com/username/repo.git │")
1132
+ print("│ # Claude Code Agent analyzes repo and sets up environment automatically │")
1285
1133
  print("└────────────────────────────────────────────────────────────────────────────────────┘\n")
1286
1134
 
1287
- print("Without GitIngest API")
1288
- print("┌────────────────────────────────────────────────────────────────────────────────────┐")
1289
- print("│ gitarsenal --gpu A10G --repo-url https://github.com/username/repo.git \\ │")
1290
- print("│ --no-gitingest │")
1291
- print("└────────────────────────────────────────────────────────────────────────────────────┘\n")
1292
-
1293
- print("With Original API")
1135
+ print("With Manual Setup Commands (Advanced)")
1294
1136
  print("┌────────────────────────────────────────────────────────────────────────────────────┐")
1295
- print("│ gitarsenal --gpu A10G --repo-url https://github.com/username/repo.git \\ │")
1296
- print("│ --use-api │")
1137
+ print("│ gitarsenal --gpu A10G --setup-commands \"pip install torch\" \"python train.py\" │")
1138
+ print("│ # Only use when not providing --repo-url (bypasses Claude Code Agent) │")
1297
1139
  print("└────────────────────────────────────────────────────────────────────────────────────┘\n")
1298
1140
 
1299
1141
  print("Development Mode (Skip Authentication)")
@@ -1314,15 +1156,20 @@ def show_usage_examples():
1314
1156
  print(" • With --gpu: Uses specified GPU without prompting")
1315
1157
  print(" • Without --gpu: Shows interactive GPU selection menu")
1316
1158
  print()
1159
+ print("Repository Setup Behavior:")
1160
+ print(" • With --repo-url: Claude Code Agent intelligently clones and sets up repository")
1161
+ print(" • Without --repo-url: Manual container setup (no automatic repository setup)")
1162
+ print(" • Legacy --setup-commands: Only used when --repo-url not provided")
1163
+ print()
1317
1164
  print("Examples:")
1318
- print(" # First time setup (will prompt for registration):")
1165
+ print(" # Intelligent repository setup (recommended):")
1319
1166
  print(" gitarsenal --gpu A10G --repo-url https://github.com/username/repo.git")
1320
1167
  print()
1321
- print(" # Subsequent runs (automatic login):")
1322
- print(" gitarsenal --repo-url https://github.com/username/repo.git")
1323
- print()
1324
1168
  print(" # Development mode (skip authentication):")
1325
- print(" gitarsenal --skip-auth --repo-url https://github.com/username/repo.git")
1169
+ print(" gitarsenal --skip-auth --gpu A10G --repo-url https://github.com/username/repo.git")
1170
+ print()
1171
+ print(" # Manual setup (advanced users):")
1172
+ print(" gitarsenal --gpu A10G --setup-commands \"pip install torch\" \"python train.py\"")
1326
1173
 
1327
1174
  def make_api_request_with_retry(url, payload, max_retries=2, timeout=180):
1328
1175
  """Make an API request with retry mechanism."""
@@ -1984,9 +1831,9 @@ if __name__ == "__main__":
1984
1831
  parser.add_argument('--volume-name', type=str, help='Name of the Modal volume for persistent storage')
1985
1832
  parser.add_argument('--timeout', type=int, default=60, help='Container timeout in minutes (default: 60)')
1986
1833
  parser.add_argument('--ssh-password', type=str, help='SSH password (random if not provided)')
1987
- parser.add_argument('--use-api', action='store_true', help='Fetch setup commands from original API')
1988
- parser.add_argument('--use-gitingest', action='store_true', default=True, help='Use gitingest approach to fetch setup commands (default)')
1989
- parser.add_argument('--no-gitingest', action='store_true', help='Disable gitingest approach for setup commands')
1834
+ parser.add_argument('--use-api', action='store_true', help='[DEPRECATED] Fetch setup commands from original API (use --repo-url for Claude Code Agent instead)')
1835
+ parser.add_argument('--use-gitingest', action='store_true', default=True, help='[DEPRECATED] Use gitingest approach (Claude Code Agent is now used when --repo-url is provided)')
1836
+ parser.add_argument('--no-gitingest', action='store_true', help='[DEPRECATED] Disable gitingest approach (no longer needed with Claude Code Agent)')
1990
1837
  parser.add_argument('--show-examples', action='store_true', help='Show usage examples')
1991
1838
  parser.add_argument('--list-gpus', action='store_true', help='List available GPU types with their specifications')
1992
1839
  parser.add_argument('--interactive', action='store_true', help='Run in interactive mode with prompts')
@@ -2118,12 +1965,14 @@ if __name__ == "__main__":
2118
1965
  else:
2119
1966
  print(f"GPU Type: {gpu_type}")
2120
1967
  print(f"Volume: {args.volume_name or 'None'}")
2121
- if args.use_api:
1968
+ if args.repo_url:
1969
+ print("Repository Setup: Claude Code Agent (intelligent)")
1970
+ elif args.use_api:
2122
1971
  print("Setup Commands: Auto-detect from repository")
2123
1972
  elif args.setup_commands:
2124
1973
  print(f"Setup Commands: {len(args.setup_commands)} custom commands")
2125
1974
  else:
2126
- print("Setup Commands: Auto-detect from repository")
1975
+ print("Setup Commands: None")
2127
1976
 
2128
1977
  # Confirm settings (skip if --yes specified)
2129
1978
  if not getattr(args, 'yes', False):
@@ -2203,15 +2052,15 @@ if __name__ == "__main__":
2203
2052
  args.use_gitingest = use_gitingest
2204
2053
 
2205
2054
  try:
2206
- # Get setup commands from file if specified
2055
+ # Setup commands are no longer used when repo_url is provided (Claude Code Agent handles setup)
2207
2056
  setup_commands = args.setup_commands or []
2208
2057
 
2209
- # Use gitingest by default unless --no-gitingest is set
2210
- if args.repo_url and (args.use_gitingest and not args.no_gitingest):
2211
- # print("🔄 Using gitingest approach to fetch setup commands (default)")
2212
- api_commands = get_setup_commands_from_gitingest(args.repo_url)
2213
- setup_commands = api_commands
2214
- print(f"📋 Using {len(setup_commands)} commands from gitingest API")
2058
+ # Repository setup approach
2059
+ if args.repo_url:
2060
+ print("🤖 Repository setup will be handled by Claude Code Agent in container")
2061
+ setup_commands = [] # Claude Code Agent will handle setup intelligently
2062
+ else:
2063
+ print("⚠️ No repository URL provided - setup commands may be needed manually")
2215
2064
 
2216
2065
 
2217
2066
  # Parse setup commands from JSON if provided
@@ -2265,7 +2114,7 @@ if __name__ == "__main__":
2265
2114
  try:
2266
2115
  with open(args.setup_script, 'r') as f:
2267
2116
  script_content = f.read().strip()
2268
- # Convert script to individual commands
2117
+ # Convert script to individual commandsr
2269
2118
  script_commands = [line.strip() for line in script_content.split('\n')
2270
2119
  if line.strip() and not line.strip().startswith('#')]
2271
2120
  setup_commands.extend(script_commands)