gitarsenal-cli 1.9.52 ā 1.9.54
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 +1 -1
- package/bin/gitarsenal.js +1 -1
- package/package.json +1 -1
- package/python/test_modalSandboxScript.py +243 -133
package/.venv_status.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"created":"2025-08-
|
|
1
|
+
{"created":"2025-08-12T17:07:28.249Z","packages":["modal","gitingest","requests","anthropic"],"uv_version":"uv 0.8.4 (Homebrew 2025-07-30)"}
|
package/bin/gitarsenal.js
CHANGED
|
@@ -41,7 +41,7 @@ function activateVirtualEnvironment() {
|
|
|
41
41
|
if (fs.existsSync(statusFile)) {
|
|
42
42
|
try {
|
|
43
43
|
const status = JSON.parse(fs.readFileSync(statusFile, 'utf8'));
|
|
44
|
-
console.log(chalk.gray(`š¦ Packages: ${status.packages.join(', ')}`));
|
|
44
|
+
// console.log(chalk.gray(`š¦ Packages: ${status.packages.join(', ')}`));
|
|
45
45
|
} catch (error) {
|
|
46
46
|
console.log(chalk.gray('ā
Virtual environment found'));
|
|
47
47
|
}
|
package/package.json
CHANGED
|
@@ -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
|
-
#
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
#
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
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
|
-
|
|
449
|
-
|
|
450
|
-
|
|
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
|
-
#
|
|
453
|
-
if
|
|
454
|
-
|
|
455
|
-
|
|
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
|
-
#
|
|
458
|
-
|
|
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
|
-
#
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
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
|
-
#
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
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
|
-
|
|
487
|
-
|
|
488
|
-
|
|
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
|
-
#
|
|
502
|
-
|
|
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
|
|
507
|
-
|
|
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
|
-
#
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
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
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
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
|
-
#
|
|
539
|
-
|
|
540
|
-
|
|
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
|
|
544
|
-
print(f"
|
|
623
|
+
if should_skip:
|
|
624
|
+
print(f"š Skipping original command: {skip_reason}")
|
|
545
625
|
|
|
546
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
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
|
-
|
|
559
|
-
|
|
560
|
-
|
|
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
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
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
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
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:
|