gitarsenal-cli 1.9.69 ā 1.9.71
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 +83 -0
- package/kill_claude/claude_code_agent.py +1 -1
- package/package.json +1 -1
- package/python/test_modalSandboxScript.py +117 -17
package/.venv_status.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"created":"2025-08-15T05:
|
|
1
|
+
{"created":"2025-08-15T05:31:11.835Z","packages":["modal","gitingest","requests","anthropic"],"uv_version":"uv 0.8.4 (Homebrew 2025-07-30)"}
|
package/bin/gitarsenal.js
CHANGED
|
@@ -16,6 +16,89 @@ const fs = require('fs');
|
|
|
16
16
|
const https = require('https');
|
|
17
17
|
const http = require('http');
|
|
18
18
|
|
|
19
|
+
// Function to activate virtual environment
|
|
20
|
+
function activateVirtualEnvironment() {
|
|
21
|
+
const isWindows = process.platform === 'win32';
|
|
22
|
+
const venvPath = path.join(__dirname, '..', '.venv');
|
|
23
|
+
const statusFile = path.join(__dirname, '..', '.venv_status.json');
|
|
24
|
+
|
|
25
|
+
// Check if virtual environment exists
|
|
26
|
+
if (!fs.existsSync(venvPath)) {
|
|
27
|
+
console.log(chalk.red('ā Virtual environment not found. Please reinstall the package:'));
|
|
28
|
+
console.log(chalk.yellow(' npm uninstall -g gitarsenal-cli'));
|
|
29
|
+
console.log(chalk.yellow(' npm install -g gitarsenal-cli'));
|
|
30
|
+
console.log(chalk.yellow(''));
|
|
31
|
+
console.log(chalk.yellow('š” Or run the postinstall script manually:'));
|
|
32
|
+
console.log(chalk.yellow(' cd /root/.nvm/versions/node/v22.18.0/lib/node_modules/gitarsenal-cli'));
|
|
33
|
+
console.log(chalk.yellow(' node scripts/postinstall.js'));
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Check if status file exists (indicates successful installation)
|
|
38
|
+
if (fs.existsSync(statusFile)) {
|
|
39
|
+
try {
|
|
40
|
+
const status = JSON.parse(fs.readFileSync(statusFile, 'utf8'));
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.log(chalk.gray('ā
Virtual environment found'));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Verify virtual environment structure - uv creates different structure
|
|
47
|
+
let pythonPath, pipPath;
|
|
48
|
+
|
|
49
|
+
// Check for uv-style virtual environment first
|
|
50
|
+
const uvPythonPath = path.join(venvPath, 'bin', 'python');
|
|
51
|
+
const uvPipPath = path.join(venvPath, 'bin', 'pip');
|
|
52
|
+
|
|
53
|
+
// Check for traditional venv structure
|
|
54
|
+
const traditionalPythonPath = isWindows ?
|
|
55
|
+
path.join(venvPath, 'Scripts', 'python.exe') :
|
|
56
|
+
path.join(venvPath, 'bin', 'python');
|
|
57
|
+
const traditionalPipPath = isWindows ?
|
|
58
|
+
path.join(venvPath, 'Scripts', 'pip.exe') :
|
|
59
|
+
path.join(venvPath, 'bin', 'pip');
|
|
60
|
+
|
|
61
|
+
// Determine which structure exists
|
|
62
|
+
// console.log(chalk.gray(`š Checking virtual environment structure:`));
|
|
63
|
+
// console.log(chalk.gray(` Python: ${uvPythonPath} (exists: ${fs.existsSync(uvPythonPath)})`));
|
|
64
|
+
// console.log(chalk.gray(` Pip: ${uvPipPath} (exists: ${fs.existsSync(uvPipPath)})`));
|
|
65
|
+
|
|
66
|
+
// For uv virtual environments, we only need Python to exist
|
|
67
|
+
// uv doesn't create a pip executable, it uses 'uv pip' instead
|
|
68
|
+
if (fs.existsSync(uvPythonPath)) {
|
|
69
|
+
pythonPath = uvPythonPath;
|
|
70
|
+
pipPath = 'uv pip'; // Use uv pip instead of pip executable
|
|
71
|
+
// console.log(chalk.gray('ā
Found uv-style virtual environment'));
|
|
72
|
+
} else if (fs.existsSync(traditionalPythonPath) && fs.existsSync(traditionalPipPath)) {
|
|
73
|
+
pythonPath = traditionalPythonPath;
|
|
74
|
+
pipPath = traditionalPipPath;
|
|
75
|
+
console.log(chalk.gray('ā
Found traditional virtual environment'));
|
|
76
|
+
} else {
|
|
77
|
+
console.log(chalk.red('ā Virtual environment structure not recognized'));
|
|
78
|
+
console.log(chalk.gray('Expected Python at:'));
|
|
79
|
+
console.log(chalk.gray(` ${uvPythonPath}`));
|
|
80
|
+
console.log(chalk.gray(` ${traditionalPythonPath}`));
|
|
81
|
+
console.log(chalk.yellow('š” Please reinstall the package'));
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Update PATH to prioritize virtual environment
|
|
86
|
+
const pathSeparator = isWindows ? ';' : ':';
|
|
87
|
+
const venvBinPath = path.dirname(pythonPath); // Use the same directory as the Python executable
|
|
88
|
+
|
|
89
|
+
process.env.PATH = `${venvBinPath}${pathSeparator}${process.env.PATH}`;
|
|
90
|
+
process.env.VIRTUAL_ENV = venvPath;
|
|
91
|
+
process.env.PYTHONPATH = venvPath;
|
|
92
|
+
|
|
93
|
+
// Set Python executable path for child processes
|
|
94
|
+
process.env.PYTHON_EXECUTABLE = pythonPath;
|
|
95
|
+
process.env.PIP_EXECUTABLE = pipPath;
|
|
96
|
+
|
|
97
|
+
console.log(chalk.green('ā
Virtual environment activated successfully'));
|
|
98
|
+
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
|
|
19
102
|
|
|
20
103
|
// Lightweight preview of GPU/Torch/CUDA recommendations prior to GPU selection
|
|
21
104
|
async function previewRecommendations(repoUrl, optsOrShowSummary = true) {
|
|
@@ -372,7 +372,7 @@ The following {len(TOOL_SCHEMAS)} tools are loaded and available:
|
|
|
372
372
|
|
|
373
373
|
# Execute tool calls
|
|
374
374
|
if tool_calls:
|
|
375
|
-
print(f"š¤
|
|
375
|
+
print(f"š¤ Agent requested {len(tool_calls)} tool call(s)")
|
|
376
376
|
tool_results = []
|
|
377
377
|
|
|
378
378
|
for i, tool_call in enumerate(tool_calls, 1):
|
package/package.json
CHANGED
|
@@ -541,28 +541,89 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
|
|
|
541
541
|
print("š AGENT OUTPUT (LIVE)")
|
|
542
542
|
print("="*60)
|
|
543
543
|
|
|
544
|
-
# Use Popen for real-time output streaming
|
|
544
|
+
# Use Popen for real-time output streaming with optimizations
|
|
545
|
+
import sys
|
|
546
|
+
import select
|
|
547
|
+
import fcntl
|
|
548
|
+
import os as os_module
|
|
549
|
+
|
|
545
550
|
process = subprocess.Popen(
|
|
546
|
-
["python", "/python/kill_claude/claude_code_agent.py", claude_prompt],
|
|
551
|
+
["python", "-u", "/python/kill_claude/claude_code_agent.py", claude_prompt], # -u for unbuffered output
|
|
547
552
|
cwd="/root",
|
|
548
553
|
stdout=subprocess.PIPE,
|
|
549
|
-
stderr=subprocess.
|
|
554
|
+
stderr=subprocess.PIPE, # Keep separate for better handling
|
|
550
555
|
text=True,
|
|
551
|
-
bufsize=
|
|
552
|
-
universal_newlines=True
|
|
556
|
+
bufsize=0, # Unbuffered for fastest output
|
|
557
|
+
universal_newlines=True,
|
|
558
|
+
env=dict(os.environ, PYTHONUNBUFFERED='1') # Force unbuffered Python output
|
|
553
559
|
)
|
|
554
560
|
|
|
555
|
-
#
|
|
561
|
+
# Make stdout and stderr non-blocking for faster reading
|
|
562
|
+
def make_non_blocking(fd):
|
|
563
|
+
flags = fcntl.fcntl(fd, fcntl.F_GETFL)
|
|
564
|
+
fcntl.fcntl(fd, fcntl.F_SETFL, flags | os_module.O_NONBLOCK)
|
|
565
|
+
|
|
566
|
+
make_non_blocking(process.stdout)
|
|
567
|
+
make_non_blocking(process.stderr)
|
|
568
|
+
|
|
569
|
+
# Stream output in real-time with robust error handling
|
|
556
570
|
try:
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
571
|
+
stdout_buffer = ""
|
|
572
|
+
stderr_buffer = ""
|
|
573
|
+
|
|
574
|
+
while process.poll() is None:
|
|
575
|
+
try:
|
|
576
|
+
# Use select for efficient I/O multiplexing with error handling
|
|
577
|
+
ready, _, _ = select.select([process.stdout, process.stderr], [], [], 0.1) # 100ms timeout
|
|
578
|
+
|
|
579
|
+
for stream in ready:
|
|
580
|
+
try:
|
|
581
|
+
if stream == process.stdout:
|
|
582
|
+
chunk = stream.read(1024) # Read in chunks for efficiency
|
|
583
|
+
if chunk is not None and chunk:
|
|
584
|
+
stdout_buffer += chunk
|
|
585
|
+
# Process complete lines immediately
|
|
586
|
+
while '\n' in stdout_buffer:
|
|
587
|
+
line, stdout_buffer = stdout_buffer.split('\n', 1)
|
|
588
|
+
print(line, flush=True) # Force immediate flush
|
|
589
|
+
elif stream == process.stderr:
|
|
590
|
+
chunk = stream.read(1024)
|
|
591
|
+
if chunk is not None and chunk:
|
|
592
|
+
stderr_buffer += chunk
|
|
593
|
+
# Process complete lines immediately
|
|
594
|
+
while '\n' in stderr_buffer:
|
|
595
|
+
line, stderr_buffer = stderr_buffer.split('\n', 1)
|
|
596
|
+
print(f"STDERR: {line}", flush=True)
|
|
597
|
+
except (BlockingIOError, OSError, ValueError):
|
|
598
|
+
# Handle various I/O errors gracefully
|
|
599
|
+
continue
|
|
600
|
+
except (select.error, OSError):
|
|
601
|
+
# If select fails, fall back to simple polling
|
|
602
|
+
time.sleep(0.1)
|
|
603
|
+
continue
|
|
604
|
+
|
|
605
|
+
# Process any remaining output after process ends
|
|
606
|
+
try:
|
|
607
|
+
# Read any remaining data from streams
|
|
608
|
+
remaining_stdout = process.stdout.read()
|
|
609
|
+
remaining_stderr = process.stderr.read()
|
|
610
|
+
|
|
611
|
+
if remaining_stdout:
|
|
612
|
+
stdout_buffer += remaining_stdout
|
|
613
|
+
if remaining_stderr:
|
|
614
|
+
stderr_buffer += remaining_stderr
|
|
615
|
+
|
|
616
|
+
# Output remaining buffered content
|
|
617
|
+
if stdout_buffer.strip():
|
|
618
|
+
print(stdout_buffer.strip(), flush=True)
|
|
619
|
+
if stderr_buffer.strip():
|
|
620
|
+
print(f"STDERR: {stderr_buffer.strip()}", flush=True)
|
|
621
|
+
except (OSError, ValueError):
|
|
622
|
+
# Handle cases where streams are already closed
|
|
623
|
+
pass
|
|
563
624
|
|
|
564
|
-
#
|
|
565
|
-
return_code = process.
|
|
625
|
+
# Get final return code
|
|
626
|
+
return_code = process.returncode
|
|
566
627
|
|
|
567
628
|
print("\n" + "="*60)
|
|
568
629
|
if return_code == 0:
|
|
@@ -576,9 +637,48 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
|
|
|
576
637
|
process.kill()
|
|
577
638
|
process.wait()
|
|
578
639
|
except Exception as stream_error:
|
|
579
|
-
print(f"\nā ļø Error streaming
|
|
580
|
-
|
|
581
|
-
|
|
640
|
+
# print(f"\nā ļø Error with advanced streaming: {stream_error}")
|
|
641
|
+
# print("š Falling back to simple streaming...")
|
|
642
|
+
pass
|
|
643
|
+
|
|
644
|
+
# Fallback to simple readline approach
|
|
645
|
+
try:
|
|
646
|
+
# Restart the process with simpler streaming
|
|
647
|
+
if process.poll() is None:
|
|
648
|
+
process.kill()
|
|
649
|
+
process.wait()
|
|
650
|
+
|
|
651
|
+
fallback_process = subprocess.Popen(
|
|
652
|
+
["python", "-u", "/python/kill_claude/claude_code_agent.py", claude_prompt],
|
|
653
|
+
cwd="/root",
|
|
654
|
+
stdout=subprocess.PIPE,
|
|
655
|
+
stderr=subprocess.STDOUT,
|
|
656
|
+
text=True,
|
|
657
|
+
bufsize=1,
|
|
658
|
+
universal_newlines=True
|
|
659
|
+
)
|
|
660
|
+
|
|
661
|
+
# Simple line-by-line reading
|
|
662
|
+
while True:
|
|
663
|
+
line = fallback_process.stdout.readline()
|
|
664
|
+
if line == '' and fallback_process.poll() is not None:
|
|
665
|
+
break
|
|
666
|
+
if line:
|
|
667
|
+
print(line.rstrip(), flush=True)
|
|
668
|
+
|
|
669
|
+
return_code = fallback_process.returncode
|
|
670
|
+
|
|
671
|
+
print("\n" + "="*60)
|
|
672
|
+
if return_code == 0:
|
|
673
|
+
print("ā
Agent completed successfully!")
|
|
674
|
+
else:
|
|
675
|
+
print(f"ā ļø Agent exited with code: {return_code}")
|
|
676
|
+
print("="*60)
|
|
677
|
+
|
|
678
|
+
except Exception as fallback_error:
|
|
679
|
+
print(f"\nā Fallback streaming also failed: {fallback_error}")
|
|
680
|
+
print("ā ļø Agent may have completed, but output streaming failed")
|
|
681
|
+
return_code = 1
|
|
582
682
|
|
|
583
683
|
except Exception as e:
|
|
584
684
|
print(f"ā Error during repository setup: {e}")
|