gitarsenal-cli 1.0.0
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/README.md +105 -0
- package/bin/gitarsenal.js +185 -0
- package/index.js +12 -0
- package/install.sh +30 -0
- package/lib/dependencies.js +113 -0
- package/lib/sandbox.js +106 -0
- package/package.json +37 -0
- package/python/test_modalSandboxScript.py +44 -0
- package/scripts/postinstall.js +146 -0
- package/test.js +27 -0
package/README.md
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
# GitArsenal CLI
|
2
|
+
|
3
|
+
A command-line tool for easily creating Modal sandboxes with GitHub repositories.
|
4
|
+
|
5
|
+
## Features
|
6
|
+
|
7
|
+
- Interactive prompts to configure your sandbox
|
8
|
+
- Support for custom GPU types (A10G, A100, H100, T4, V100)
|
9
|
+
- Persistent volume support for faster installations
|
10
|
+
- Custom setup commands
|
11
|
+
- Automatic repository cloning
|
12
|
+
|
13
|
+
## Installation
|
14
|
+
|
15
|
+
### Prerequisites
|
16
|
+
|
17
|
+
- Node.js 14 or higher
|
18
|
+
- Python 3.8 or higher
|
19
|
+
- Modal CLI (`pip install modal`)
|
20
|
+
- Git
|
21
|
+
|
22
|
+
### Global Installation
|
23
|
+
|
24
|
+
```bash
|
25
|
+
npm install -g gitarsenal-cli
|
26
|
+
```
|
27
|
+
|
28
|
+
### Local Installation
|
29
|
+
|
30
|
+
```bash
|
31
|
+
npm install gitarsenal-cli
|
32
|
+
```
|
33
|
+
|
34
|
+
## Usage
|
35
|
+
|
36
|
+
### Command Line Interface
|
37
|
+
|
38
|
+
Simply run the command:
|
39
|
+
|
40
|
+
```bash
|
41
|
+
gitarsenal
|
42
|
+
```
|
43
|
+
|
44
|
+
The CLI will guide you through the process with interactive prompts:
|
45
|
+
|
46
|
+
1. Enter the GitHub repository URL
|
47
|
+
2. Select a GPU type
|
48
|
+
3. Choose whether to use a persistent volume
|
49
|
+
4. Optionally provide custom setup commands
|
50
|
+
5. Confirm your settings
|
51
|
+
|
52
|
+
### Command Line Options
|
53
|
+
|
54
|
+
```
|
55
|
+
Usage: gitarsenal [options]
|
56
|
+
|
57
|
+
Options:
|
58
|
+
-V, --version output the version number
|
59
|
+
-r, --repo <url> GitHub repository URL
|
60
|
+
-g, --gpu <type> GPU type (A10G, A100, H100, T4, V100) (default: "A10G")
|
61
|
+
-v, --volume <name> Name of persistent volume
|
62
|
+
-y, --yes Skip confirmation prompts
|
63
|
+
-h, --help display help for command
|
64
|
+
```
|
65
|
+
|
66
|
+
### Example
|
67
|
+
|
68
|
+
```bash
|
69
|
+
gitarsenal --repo https://github.com/username/repo-name --gpu A10G --volume my-volume
|
70
|
+
```
|
71
|
+
|
72
|
+
## Programmatic Usage
|
73
|
+
|
74
|
+
You can also use GitArsenal programmatically in your Node.js applications:
|
75
|
+
|
76
|
+
```javascript
|
77
|
+
const { runModalSandbox } = require('gitarsenal-cli');
|
78
|
+
|
79
|
+
async function main() {
|
80
|
+
await runModalSandbox({
|
81
|
+
repoUrl: 'https://github.com/username/repo-name',
|
82
|
+
gpuType: 'A10G',
|
83
|
+
volumeName: 'my-volume',
|
84
|
+
setupCommands: [
|
85
|
+
'pip install -r requirements.txt',
|
86
|
+
'python setup.py install'
|
87
|
+
]
|
88
|
+
});
|
89
|
+
}
|
90
|
+
|
91
|
+
main().catch(console.error);
|
92
|
+
```
|
93
|
+
|
94
|
+
## How It Works
|
95
|
+
|
96
|
+
GitArsenal CLI is a Node.js wrapper around a Python script that creates Modal sandboxes. The CLI:
|
97
|
+
|
98
|
+
1. Checks for required dependencies (Python, Modal, Git)
|
99
|
+
2. Prompts for configuration options
|
100
|
+
3. Executes the Python script with the provided options
|
101
|
+
4. Handles errors and provides feedback
|
102
|
+
|
103
|
+
## License
|
104
|
+
|
105
|
+
MIT
|
@@ -0,0 +1,185 @@
|
|
1
|
+
#!/usr/bin/env node
|
2
|
+
|
3
|
+
const { program } = require('commander');
|
4
|
+
const chalk = require('chalk');
|
5
|
+
const inquirer = require('inquirer');
|
6
|
+
const ora = require('ora');
|
7
|
+
const path = require('path');
|
8
|
+
const { version } = require('../package.json');
|
9
|
+
const { checkDependencies } = require('../lib/dependencies');
|
10
|
+
const { runModalSandbox } = require('../lib/sandbox');
|
11
|
+
const updateNotifier = require('update-notifier');
|
12
|
+
const pkg = require('../package.json');
|
13
|
+
const boxen = require('boxen');
|
14
|
+
|
15
|
+
// Check for updates
|
16
|
+
updateNotifier({ pkg }).notify();
|
17
|
+
|
18
|
+
// Display banner
|
19
|
+
console.log(boxen(chalk.bold.green('GitArsenal CLI') + '\n' + chalk.blue('Create Modal sandboxes with GitHub repositories'), {
|
20
|
+
padding: 1,
|
21
|
+
margin: 1,
|
22
|
+
borderStyle: 'round',
|
23
|
+
borderColor: 'green'
|
24
|
+
}));
|
25
|
+
|
26
|
+
// Set up CLI options
|
27
|
+
program
|
28
|
+
.version(version)
|
29
|
+
.description('Create Modal sandboxes with GitHub repositories')
|
30
|
+
.option('-r, --repo <url>', 'GitHub repository URL')
|
31
|
+
.option('-g, --gpu <type>', 'GPU type (A10G, A100, H100, T4, V100)', 'A10G')
|
32
|
+
.option('-v, --volume <name>', 'Name of persistent volume')
|
33
|
+
.option('-y, --yes', 'Skip confirmation prompts')
|
34
|
+
.parse(process.argv);
|
35
|
+
|
36
|
+
const options = program.opts();
|
37
|
+
|
38
|
+
async function main() {
|
39
|
+
try {
|
40
|
+
// Check for required dependencies
|
41
|
+
const spinner = ora('Checking dependencies...').start();
|
42
|
+
const dependenciesOk = await checkDependencies();
|
43
|
+
|
44
|
+
if (!dependenciesOk) {
|
45
|
+
spinner.fail('Missing dependencies. Please install them and try again.');
|
46
|
+
process.exit(1);
|
47
|
+
}
|
48
|
+
spinner.succeed('Dependencies checked');
|
49
|
+
|
50
|
+
// If repo URL not provided, prompt for it
|
51
|
+
let repoUrl = options.repo;
|
52
|
+
let gpuType = options.gpu;
|
53
|
+
let volumeName = options.volume;
|
54
|
+
let skipConfirmation = options.yes;
|
55
|
+
|
56
|
+
if (!repoUrl) {
|
57
|
+
const answers = await inquirer.prompt([
|
58
|
+
{
|
59
|
+
type: 'input',
|
60
|
+
name: 'repoUrl',
|
61
|
+
message: 'Enter GitHub repository URL:',
|
62
|
+
validate: (input) => input.trim() !== '' ? true : 'Repository URL is required'
|
63
|
+
}
|
64
|
+
]);
|
65
|
+
repoUrl = answers.repoUrl;
|
66
|
+
}
|
67
|
+
|
68
|
+
// Prompt for GPU type if not specified
|
69
|
+
if (!options.gpu) {
|
70
|
+
const gpuAnswers = await inquirer.prompt([
|
71
|
+
{
|
72
|
+
type: 'list',
|
73
|
+
name: 'gpuType',
|
74
|
+
message: 'Select GPU type:',
|
75
|
+
choices: [
|
76
|
+
{ name: 'A10G (24GB VRAM)', value: 'A10G' },
|
77
|
+
{ name: 'A100 (40GB VRAM)', value: 'A100' },
|
78
|
+
{ name: 'H100 (80GB VRAM)', value: 'H100' },
|
79
|
+
{ name: 'T4 (16GB VRAM)', value: 'T4' },
|
80
|
+
{ name: 'V100 (16GB VRAM)', value: 'V100' }
|
81
|
+
],
|
82
|
+
default: 'A10G'
|
83
|
+
}
|
84
|
+
]);
|
85
|
+
gpuType = gpuAnswers.gpuType;
|
86
|
+
}
|
87
|
+
|
88
|
+
// Prompt for persistent volume
|
89
|
+
if (!volumeName) {
|
90
|
+
const volumeAnswers = await inquirer.prompt([
|
91
|
+
{
|
92
|
+
type: 'confirm',
|
93
|
+
name: 'useVolume',
|
94
|
+
message: 'Use persistent volume for faster installs?',
|
95
|
+
default: true
|
96
|
+
},
|
97
|
+
{
|
98
|
+
type: 'input',
|
99
|
+
name: 'volumeName',
|
100
|
+
message: 'Enter volume name:',
|
101
|
+
default: 'gitarsenal-volume',
|
102
|
+
when: (answers) => answers.useVolume
|
103
|
+
}
|
104
|
+
]);
|
105
|
+
|
106
|
+
if (volumeAnswers.useVolume) {
|
107
|
+
volumeName = volumeAnswers.volumeName;
|
108
|
+
}
|
109
|
+
}
|
110
|
+
|
111
|
+
// Prompt for custom setup commands
|
112
|
+
const setupAnswers = await inquirer.prompt([
|
113
|
+
{
|
114
|
+
type: 'confirm',
|
115
|
+
name: 'useCustomCommands',
|
116
|
+
message: 'Provide custom setup commands?',
|
117
|
+
default: false
|
118
|
+
}
|
119
|
+
]);
|
120
|
+
|
121
|
+
let setupCommands = [];
|
122
|
+
if (setupAnswers.useCustomCommands) {
|
123
|
+
console.log(chalk.yellow('Enter setup commands (one per line). Type "done" on a new line when finished:'));
|
124
|
+
|
125
|
+
const commandsInput = await inquirer.prompt([
|
126
|
+
{
|
127
|
+
type: 'editor',
|
128
|
+
name: 'commands',
|
129
|
+
message: 'Enter setup commands:',
|
130
|
+
default: '# Enter one command per line\n# Example: pip install -r requirements.txt\n'
|
131
|
+
}
|
132
|
+
]);
|
133
|
+
|
134
|
+
setupCommands = commandsInput.commands
|
135
|
+
.split('\n')
|
136
|
+
.filter(line => line.trim() !== '' && !line.trim().startsWith('#'));
|
137
|
+
}
|
138
|
+
|
139
|
+
// Show configuration summary
|
140
|
+
console.log(chalk.bold('\n📋 Sandbox Configuration:'));
|
141
|
+
console.log(chalk.cyan('Repository URL: ') + repoUrl);
|
142
|
+
console.log(chalk.cyan('GPU Type: ') + gpuType);
|
143
|
+
console.log(chalk.cyan('Volume: ') + (volumeName || 'None'));
|
144
|
+
|
145
|
+
if (setupCommands.length > 0) {
|
146
|
+
console.log(chalk.cyan('Setup Commands:'));
|
147
|
+
setupCommands.forEach((cmd, i) => {
|
148
|
+
console.log(` ${i + 1}. ${cmd}`);
|
149
|
+
});
|
150
|
+
} else {
|
151
|
+
console.log(chalk.cyan('Setup Commands: ') + 'None (will use defaults)');
|
152
|
+
}
|
153
|
+
|
154
|
+
// Confirm settings
|
155
|
+
if (!skipConfirmation) {
|
156
|
+
const confirmAnswers = await inquirer.prompt([
|
157
|
+
{
|
158
|
+
type: 'confirm',
|
159
|
+
name: 'proceed',
|
160
|
+
message: 'Proceed with these settings?',
|
161
|
+
default: true
|
162
|
+
}
|
163
|
+
]);
|
164
|
+
|
165
|
+
if (!confirmAnswers.proceed) {
|
166
|
+
console.log(chalk.yellow('Operation cancelled by user.'));
|
167
|
+
process.exit(0);
|
168
|
+
}
|
169
|
+
}
|
170
|
+
|
171
|
+
// Run the sandbox
|
172
|
+
await runModalSandbox({
|
173
|
+
repoUrl,
|
174
|
+
gpuType,
|
175
|
+
volumeName,
|
176
|
+
setupCommands
|
177
|
+
});
|
178
|
+
|
179
|
+
} catch (error) {
|
180
|
+
console.error(chalk.red(`Error: ${error.message}`));
|
181
|
+
process.exit(1);
|
182
|
+
}
|
183
|
+
}
|
184
|
+
|
185
|
+
main();
|
package/index.js
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env node
|
2
|
+
|
3
|
+
// This file is the main entry point for the package
|
4
|
+
// It simply re-exports the main functionality
|
5
|
+
|
6
|
+
const { runModalSandbox } = require('./lib/sandbox');
|
7
|
+
const { checkDependencies } = require('./lib/dependencies');
|
8
|
+
|
9
|
+
module.exports = {
|
10
|
+
runModalSandbox,
|
11
|
+
checkDependencies
|
12
|
+
};
|
package/install.sh
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
# GitArsenal CLI Installation Script
|
4
|
+
|
5
|
+
echo "🚀 Installing GitArsenal CLI..."
|
6
|
+
|
7
|
+
# Check if npm is installed
|
8
|
+
if ! command -v npm &> /dev/null; then
|
9
|
+
echo "❌ npm is not installed. Please install Node.js and npm first."
|
10
|
+
exit 1
|
11
|
+
fi
|
12
|
+
|
13
|
+
# Check if Python is installed
|
14
|
+
if ! command -v python &> /dev/null && ! command -v python3 &> /dev/null; then
|
15
|
+
echo "❌ Python is not installed. Please install Python 3.8 or higher."
|
16
|
+
exit 1
|
17
|
+
fi
|
18
|
+
|
19
|
+
# Check if Modal is installed
|
20
|
+
if ! command -v modal &> /dev/null; then
|
21
|
+
echo "⚠️ Modal CLI not found. Installing modal-client..."
|
22
|
+
pip install modal || python3 -m pip install modal
|
23
|
+
fi
|
24
|
+
|
25
|
+
# Install the package globally
|
26
|
+
echo "📦 Installing GitArsenal CLI globally..."
|
27
|
+
npm install -g .
|
28
|
+
|
29
|
+
echo "✅ Installation complete!"
|
30
|
+
echo "You can now run the CLI tool with: gitarsenal"
|
@@ -0,0 +1,113 @@
|
|
1
|
+
const { promisify } = require('util');
|
2
|
+
const { exec } = require('child_process');
|
3
|
+
const which = require('which');
|
4
|
+
const chalk = require('chalk');
|
5
|
+
|
6
|
+
const execAsync = promisify(exec);
|
7
|
+
|
8
|
+
/**
|
9
|
+
* Check if a command is available in the system
|
10
|
+
* @param {string} command - Command to check
|
11
|
+
* @returns {Promise<boolean>} - True if command exists
|
12
|
+
*/
|
13
|
+
async function commandExists(command) {
|
14
|
+
try {
|
15
|
+
await which(command);
|
16
|
+
return true;
|
17
|
+
} catch (error) {
|
18
|
+
return false;
|
19
|
+
}
|
20
|
+
}
|
21
|
+
|
22
|
+
/**
|
23
|
+
* Check Python version
|
24
|
+
* @returns {Promise<{exists: boolean, version: string|null}>}
|
25
|
+
*/
|
26
|
+
async function checkPython() {
|
27
|
+
try {
|
28
|
+
const { stdout } = await execAsync('python --version');
|
29
|
+
const version = stdout.trim().split(' ')[1];
|
30
|
+
return { exists: true, version };
|
31
|
+
} catch (error) {
|
32
|
+
try {
|
33
|
+
const { stdout } = await execAsync('python3 --version');
|
34
|
+
const version = stdout.trim().split(' ')[1];
|
35
|
+
return { exists: true, version };
|
36
|
+
} catch (error) {
|
37
|
+
return { exists: false, version: null };
|
38
|
+
}
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
/**
|
43
|
+
* Check if Modal is installed
|
44
|
+
* @returns {Promise<boolean>}
|
45
|
+
*/
|
46
|
+
async function checkModal() {
|
47
|
+
try {
|
48
|
+
await execAsync('modal --version');
|
49
|
+
return true;
|
50
|
+
} catch (error) {
|
51
|
+
return false;
|
52
|
+
}
|
53
|
+
}
|
54
|
+
|
55
|
+
/**
|
56
|
+
* Check if Git is installed
|
57
|
+
* @returns {Promise<boolean>}
|
58
|
+
*/
|
59
|
+
async function checkGit() {
|
60
|
+
try {
|
61
|
+
await execAsync('git --version');
|
62
|
+
return true;
|
63
|
+
} catch (error) {
|
64
|
+
return false;
|
65
|
+
}
|
66
|
+
}
|
67
|
+
|
68
|
+
/**
|
69
|
+
* Check all required dependencies
|
70
|
+
* @returns {Promise<boolean>} - True if all dependencies are met
|
71
|
+
*/
|
72
|
+
async function checkDependencies() {
|
73
|
+
const pythonCheck = await checkPython();
|
74
|
+
const modalExists = await checkModal();
|
75
|
+
const gitExists = await checkGit();
|
76
|
+
|
77
|
+
let allDependenciesMet = true;
|
78
|
+
|
79
|
+
console.log('\n--- Dependency Check ---');
|
80
|
+
|
81
|
+
if (pythonCheck.exists) {
|
82
|
+
console.log(`${chalk.green('✓')} Python ${pythonCheck.version} found`);
|
83
|
+
} else {
|
84
|
+
console.log(`${chalk.red('✗')} Python not found. Please install Python 3.8 or newer.`);
|
85
|
+
allDependenciesMet = false;
|
86
|
+
}
|
87
|
+
|
88
|
+
if (modalExists) {
|
89
|
+
console.log(`${chalk.green('✓')} Modal CLI found`);
|
90
|
+
} else {
|
91
|
+
console.log(`${chalk.red('✗')} Modal CLI not found. Please install it with: pip install modal`);
|
92
|
+
allDependenciesMet = false;
|
93
|
+
}
|
94
|
+
|
95
|
+
if (gitExists) {
|
96
|
+
console.log(`${chalk.green('✓')} Git found`);
|
97
|
+
} else {
|
98
|
+
console.log(`${chalk.red('✗')} Git not found. Please install Git.`);
|
99
|
+
allDependenciesMet = false;
|
100
|
+
}
|
101
|
+
|
102
|
+
console.log('------------------------\n');
|
103
|
+
|
104
|
+
return allDependenciesMet;
|
105
|
+
}
|
106
|
+
|
107
|
+
module.exports = {
|
108
|
+
checkDependencies,
|
109
|
+
commandExists,
|
110
|
+
checkPython,
|
111
|
+
checkModal,
|
112
|
+
checkGit
|
113
|
+
};
|
package/lib/sandbox.js
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
const path = require('path');
|
2
|
+
const fs = require('fs-extra');
|
3
|
+
const { spawn } = require('child_process');
|
4
|
+
const chalk = require('chalk');
|
5
|
+
const ora = require('ora');
|
6
|
+
const { promisify } = require('util');
|
7
|
+
const { exec } = require('child_process');
|
8
|
+
const os = require('os');
|
9
|
+
|
10
|
+
const execAsync = promisify(exec);
|
11
|
+
|
12
|
+
/**
|
13
|
+
* Get the path to the Python script
|
14
|
+
* @returns {string} - Path to the Python script
|
15
|
+
*/
|
16
|
+
function getPythonScriptPath() {
|
17
|
+
// First check if the script exists in the package directory
|
18
|
+
const packageScriptPath = path.join(__dirname, '..', 'python', 'test_modalSandboxScript.py');
|
19
|
+
|
20
|
+
if (fs.existsSync(packageScriptPath)) {
|
21
|
+
return packageScriptPath;
|
22
|
+
}
|
23
|
+
|
24
|
+
// If not found, return the path where it will be copied during installation
|
25
|
+
return packageScriptPath;
|
26
|
+
}
|
27
|
+
|
28
|
+
/**
|
29
|
+
* Run the Modal sandbox with the given options
|
30
|
+
* @param {Object} options - Sandbox options
|
31
|
+
* @param {string} options.repoUrl - GitHub repository URL
|
32
|
+
* @param {string} options.gpuType - GPU type
|
33
|
+
* @param {string} options.volumeName - Volume name
|
34
|
+
* @param {Array<string>} options.setupCommands - Setup commands
|
35
|
+
* @returns {Promise<void>}
|
36
|
+
*/
|
37
|
+
async function runModalSandbox(options) {
|
38
|
+
const { repoUrl, gpuType, volumeName, setupCommands = [] } = options;
|
39
|
+
|
40
|
+
// Get the path to the Python script
|
41
|
+
const scriptPath = getPythonScriptPath();
|
42
|
+
|
43
|
+
// Check if the script exists
|
44
|
+
if (!fs.existsSync(scriptPath)) {
|
45
|
+
throw new Error(`Python script not found at ${scriptPath}. Please reinstall the package.`);
|
46
|
+
}
|
47
|
+
|
48
|
+
// Prepare command arguments
|
49
|
+
const args = [
|
50
|
+
scriptPath,
|
51
|
+
'--gpu', gpuType,
|
52
|
+
'--repo-url', repoUrl
|
53
|
+
];
|
54
|
+
|
55
|
+
if (volumeName) {
|
56
|
+
args.push('--volume-name', volumeName);
|
57
|
+
}
|
58
|
+
|
59
|
+
// Handle setup commands
|
60
|
+
if (setupCommands.length > 0) {
|
61
|
+
// Create a temporary file to store setup commands
|
62
|
+
const tempCommandsFile = path.join(os.tmpdir(), `gitarsenal-commands-${Date.now()}.txt`);
|
63
|
+
fs.writeFileSync(tempCommandsFile, setupCommands.join('\n'));
|
64
|
+
args.push('--commands-file', tempCommandsFile);
|
65
|
+
}
|
66
|
+
|
67
|
+
// Log the command being executed
|
68
|
+
console.log(chalk.dim(`\nExecuting: python ${args.join(' ')}`));
|
69
|
+
|
70
|
+
// Start the spinner
|
71
|
+
const spinner = ora('Launching Modal sandbox...').start();
|
72
|
+
|
73
|
+
try {
|
74
|
+
// Run the Python script
|
75
|
+
const pythonProcess = spawn('python', args, {
|
76
|
+
stdio: 'inherit' // Inherit stdio to show real-time output
|
77
|
+
});
|
78
|
+
|
79
|
+
// Handle process completion
|
80
|
+
return new Promise((resolve, reject) => {
|
81
|
+
pythonProcess.on('close', (code) => {
|
82
|
+
if (code === 0) {
|
83
|
+
spinner.succeed('Modal sandbox launched successfully');
|
84
|
+
resolve();
|
85
|
+
} else {
|
86
|
+
spinner.fail(`Modal sandbox launch failed with exit code ${code}`);
|
87
|
+
reject(new Error(`Process exited with code ${code}`));
|
88
|
+
}
|
89
|
+
});
|
90
|
+
|
91
|
+
// Handle process errors
|
92
|
+
pythonProcess.on('error', (error) => {
|
93
|
+
spinner.fail(`Failed to start Python process: ${error.message}`);
|
94
|
+
reject(error);
|
95
|
+
});
|
96
|
+
});
|
97
|
+
} catch (error) {
|
98
|
+
spinner.fail(`Error launching Modal sandbox: ${error.message}`);
|
99
|
+
throw error;
|
100
|
+
}
|
101
|
+
}
|
102
|
+
|
103
|
+
module.exports = {
|
104
|
+
runModalSandbox,
|
105
|
+
getPythonScriptPath
|
106
|
+
};
|
package/package.json
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
{
|
2
|
+
"name": "gitarsenal-cli",
|
3
|
+
"version": "1.0.0",
|
4
|
+
"description": "CLI tool for creating Modal sandboxes with GitHub repositories",
|
5
|
+
"main": "index.js",
|
6
|
+
"bin": {
|
7
|
+
"gitarsenal": "./bin/gitarsenal.js"
|
8
|
+
},
|
9
|
+
"scripts": {
|
10
|
+
"postinstall": "node scripts/postinstall.js"
|
11
|
+
},
|
12
|
+
"keywords": [
|
13
|
+
"modal",
|
14
|
+
"sandbox",
|
15
|
+
"cli",
|
16
|
+
"gpu",
|
17
|
+
"github",
|
18
|
+
"repository",
|
19
|
+
"setup"
|
20
|
+
],
|
21
|
+
"author": "",
|
22
|
+
"license": "MIT",
|
23
|
+
"dependencies": {
|
24
|
+
"chalk": "^4.1.2",
|
25
|
+
"inquirer": "^8.2.4",
|
26
|
+
"ora": "^5.4.1",
|
27
|
+
"commander": "^9.4.1",
|
28
|
+
"which": "^3.0.0",
|
29
|
+
"fs-extra": "^11.1.0",
|
30
|
+
"execa": "^5.1.1",
|
31
|
+
"boxen": "^5.1.2",
|
32
|
+
"update-notifier": "^5.1.0"
|
33
|
+
},
|
34
|
+
"engines": {
|
35
|
+
"node": ">=14.0.0"
|
36
|
+
}
|
37
|
+
}
|
@@ -0,0 +1,44 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
# This is a minimal version of the Modal sandbox script
|
3
|
+
# For full functionality, please ensure the original script is available
|
4
|
+
|
5
|
+
import os
|
6
|
+
import sys
|
7
|
+
import argparse
|
8
|
+
import subprocess
|
9
|
+
|
10
|
+
def main():
|
11
|
+
parser = argparse.ArgumentParser(description='Create a Modal sandbox with GPU')
|
12
|
+
parser.add_argument('--gpu', type=str, default='A10G', help='GPU type (default: A10G)')
|
13
|
+
parser.add_argument('--repo-url', type=str, help='Repository URL to clone')
|
14
|
+
parser.add_argument('--repo-name', type=str, help='Repository name override')
|
15
|
+
parser.add_argument('--volume-name', type=str, help='Name of the Modal volume for persistent storage')
|
16
|
+
parser.add_argument('--commands-file', type=str, help='Path to file containing setup commands (one per line)')
|
17
|
+
|
18
|
+
args = parser.parse_args()
|
19
|
+
|
20
|
+
# Check if modal is installed
|
21
|
+
try:
|
22
|
+
subprocess.run(['modal', '--version'], check=True, capture_output=True)
|
23
|
+
except (subprocess.SubprocessError, FileNotFoundError):
|
24
|
+
print("❌ Modal CLI not found. Please install it with: pip install modal")
|
25
|
+
sys.exit(1)
|
26
|
+
|
27
|
+
# Build the modal command
|
28
|
+
cmd = [
|
29
|
+
'modal', 'run', '--gpu', args.gpu.lower(),
|
30
|
+
'--command', f'git clone {args.repo_url} && cd $(basename {args.repo_url} .git) && bash'
|
31
|
+
]
|
32
|
+
|
33
|
+
# Execute the command
|
34
|
+
print(f"🚀 Launching Modal sandbox with {args.gpu} GPU...")
|
35
|
+
print(f"📥 Cloning repository: {args.repo_url}")
|
36
|
+
|
37
|
+
try:
|
38
|
+
subprocess.run(cmd)
|
39
|
+
except Exception as e:
|
40
|
+
print(f"❌ Error: {e}")
|
41
|
+
sys.exit(1)
|
42
|
+
|
43
|
+
if __name__ == "__main__":
|
44
|
+
main()
|
@@ -0,0 +1,146 @@
|
|
1
|
+
#!/usr/bin/env node
|
2
|
+
|
3
|
+
const fs = require('fs-extra');
|
4
|
+
const path = require('path');
|
5
|
+
const { promisify } = require('util');
|
6
|
+
const { exec } = require('child_process');
|
7
|
+
const chalk = require('chalk');
|
8
|
+
|
9
|
+
const execAsync = promisify(exec);
|
10
|
+
|
11
|
+
// Path to the Python script in the package
|
12
|
+
const pythonScriptDir = path.join(__dirname, '..', 'python');
|
13
|
+
const pythonScriptPath = path.join(pythonScriptDir, 'test_modalSandboxScript.py');
|
14
|
+
|
15
|
+
// Path to the original Python script
|
16
|
+
const originalScriptPath = path.join(__dirname, '..', '..', '..', 'mcp-server', 'src', 'utils', 'test_modalSandboxScript.py');
|
17
|
+
|
18
|
+
async function postinstall() {
|
19
|
+
try {
|
20
|
+
console.log(chalk.blue('📦 Running GitArsenal CLI postinstall script...'));
|
21
|
+
|
22
|
+
// Create the Python directory if it doesn't exist
|
23
|
+
await fs.ensureDir(pythonScriptDir);
|
24
|
+
|
25
|
+
// Check if the original script exists
|
26
|
+
let scriptFound = false;
|
27
|
+
|
28
|
+
if (await fs.pathExists(originalScriptPath)) {
|
29
|
+
console.log(chalk.green('✅ Found original Python script in mcp-server'));
|
30
|
+
await fs.copy(originalScriptPath, pythonScriptPath);
|
31
|
+
scriptFound = true;
|
32
|
+
} else {
|
33
|
+
// Try to find the script in common locations
|
34
|
+
console.log(chalk.yellow('⚠️ Original script not found in expected location. Searching for alternatives...'));
|
35
|
+
|
36
|
+
const possibleLocations = [
|
37
|
+
path.join(process.cwd(), 'test_modalSandboxScript.py'),
|
38
|
+
path.join(process.cwd(), 'mcp-server', 'src', 'utils', 'test_modalSandboxScript.py'),
|
39
|
+
path.join(process.cwd(), 'src', 'utils', 'test_modalSandboxScript.py'),
|
40
|
+
path.join(process.cwd(), 'utils', 'test_modalSandboxScript.py'),
|
41
|
+
path.join(process.cwd(), 'scripts', 'test_modalSandboxScript.py')
|
42
|
+
];
|
43
|
+
|
44
|
+
for (const location of possibleLocations) {
|
45
|
+
if (await fs.pathExists(location)) {
|
46
|
+
console.log(chalk.green(`✅ Found Python script at ${location}`));
|
47
|
+
await fs.copy(location, pythonScriptPath);
|
48
|
+
scriptFound = true;
|
49
|
+
break;
|
50
|
+
}
|
51
|
+
}
|
52
|
+
}
|
53
|
+
|
54
|
+
// If script not found, create it from embedded content
|
55
|
+
if (!scriptFound) {
|
56
|
+
console.log(chalk.yellow('⚠️ Python script not found. Creating from embedded content...'));
|
57
|
+
|
58
|
+
// Create a minimal version of the script
|
59
|
+
const minimalScript = `#!/usr/bin/env python3
|
60
|
+
# This is a minimal version of the Modal sandbox script
|
61
|
+
# For full functionality, please ensure the original script is available
|
62
|
+
|
63
|
+
import os
|
64
|
+
import sys
|
65
|
+
import argparse
|
66
|
+
import subprocess
|
67
|
+
|
68
|
+
def main():
|
69
|
+
parser = argparse.ArgumentParser(description='Create a Modal sandbox with GPU')
|
70
|
+
parser.add_argument('--gpu', type=str, default='A10G', help='GPU type (default: A10G)')
|
71
|
+
parser.add_argument('--repo-url', type=str, help='Repository URL to clone')
|
72
|
+
parser.add_argument('--repo-name', type=str, help='Repository name override')
|
73
|
+
parser.add_argument('--volume-name', type=str, help='Name of the Modal volume for persistent storage')
|
74
|
+
parser.add_argument('--commands-file', type=str, help='Path to file containing setup commands (one per line)')
|
75
|
+
|
76
|
+
args = parser.parse_args()
|
77
|
+
|
78
|
+
# Check if modal is installed
|
79
|
+
try:
|
80
|
+
subprocess.run(['modal', '--version'], check=True, capture_output=True)
|
81
|
+
except (subprocess.SubprocessError, FileNotFoundError):
|
82
|
+
print("❌ Modal CLI not found. Please install it with: pip install modal")
|
83
|
+
sys.exit(1)
|
84
|
+
|
85
|
+
# Build the modal command
|
86
|
+
cmd = [
|
87
|
+
'modal', 'run', '--gpu', args.gpu.lower(),
|
88
|
+
'--command', f'git clone {args.repo_url} && cd $(basename {args.repo_url} .git) && bash'
|
89
|
+
]
|
90
|
+
|
91
|
+
# Execute the command
|
92
|
+
print(f"🚀 Launching Modal sandbox with {args.gpu} GPU...")
|
93
|
+
print(f"📥 Cloning repository: {args.repo_url}")
|
94
|
+
|
95
|
+
try:
|
96
|
+
subprocess.run(cmd)
|
97
|
+
except Exception as e:
|
98
|
+
print(f"❌ Error: {e}")
|
99
|
+
sys.exit(1)
|
100
|
+
|
101
|
+
if __name__ == "__main__":
|
102
|
+
main()
|
103
|
+
`;
|
104
|
+
|
105
|
+
await fs.writeFile(pythonScriptPath, minimalScript);
|
106
|
+
console.log(chalk.yellow('⚠️ Created minimal Python script. For full functionality, please install the original script.'));
|
107
|
+
}
|
108
|
+
|
109
|
+
// Make the script executable
|
110
|
+
try {
|
111
|
+
await fs.chmod(pythonScriptPath, 0o755);
|
112
|
+
console.log(chalk.green('✅ Made Python script executable'));
|
113
|
+
} catch (error) {
|
114
|
+
console.log(chalk.yellow(`⚠️ Could not make script executable: ${error.message}`));
|
115
|
+
}
|
116
|
+
|
117
|
+
// Check Python and Modal installation
|
118
|
+
try {
|
119
|
+
await execAsync('python --version');
|
120
|
+
console.log(chalk.green('✅ Python is installed'));
|
121
|
+
} catch (error) {
|
122
|
+
try {
|
123
|
+
await execAsync('python3 --version');
|
124
|
+
console.log(chalk.green('✅ Python3 is installed'));
|
125
|
+
} catch (error) {
|
126
|
+
console.log(chalk.yellow('⚠️ Python not found. Please install Python 3.8 or newer.'));
|
127
|
+
}
|
128
|
+
}
|
129
|
+
|
130
|
+
try {
|
131
|
+
await execAsync('modal --version');
|
132
|
+
console.log(chalk.green('✅ Modal CLI is installed'));
|
133
|
+
} catch (error) {
|
134
|
+
console.log(chalk.yellow('⚠️ Modal CLI not found. Please install it with: pip install modal'));
|
135
|
+
}
|
136
|
+
|
137
|
+
console.log(chalk.blue('📦 GitArsenal CLI installation complete!'));
|
138
|
+
console.log(chalk.blue('🚀 Run "gitarsenal" to start using the CLI.'));
|
139
|
+
|
140
|
+
} catch (error) {
|
141
|
+
console.error(chalk.red(`❌ Error during postinstall: ${error.message}`));
|
142
|
+
// Don't exit with error to allow npm install to complete
|
143
|
+
}
|
144
|
+
}
|
145
|
+
|
146
|
+
postinstall();
|
package/test.js
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env node
|
2
|
+
|
3
|
+
// Simple test script to verify the package works
|
4
|
+
|
5
|
+
const { checkDependencies } = require('./lib/dependencies');
|
6
|
+
const chalk = require('chalk');
|
7
|
+
|
8
|
+
async function test() {
|
9
|
+
console.log(chalk.blue('🧪 Testing GitArsenal CLI...'));
|
10
|
+
|
11
|
+
// Check dependencies
|
12
|
+
console.log(chalk.yellow('Checking dependencies...'));
|
13
|
+
const dependenciesOk = await checkDependencies();
|
14
|
+
|
15
|
+
if (dependenciesOk) {
|
16
|
+
console.log(chalk.green('✅ All dependencies are installed!'));
|
17
|
+
console.log(chalk.green('✅ GitArsenal CLI is ready to use.'));
|
18
|
+
console.log(chalk.blue('Run "gitarsenal" to start using the CLI.'));
|
19
|
+
} else {
|
20
|
+
console.log(chalk.red('❌ Some dependencies are missing. Please install them before using GitArsenal CLI.'));
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
test().catch(error => {
|
25
|
+
console.error(chalk.red(`❌ Test failed: ${error.message}`));
|
26
|
+
process.exit(1);
|
27
|
+
});
|