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 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
+ });