gitarsenal-cli 1.9.52 → 1.9.53

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.
package/.venv_status.json CHANGED
@@ -1 +1 @@
1
- {"created":"2025-08-12T12:09:30.316Z","packages":["modal","gitingest","requests","anthropic"],"uv_version":"uv 0.8.4 (Homebrew 2025-07-30)"}
1
+ {"created":"2025-08-12T14:24:06.005Z","packages":["modal","gitingest","requests","anthropic"],"uv_version":"uv 0.8.4 (Homebrew 2025-07-30)"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitarsenal-cli",
3
- "version": "1.9.52",
3
+ "version": "1.9.53",
4
4
  "description": "CLI tool for creating Modal sandboxes with GitHub repositories",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -15,13 +15,118 @@ import uuid
15
15
  import signal
16
16
  from pathlib import Path
17
17
  import modal
18
+ import io
19
+ import contextlib
20
+ import unicodedata
21
+ import shutil
22
+ from auth_manager import AuthManager
18
23
 
19
- # Import authentication manager
20
- try:
21
- from auth_manager import AuthManager
22
- except ImportError:
23
- print("❌ Authentication module not found. Please ensure auth_manager.py is in the same directory.")
24
- sys.exit(1)
24
+ # Global flag to indicate when an outer boxed capture is active
25
+ _BOX_CAPTURE_ACTIVE = False
26
+
27
+
28
+ def _print_boxed_block(content: str):
29
+ # Use NBSP for padding to prevent renderers from trimming trailing spaces
30
+ NBSP = "\u00A0"
31
+ lines = content.splitlines() if content else ["(no output)"]
32
+
33
+ def _strip_ansi(s: str) -> str:
34
+ return re.sub(r"\x1B\[[0-9;]*[A-Za-z]", "", s)
35
+
36
+ def _display_width(s: str) -> int:
37
+ s = s.replace("\t", " ")
38
+ width = 0
39
+ for ch in s:
40
+ if unicodedata.combining(ch):
41
+ continue
42
+ ea = unicodedata.east_asian_width(ch)
43
+ width += 2 if ea in ("W", "F") else 1
44
+ return width
45
+
46
+ # Compute desired inner width: full terminal width when available
47
+ visible_widths = [_display_width(_strip_ansi(line)) for line in lines]
48
+ term_width = shutil.get_terminal_size(fallback=(120, 24)).columns
49
+ # Borders add 4 chars: left '│ ', right ' │'
50
+ desired_inner = max(20, term_width - 4)
51
+
52
+ # If content has lines longer than max_inner, hard-truncate with ellipsis preserving ANSI
53
+ def _truncate_line(line: str, target: int) -> str:
54
+ plain = _strip_ansi(line)
55
+ if _display_width(plain) <= target:
56
+ return line
57
+ out = []
58
+ acc = 0
59
+ i = 0
60
+ while i < len(line) and acc < max(0, target - 1):
61
+ ch = line[i]
62
+ out.append(ch)
63
+ # Skip ANSI sequences intact
64
+ if ch == "\x1b":
65
+ # consume until letter
66
+ j = i + 1
67
+ while j < len(line) and not (line[j].isalpha() and line[j-1] == 'm'):
68
+ j += 1
69
+ # include the terminator if present
70
+ if j < len(line):
71
+ out.append(line[i+1:j+1])
72
+ i = j + 1
73
+ else:
74
+ i += 1
75
+ continue
76
+ # width accounting
77
+ if not unicodedata.combining(ch):
78
+ ea = unicodedata.east_asian_width(ch)
79
+ acc += 2 if ea in ("W", "F") else 1
80
+ i += 1
81
+ return ''.join(out) + '…'
82
+
83
+ adjusted_lines = [_truncate_line(line, desired_inner) for line in lines]
84
+ inner_width = desired_inner
85
+
86
+ top = "┌" + "─" * (inner_width + 2) + "┐"
87
+ bottom = "└" + "─" * (inner_width + 2) + "┘"
88
+ print(top)
89
+ for line in adjusted_lines:
90
+ vis = _strip_ansi(line)
91
+ pad_len = inner_width - _display_width(vis)
92
+ pad = NBSP * max(pad_len, 0)
93
+ print("│ " + line + pad + " │")
94
+ print(bottom)
95
+
96
+
97
+ @contextlib.contextmanager
98
+ def _boxed_capture():
99
+ global _BOX_CAPTURE_ACTIVE
100
+ buf = io.StringIO()
101
+ prev = _BOX_CAPTURE_ACTIVE
102
+ _BOX_CAPTURE_ACTIVE = True
103
+ try:
104
+ with contextlib.redirect_stdout(buf):
105
+ yield
106
+ finally:
107
+ _BOX_CAPTURE_ACTIVE = prev
108
+ content = buf.getvalue().rstrip("\n")
109
+ _print_boxed_block(content)
110
+
111
+
112
+ def _execute_with_box(shell, cmd_text: str, timeout: int, title_line: str = None, box: bool = True):
113
+ start_time = time.time()
114
+ if box and not _BOX_CAPTURE_ACTIVE:
115
+ buf = io.StringIO()
116
+ with contextlib.redirect_stdout(buf):
117
+ if title_line:
118
+ print(title_line)
119
+ success, stdout, stderr = shell.execute(cmd_text, timeout=timeout)
120
+ execution_time = time.time() - start_time
121
+ content = buf.getvalue().rstrip("\n")
122
+ _print_boxed_block(content)
123
+ return success, stdout, stderr, execution_time
124
+ else:
125
+ if title_line:
126
+ print(title_line)
127
+ success, stdout, stderr = shell.execute(cmd_text, timeout=timeout)
128
+ execution_time = time.time() - start_time
129
+ return success, stdout, stderr, execution_time
25
130
 
26
131
  # Parse command-line arguments
27
132
  parser = argparse.ArgumentParser()
@@ -429,151 +534,156 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
429
534
  if not next_cmd:
430
535
  break
431
536
 
432
- # Print status before executing
433
- cmd_manager.print_status()
434
-
435
- # Periodically analyze and update the command list
436
- if commands_since_analysis >= 3 and cmd_type == 'main':
437
- print("\n🔍 Periodic command list analysis...")
438
- api_key = os.environ.get("OPENAI_API_KEY")
439
- cmd_manager.update_command_list_with_llm(api_key)
440
- commands_since_analysis = 0
441
-
442
- # Execute the command
443
- if cmd_type == 'main':
444
- cmd_text = next_cmd['command']
445
- cmd_index = next_cmd['index']
446
- print(f"📋 Executing main command {cmd_index + 1}/{cmd_manager.total_commands}: {cmd_text}")
537
+ # Box everything printed in this iteration into one block
538
+ with _boxed_capture():
539
+ # Print status before executing
540
+ cmd_manager.print_status()
447
541
 
448
- start_time = time.time()
449
- success, stdout, stderr = shell.execute(cmd_text, timeout=100)
450
- execution_time = time.time() - start_time
542
+ # Periodically analyze and update the command list
543
+ if commands_since_analysis >= 3 and cmd_type == 'main':
544
+ print("\n🔍 Periodic command list analysis...")
545
+ api_key = os.environ.get("OPENAI_API_KEY")
546
+ cmd_manager.update_command_list_with_llm(api_key)
547
+ commands_since_analysis = 0
451
548
 
452
- # Check if the command was aborted due to waiting for input and an alternative was suggested
453
- if not success and "Command aborted - requires user input" in stderr and shell.suggested_alternative:
454
- alternative_cmd = shell.suggested_alternative
455
- print(f"🔄 Command aborted due to input requirement. Adding suggested alternative: {alternative_cmd}")
549
+ # Execute the command
550
+ if cmd_type == 'main':
551
+ cmd_text = next_cmd['command']
552
+ cmd_index = next_cmd['index']
553
+ success, stdout, stderr, execution_time = _execute_with_box(
554
+ shell, cmd_text, timeout=100,
555
+ title_line=f"📋 Executing main command {cmd_index + 1}/{cmd_manager.total_commands}: {cmd_text}",
556
+ box=False
557
+ )
456
558
 
457
- # Add the alternative command with high priority
458
- cmd_manager.add_command_dynamically(alternative_cmd, priority='high')
559
+ # Check if the command was aborted due to waiting for input and an alternative was suggested
560
+ if not success and "Command aborted - requires user input" in stderr and shell.suggested_alternative:
561
+ alternative_cmd = shell.suggested_alternative
562
+ print(f"🔄 Command aborted due to input requirement. Adding suggested alternative: {alternative_cmd}")
563
+
564
+ # Add the alternative command with high priority
565
+ cmd_manager.add_command_dynamically(alternative_cmd, priority='high')
566
+
567
+ # Clear the suggested alternative
568
+ shell.suggested_alternative = None
569
+ # Check if the command should be removed as suggested by LLM
570
+ elif not success and stderr.startswith("Command removed -"):
571
+ reason = stderr.replace("Command removed -", "").strip()
572
+ print(f"🚫 Removed command as suggested by LLM: {reason}")
573
+ # We don't need to do anything else, just mark it as executed and move on
459
574
 
460
- # Clear the suggested alternative
461
- shell.suggested_alternative = None
462
- # Check if the command should be removed as suggested by LLM
463
- elif not success and stderr.startswith("Command removed -"):
464
- reason = stderr.replace("Command removed -", "").strip()
465
- print(f"🚫 Removed command as suggested by LLM: {reason}")
466
- # We don't need to do anything else, just mark it as executed and move on
467
-
468
- # Mark command as executed
469
- cmd_manager.mark_command_executed(
470
- cmd_index, 'main', success, stdout, stderr, execution_time
471
- )
472
-
473
- # Increment counter for periodic analysis
474
- commands_since_analysis += 1
475
-
476
- if not success:
477
- print(f"⚠️ Command failed, attempting LLM debugging...")
575
+ # Mark command as executed
576
+ cmd_manager.mark_command_executed(
577
+ cmd_index, 'main', success, stdout, stderr, execution_time
578
+ )
478
579
 
479
- # Call LLM for debugging
480
- try:
481
- current_dir = shell.get_cwd()
482
-
483
- # Use unified LLM debugging function
484
- fix_command = call_llm_for_debug(cmd_text, stderr, current_dir=current_dir, sandbox=shell)
580
+ # Increment counter for periodic analysis
581
+ commands_since_analysis += 1
582
+
583
+ if not success:
584
+ print(f"⚠️ Command failed, attempting LLM debugging...")
485
585
 
486
- if fix_command:
487
- # Get the current debug model to show the correct provider name
488
- from llm_debugging import get_current_debug_model
489
- current_model = get_current_debug_model()
490
- print(f"🔧 {current_model.capitalize()} suggested fix command: {fix_command}")
491
-
492
- # Add the fix to the command list manager
493
- fix_index = cmd_manager.add_suggested_fix(cmd_text, fix_command, "LLM suggested fix")
494
-
495
- # Execute the fix command
496
- print(f"🔄 Running suggested fix command: {fix_command}")
497
- fix_start_time = time.time()
498
- fix_success, fix_stdout, fix_stderr = shell.execute(fix_command, timeout=120)
499
- fix_execution_time = time.time() - fix_start_time
586
+ # Call LLM for debugging
587
+ try:
588
+ current_dir = shell.get_cwd()
500
589
 
501
- # Mark fix command as executed
502
- cmd_manager.mark_command_executed(
503
- fix_index, 'fix', fix_success, fix_stdout, fix_stderr, fix_execution_time
504
- )
590
+ # Use unified LLM debugging function
591
+ fix_command = call_llm_for_debug(cmd_text, stderr, current_dir=current_dir, sandbox=shell)
505
592
 
506
- if fix_success:
507
- print(f"✅ Fix command succeeded")
593
+ if fix_command:
594
+ # Get the current debug model to show the correct provider name
595
+ from llm_debugging import get_current_debug_model
596
+ current_model = get_current_debug_model()
597
+ print(f"🔧 {current_model.capitalize()} suggested fix command: {fix_command}")
598
+
599
+ # Add the fix to the command list manager
600
+ fix_index = cmd_manager.add_suggested_fix(cmd_text, fix_command, "LLM suggested fix")
508
601
 
509
- # Check if we should skip the original command
510
- api_key = os.environ.get("OPENAI_API_KEY")
511
- should_skip, skip_reason = cmd_manager.should_skip_original_command(
512
- cmd_text, fix_command, fix_stdout, fix_stderr, api_key
602
+ # Execute the fix command
603
+ fix_success, fix_stdout, fix_stderr, fix_execution_time = _execute_with_box(
604
+ shell, fix_command, timeout=120,
605
+ title_line=f"🔧 Executing suggested fix: {fix_command}",
606
+ box=False
513
607
  )
514
608
 
515
- if should_skip:
516
- print(f"🔄 Skipping original command: {skip_reason}")
517
-
518
- # Mark the original command as successful without running it
519
- cmd_manager.mark_command_executed(
520
- cmd_index, 'main', True,
521
- f"Command skipped after successful fix: {skip_reason}",
522
- "", time.time() - start_time
523
- )
524
-
525
- print(f"✅ Original command marked as successful (skipped)")
526
-
527
- # After a successful fix and skipping the original command,
528
- # analyze and update the entire command list
529
- print("\n🔍 Analyzing and updating remaining commands based on fix results...")
530
- cmd_manager.update_command_list_with_llm(api_key)
531
- else:
532
- # Retry the original command
533
- print(f"🔄 Retrying original command: {cmd_text}")
534
- retry_start_time = time.time()
535
- retry_success, retry_stdout, retry_stderr = shell.execute(cmd_text, timeout=100)
536
- retry_execution_time = time.time() - retry_start_time
609
+ # Mark fix command as executed
610
+ cmd_manager.mark_command_executed(
611
+ fix_index, 'fix', fix_success, fix_stdout, fix_stderr, fix_execution_time
612
+ )
613
+
614
+ if fix_success:
615
+ print(f" Fix command succeeded")
537
616
 
538
- # Update the original command status
539
- cmd_manager.mark_command_executed(
540
- cmd_index, 'main', retry_success, retry_stdout, retry_stderr, retry_execution_time
617
+ # Check if we should skip the original command
618
+ api_key = os.environ.get("OPENAI_API_KEY")
619
+ should_skip, skip_reason = cmd_manager.should_skip_original_command(
620
+ cmd_text, fix_command, fix_stdout, fix_stderr, api_key
541
621
  )
542
622
 
543
- if retry_success:
544
- print(f" Original command succeeded after fix!")
623
+ if should_skip:
624
+ print(f"🔄 Skipping original command: {skip_reason}")
545
625
 
546
- # After a successful fix and successful retry,
626
+ # Mark the original command as successful without running it
627
+ cmd_manager.mark_command_executed(
628
+ cmd_index, 'main', True,
629
+ f"Command skipped after successful fix: {skip_reason}",
630
+ "", execution_time
631
+ )
632
+
633
+ print(f"✅ Original command marked as successful (skipped)")
634
+
635
+ # After a successful fix and skipping the original command,
547
636
  # analyze and update the entire command list
548
637
  print("\n🔍 Analyzing and updating remaining commands based on fix results...")
549
638
  cmd_manager.update_command_list_with_llm(api_key)
550
639
  else:
551
- print(f"⚠️ Original command still failed after fix, continuing...")
640
+ # Retry the original command
641
+ print(f"🔄 Retrying original command: {cmd_text}")
642
+ retry_success, retry_stdout, retry_stderr, retry_execution_time = _execute_with_box(
643
+ shell, cmd_text, timeout=100,
644
+ title_line=f"🔄 Retrying command: {cmd_text}",
645
+ box=False
646
+ )
647
+
648
+ # Update the original command status
649
+ cmd_manager.mark_command_executed(
650
+ cmd_index, 'main', retry_success, retry_stdout, retry_stderr, retry_execution_time
651
+ )
652
+
653
+ if retry_success:
654
+ print(f"✅ Original command succeeded after fix!")
655
+
656
+ # After a successful fix and successful retry,
657
+ # analyze and update the entire command list
658
+ print("\n🔍 Analyzing and updating remaining commands based on fix results...")
659
+ cmd_manager.update_command_list_with_llm(api_key)
660
+ else:
661
+ print(f"⚠️ Original command still failed after fix, continuing...")
662
+ else:
663
+ print(f"❌ Fix command failed: {fix_stderr}")
664
+ print(f"⚠️ First fix attempt failed; continuing without web search...")
665
+
666
+ # No web-search retry; continue
667
+ print(f"⚠️ Continuing with remaining commands...")
552
668
  else:
553
- print(f"❌ Fix command failed: {fix_stderr}")
554
- print(f"⚠️ First fix attempt failed; continuing without web search...")
555
-
556
- # No web-search retry; continue
669
+ # Get the current debug model to show the correct provider name
670
+ from llm_debugging import get_current_debug_model
671
+ current_model = get_current_debug_model()
672
+ print(f"❌ No fix suggested by {current_model.capitalize()}")
557
673
  print(f"⚠️ Continuing with remaining commands...")
558
- else:
559
- # Get the current debug model to show the correct provider name
560
- from llm_debugging import get_current_debug_model
561
- current_model = get_current_debug_model()
562
- print(f"❌ No fix suggested by {current_model.capitalize()}")
674
+
675
+ except Exception as debug_e:
676
+ print(f"❌ LLM debugging failed: {debug_e}")
563
677
  print(f"⚠️ Continuing with remaining commands...")
564
-
565
- except Exception as debug_e:
566
- print(f"❌ LLM debugging failed: {debug_e}")
567
- print(f"⚠️ Continuing with remaining commands...")
568
678
 
569
- elif cmd_type == 'fix':
570
- cmd_text = next_cmd['fix_command']
571
- cmd_index = next_cmd['index']
572
- print(f"🔧 Executing fix command {cmd_index + 1}: {cmd_text}")
573
-
574
- start_time = time.time()
575
- success, stdout, stderr = shell.execute(cmd_text, timeout=100)
576
- execution_time = time.time() - start_time
679
+ elif cmd_type == 'fix':
680
+ cmd_text = next_cmd['fix_command']
681
+ cmd_index = next_cmd['index']
682
+ success, stdout, stderr, execution_time = _execute_with_box(
683
+ shell, cmd_text, timeout=100,
684
+ title_line=f"🔧 Executing fix command {cmd_index + 1}: {cmd_text}",
685
+ box=False
686
+ )
577
687
 
578
688
  # Check if the fix command was aborted due to waiting for input and an alternative was suggested
579
689
  if not success and "Command aborted - requires user input" in stderr and shell.suggested_alternative:
@@ -617,11 +727,11 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
617
727
  for fix_index in additional_fixes:
618
728
  fix_cmd = cmd_manager.suggested_fixes[fix_index]
619
729
  cmd_text = fix_cmd['fix_command']
620
- print(f"🔧 Executing additional fix: {cmd_text}")
621
-
622
- start_time = time.time()
623
- success, stdout, stderr = shell.execute(cmd_text, timeout=100)
624
- execution_time = time.time() - start_time
730
+ success, stdout, stderr, execution_time = _execute_with_box(
731
+ shell, cmd_text, timeout=100,
732
+ title_line=f"🔧 Executing additional fix: {cmd_text}",
733
+ box=False
734
+ )
625
735
 
626
736
  # Check if the fix command was aborted due to waiting for input and an alternative was suggested
627
737
  if not success and "Command aborted - requires user input" in stderr and shell.suggested_alternative: