gitarsenal-cli 1.2.8 → 1.3.2
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/bin/gitarsenal.js +321 -20
- package/lib/sandbox.js +56 -12
- package/package.json +2 -2
- package/python/emoji_fix_patch.py +79 -0
- package/python/modal_logs_patch.py +86 -0
- package/python/test_modalSandboxScript.py +113 -143
- package/python/test_modalSandboxScript.py.bak +2742 -0
- package/python/test_modalSandboxScript.py.emoji_backup +2742 -0
- package/scripts/postinstall.js +1 -1
- package/test_modalSandboxScript.py +1219 -416
- package/python/__pycache__/credentials_manager.cpython-313.pyc +0 -0
- package/python/__pycache__/gitarsenal_proxy_client.cpython-313.pyc +0 -0
- package/python/__pycache__/setup_modal_token.cpython-313.pyc +0 -0
- package/python/__pycache__/test_modalSandboxScript.cpython-313.pyc +0 -0
package/bin/gitarsenal.js
CHANGED
@@ -7,7 +7,7 @@ const ora = require('ora');
|
|
7
7
|
const path = require('path');
|
8
8
|
const { version } = require('../package.json');
|
9
9
|
const { checkDependencies } = require('../lib/dependencies');
|
10
|
-
const {
|
10
|
+
const { runContainer } = require('../lib/sandbox');
|
11
11
|
const updateNotifier = require('update-notifier');
|
12
12
|
const pkg = require('../package.json');
|
13
13
|
const boxen = require('boxen');
|
@@ -16,28 +16,99 @@ const boxen = require('boxen');
|
|
16
16
|
updateNotifier({ pkg }).notify();
|
17
17
|
|
18
18
|
// Display banner
|
19
|
-
console.log(boxen(chalk.bold.green('GitArsenal CLI') + '\n' + chalk.blue('Create
|
19
|
+
console.log(boxen(chalk.bold.green('GitArsenal CLI') + '\n' + chalk.blue('Create GPU-accelerated development environments'), {
|
20
20
|
padding: 1,
|
21
21
|
margin: 1,
|
22
22
|
borderStyle: 'round',
|
23
23
|
borderColor: 'green'
|
24
24
|
}));
|
25
25
|
|
26
|
-
// Set up
|
26
|
+
// Set up main command
|
27
27
|
program
|
28
28
|
.version(version)
|
29
|
-
.description('
|
29
|
+
.description('GitArsenal CLI - Create GPU-accelerated development environments');
|
30
|
+
|
31
|
+
// Container command
|
32
|
+
const containerCmd = program
|
33
|
+
.command('container')
|
34
|
+
.description('Create a container with a GitHub repository')
|
35
|
+
.option('-g, --gpu <type>', 'GPU type (A10G, A100, H100, T4, L4, L40S, V100)', 'A10G')
|
36
|
+
.option('-r, --repo-url <url>', 'GitHub repository URL')
|
37
|
+
.option('-v, --volume-name <n>', 'Name of persistent volume')
|
38
|
+
.option('-s, --setup-commands <commands...>', 'Setup commands to run in the container')
|
39
|
+
.option('-y, --yes', 'Skip confirmation prompts')
|
40
|
+
.option('-m, --manual', 'Disable automatic setup command detection')
|
41
|
+
.option('-i, --interactive', 'Run in interactive mode with prompts')
|
42
|
+
.option('--show-examples', 'Show usage examples')
|
43
|
+
.action(async (options) => {
|
44
|
+
await runContainerCommand(options);
|
45
|
+
});
|
46
|
+
|
47
|
+
// Keys management command
|
48
|
+
const keysCmd = program
|
49
|
+
.command('keys')
|
50
|
+
.description('Manage API keys for services');
|
51
|
+
|
52
|
+
keysCmd
|
53
|
+
.command('add')
|
54
|
+
.description('Add an API key')
|
55
|
+
.option('-s, --service <service>', 'Service name (openai, wandb, huggingface)')
|
56
|
+
.option('-k, --key <key>', 'API key (if not provided, will prompt)')
|
57
|
+
.action(async (options) => {
|
58
|
+
await handleKeysAdd(options);
|
59
|
+
});
|
60
|
+
|
61
|
+
keysCmd
|
62
|
+
.command('list')
|
63
|
+
.description('List saved API keys')
|
64
|
+
.action(async () => {
|
65
|
+
await handleKeysList();
|
66
|
+
});
|
67
|
+
|
68
|
+
keysCmd
|
69
|
+
.command('view')
|
70
|
+
.description('View a specific API key (masked)')
|
71
|
+
.option('-s, --service <service>', 'Service name')
|
72
|
+
.action(async (options) => {
|
73
|
+
await handleKeysView(options);
|
74
|
+
});
|
75
|
+
|
76
|
+
keysCmd
|
77
|
+
.command('delete')
|
78
|
+
.description('Delete an API key')
|
79
|
+
.option('-s, --service <service>', 'Service name')
|
80
|
+
.action(async (options) => {
|
81
|
+
await handleKeysDelete(options);
|
82
|
+
});
|
83
|
+
|
84
|
+
// For backward compatibility, support running without a subcommand
|
85
|
+
program
|
30
86
|
.option('-r, --repo <url>', 'GitHub repository URL')
|
31
|
-
.option('-g, --gpu <type>', 'GPU type (A10G, A100, H100, T4, V100)', 'A10G')
|
87
|
+
.option('-g, --gpu <type>', 'GPU type (A10G, A100, H100, T4, L4, L40S, V100)', 'A10G')
|
32
88
|
.option('-v, --volume <n>', 'Name of persistent volume')
|
33
89
|
.option('-y, --yes', 'Skip confirmation prompts')
|
34
90
|
.option('-m, --manual', 'Disable automatic setup command detection')
|
35
|
-
.
|
91
|
+
.option('-i, --interactive', 'Run in interactive mode with prompts')
|
92
|
+
.option('--show-examples', 'Show usage examples')
|
93
|
+
.action(async (options) => {
|
94
|
+
// If options are provided directly, run the container command
|
95
|
+
if (options.repo || options.interactive || options.showExamples || process.argv.length <= 3) {
|
96
|
+
await runContainerCommand(options);
|
97
|
+
}
|
98
|
+
});
|
36
99
|
|
37
|
-
|
100
|
+
program.parse(process.argv);
|
38
101
|
|
39
|
-
async function
|
102
|
+
async function runContainerCommand(options) {
|
40
103
|
try {
|
104
|
+
// If show-examples flag is set, just show examples and exit
|
105
|
+
if (options.showExamples) {
|
106
|
+
await runContainer({
|
107
|
+
showExamples: true
|
108
|
+
});
|
109
|
+
return;
|
110
|
+
}
|
111
|
+
|
41
112
|
// Check for required dependencies
|
42
113
|
const spinner = ora('Checking dependencies...').start();
|
43
114
|
const dependenciesOk = await checkDependencies();
|
@@ -48,12 +119,21 @@ async function main() {
|
|
48
119
|
}
|
49
120
|
spinner.succeed('Dependencies checked');
|
50
121
|
|
122
|
+
// If interactive mode is enabled, let the Python script handle the prompts
|
123
|
+
if (options.interactive) {
|
124
|
+
await runContainer({
|
125
|
+
interactive: true
|
126
|
+
});
|
127
|
+
return;
|
128
|
+
}
|
129
|
+
|
51
130
|
// If repo URL not provided, prompt for it
|
52
|
-
let repoUrl = options.repo;
|
131
|
+
let repoUrl = options.repoUrl || options.repo;
|
53
132
|
let gpuType = options.gpu;
|
54
|
-
let volumeName = options.volume;
|
133
|
+
let volumeName = options.volumeName || options.volume;
|
55
134
|
let skipConfirmation = options.yes;
|
56
135
|
let useApi = !options.manual;
|
136
|
+
let setupCommands = options.setupCommands || [];
|
57
137
|
|
58
138
|
if (!repoUrl) {
|
59
139
|
const answers = await inquirer.prompt([
|
@@ -68,7 +148,7 @@ async function main() {
|
|
68
148
|
}
|
69
149
|
|
70
150
|
// Prompt for GPU type if not specified
|
71
|
-
if (!
|
151
|
+
if (!gpuType) {
|
72
152
|
const gpuAnswers = await inquirer.prompt([
|
73
153
|
{
|
74
154
|
type: 'list',
|
@@ -79,6 +159,8 @@ async function main() {
|
|
79
159
|
{ name: 'A100 (40GB VRAM)', value: 'A100' },
|
80
160
|
{ name: 'H100 (80GB VRAM)', value: 'H100' },
|
81
161
|
{ name: 'T4 (16GB VRAM)', value: 'T4' },
|
162
|
+
{ name: 'L4 (24GB VRAM)', value: 'L4' },
|
163
|
+
{ name: 'L40S (48GB VRAM)', value: 'L40S' },
|
82
164
|
{ name: 'V100 (16GB VRAM)', value: 'V100' }
|
83
165
|
],
|
84
166
|
default: 'A10G'
|
@@ -111,7 +193,7 @@ async function main() {
|
|
111
193
|
}
|
112
194
|
|
113
195
|
// Ask about setup command detection if not specified via CLI
|
114
|
-
if (!options.manual && !options.yes) {
|
196
|
+
if (!options.manual && !options.yes && setupCommands.length === 0) {
|
115
197
|
const apiAnswers = await inquirer.prompt([
|
116
198
|
{
|
117
199
|
type: 'confirm',
|
@@ -124,10 +206,8 @@ async function main() {
|
|
124
206
|
useApi = apiAnswers.useApi;
|
125
207
|
}
|
126
208
|
|
127
|
-
|
128
|
-
|
129
|
-
// Only prompt for custom commands if auto-detection is disabled
|
130
|
-
if (!useApi) {
|
209
|
+
// Only prompt for custom commands if auto-detection is disabled and no commands provided
|
210
|
+
if (!useApi && setupCommands.length === 0) {
|
131
211
|
const setupAnswers = await inquirer.prompt([
|
132
212
|
{
|
133
213
|
type: 'confirm',
|
@@ -156,7 +236,7 @@ async function main() {
|
|
156
236
|
}
|
157
237
|
|
158
238
|
// Show configuration summary
|
159
|
-
console.log(chalk.bold('\n📋
|
239
|
+
console.log(chalk.bold('\n📋 Container Configuration:'));
|
160
240
|
console.log(chalk.cyan('Repository URL: ') + repoUrl);
|
161
241
|
console.log(chalk.cyan('GPU Type: ') + gpuType);
|
162
242
|
console.log(chalk.cyan('Volume: ') + (volumeName || 'None'));
|
@@ -189,8 +269,8 @@ async function main() {
|
|
189
269
|
}
|
190
270
|
}
|
191
271
|
|
192
|
-
// Run the
|
193
|
-
await
|
272
|
+
// Run the container
|
273
|
+
await runContainer({
|
194
274
|
repoUrl,
|
195
275
|
gpuType,
|
196
276
|
volumeName,
|
@@ -204,4 +284,225 @@ async function main() {
|
|
204
284
|
}
|
205
285
|
}
|
206
286
|
|
207
|
-
|
287
|
+
async function handleKeysAdd(options) {
|
288
|
+
try {
|
289
|
+
const spinner = ora('Adding API key...').start();
|
290
|
+
|
291
|
+
let service = options.service;
|
292
|
+
let key = options.key;
|
293
|
+
|
294
|
+
if (!service) {
|
295
|
+
spinner.stop();
|
296
|
+
const serviceAnswer = await inquirer.prompt([
|
297
|
+
{
|
298
|
+
type: 'list',
|
299
|
+
name: 'service',
|
300
|
+
message: 'Select service:',
|
301
|
+
choices: [
|
302
|
+
{ name: 'OpenAI', value: 'openai' },
|
303
|
+
{ name: 'Weights & Biases', value: 'wandb' },
|
304
|
+
{ name: 'Hugging Face', value: 'huggingface' }
|
305
|
+
]
|
306
|
+
}
|
307
|
+
]);
|
308
|
+
service = serviceAnswer.service;
|
309
|
+
}
|
310
|
+
|
311
|
+
if (!key) {
|
312
|
+
spinner.stop();
|
313
|
+
const keyAnswer = await inquirer.prompt([
|
314
|
+
{
|
315
|
+
type: 'password',
|
316
|
+
name: 'key',
|
317
|
+
message: `Enter ${service} API key:`,
|
318
|
+
mask: '*'
|
319
|
+
}
|
320
|
+
]);
|
321
|
+
key = keyAnswer.key;
|
322
|
+
}
|
323
|
+
|
324
|
+
// Call Python script to add the key
|
325
|
+
const { spawn } = require('child_process');
|
326
|
+
const scriptPath = require('../lib/sandbox').getPythonScriptPath();
|
327
|
+
|
328
|
+
const pythonProcess = spawn('python', [
|
329
|
+
scriptPath,
|
330
|
+
'keys',
|
331
|
+
'add',
|
332
|
+
'--service', service,
|
333
|
+
'--key', key
|
334
|
+
], { stdio: 'pipe' });
|
335
|
+
|
336
|
+
let output = '';
|
337
|
+
pythonProcess.stdout.on('data', (data) => {
|
338
|
+
output += data.toString();
|
339
|
+
});
|
340
|
+
|
341
|
+
pythonProcess.stderr.on('data', (data) => {
|
342
|
+
output += data.toString();
|
343
|
+
});
|
344
|
+
|
345
|
+
pythonProcess.on('close', (code) => {
|
346
|
+
if (code === 0) {
|
347
|
+
spinner.succeed(`API key for ${service} added successfully`);
|
348
|
+
} else {
|
349
|
+
spinner.fail(`Failed to add API key: ${output}`);
|
350
|
+
}
|
351
|
+
});
|
352
|
+
|
353
|
+
} catch (error) {
|
354
|
+
console.error(chalk.red(`Error: ${error.message}`));
|
355
|
+
process.exit(1);
|
356
|
+
}
|
357
|
+
}
|
358
|
+
|
359
|
+
async function handleKeysList() {
|
360
|
+
try {
|
361
|
+
const spinner = ora('Fetching API keys...').start();
|
362
|
+
|
363
|
+
// Call Python script to list keys
|
364
|
+
const { spawn } = require('child_process');
|
365
|
+
const scriptPath = require('../lib/sandbox').getPythonScriptPath();
|
366
|
+
|
367
|
+
const pythonProcess = spawn('python', [
|
368
|
+
scriptPath,
|
369
|
+
'keys',
|
370
|
+
'list'
|
371
|
+
], { stdio: 'inherit' });
|
372
|
+
|
373
|
+
pythonProcess.on('close', (code) => {
|
374
|
+
if (code !== 0) {
|
375
|
+
spinner.fail('Failed to list API keys');
|
376
|
+
} else {
|
377
|
+
spinner.stop();
|
378
|
+
}
|
379
|
+
});
|
380
|
+
|
381
|
+
} catch (error) {
|
382
|
+
console.error(chalk.red(`Error: ${error.message}`));
|
383
|
+
process.exit(1);
|
384
|
+
}
|
385
|
+
}
|
386
|
+
|
387
|
+
async function handleKeysView(options) {
|
388
|
+
try {
|
389
|
+
const spinner = ora('Viewing API key...').start();
|
390
|
+
|
391
|
+
let service = options.service;
|
392
|
+
|
393
|
+
if (!service) {
|
394
|
+
spinner.stop();
|
395
|
+
const serviceAnswer = await inquirer.prompt([
|
396
|
+
{
|
397
|
+
type: 'list',
|
398
|
+
name: 'service',
|
399
|
+
message: 'Select service:',
|
400
|
+
choices: [
|
401
|
+
{ name: 'OpenAI', value: 'openai' },
|
402
|
+
{ name: 'Weights & Biases', value: 'wandb' },
|
403
|
+
{ name: 'Hugging Face', value: 'huggingface' }
|
404
|
+
]
|
405
|
+
}
|
406
|
+
]);
|
407
|
+
service = serviceAnswer.service;
|
408
|
+
}
|
409
|
+
|
410
|
+
// Call Python script to view the key
|
411
|
+
const { spawn } = require('child_process');
|
412
|
+
const scriptPath = require('../lib/sandbox').getPythonScriptPath();
|
413
|
+
|
414
|
+
const pythonProcess = spawn('python', [
|
415
|
+
scriptPath,
|
416
|
+
'keys',
|
417
|
+
'view',
|
418
|
+
'--service', service
|
419
|
+
], { stdio: 'inherit' });
|
420
|
+
|
421
|
+
pythonProcess.on('close', (code) => {
|
422
|
+
if (code !== 0) {
|
423
|
+
spinner.fail(`Failed to view API key for ${service}`);
|
424
|
+
} else {
|
425
|
+
spinner.stop();
|
426
|
+
}
|
427
|
+
});
|
428
|
+
|
429
|
+
} catch (error) {
|
430
|
+
console.error(chalk.red(`Error: ${error.message}`));
|
431
|
+
process.exit(1);
|
432
|
+
}
|
433
|
+
}
|
434
|
+
|
435
|
+
async function handleKeysDelete(options) {
|
436
|
+
try {
|
437
|
+
const spinner = ora('Deleting API key...').start();
|
438
|
+
|
439
|
+
let service = options.service;
|
440
|
+
|
441
|
+
if (!service) {
|
442
|
+
spinner.stop();
|
443
|
+
const serviceAnswer = await inquirer.prompt([
|
444
|
+
{
|
445
|
+
type: 'list',
|
446
|
+
name: 'service',
|
447
|
+
message: 'Select service:',
|
448
|
+
choices: [
|
449
|
+
{ name: 'OpenAI', value: 'openai' },
|
450
|
+
{ name: 'Weights & Biases', value: 'wandb' },
|
451
|
+
{ name: 'Hugging Face', value: 'huggingface' }
|
452
|
+
]
|
453
|
+
}
|
454
|
+
]);
|
455
|
+
service = serviceAnswer.service;
|
456
|
+
}
|
457
|
+
|
458
|
+
// Confirm deletion
|
459
|
+
spinner.stop();
|
460
|
+
const confirmAnswer = await inquirer.prompt([
|
461
|
+
{
|
462
|
+
type: 'confirm',
|
463
|
+
name: 'confirm',
|
464
|
+
message: `Are you sure you want to delete the API key for ${service}?`,
|
465
|
+
default: false
|
466
|
+
}
|
467
|
+
]);
|
468
|
+
|
469
|
+
if (!confirmAnswer.confirm) {
|
470
|
+
console.log(chalk.yellow('Operation cancelled by user.'));
|
471
|
+
return;
|
472
|
+
}
|
473
|
+
|
474
|
+
spinner.start();
|
475
|
+
|
476
|
+
// Call Python script to delete the key
|
477
|
+
const { spawn } = require('child_process');
|
478
|
+
const scriptPath = require('../lib/sandbox').getPythonScriptPath();
|
479
|
+
|
480
|
+
const pythonProcess = spawn('python', [
|
481
|
+
scriptPath,
|
482
|
+
'keys',
|
483
|
+
'delete',
|
484
|
+
'--service', service
|
485
|
+
], { stdio: 'pipe' });
|
486
|
+
|
487
|
+
let output = '';
|
488
|
+
pythonProcess.stdout.on('data', (data) => {
|
489
|
+
output += data.toString();
|
490
|
+
});
|
491
|
+
|
492
|
+
pythonProcess.stderr.on('data', (data) => {
|
493
|
+
output += data.toString();
|
494
|
+
});
|
495
|
+
|
496
|
+
pythonProcess.on('close', (code) => {
|
497
|
+
if (code === 0) {
|
498
|
+
spinner.succeed(`API key for ${service} deleted successfully`);
|
499
|
+
} else {
|
500
|
+
spinner.fail(`Failed to delete API key: ${output}`);
|
501
|
+
}
|
502
|
+
});
|
503
|
+
|
504
|
+
} catch (error) {
|
505
|
+
console.error(chalk.red(`Error: ${error.message}`));
|
506
|
+
process.exit(1);
|
507
|
+
}
|
508
|
+
}
|
package/lib/sandbox.js
CHANGED
@@ -26,17 +26,27 @@ function getPythonScriptPath() {
|
|
26
26
|
}
|
27
27
|
|
28
28
|
/**
|
29
|
-
* Run the
|
30
|
-
* @param {Object} options -
|
29
|
+
* Run the container with the given options
|
30
|
+
* @param {Object} options - Container options
|
31
31
|
* @param {string} options.repoUrl - GitHub repository URL
|
32
32
|
* @param {string} options.gpuType - GPU type
|
33
33
|
* @param {string} options.volumeName - Volume name
|
34
34
|
* @param {Array<string>} options.setupCommands - Setup commands
|
35
35
|
* @param {boolean} options.useApi - Whether to use the API to fetch setup commands
|
36
|
+
* @param {boolean} options.interactive - Whether to run in interactive mode
|
37
|
+
* @param {boolean} options.showExamples - Whether to show usage examples
|
36
38
|
* @returns {Promise<void>}
|
37
39
|
*/
|
38
|
-
async function
|
39
|
-
const {
|
40
|
+
async function runContainer(options) {
|
41
|
+
const {
|
42
|
+
repoUrl,
|
43
|
+
gpuType,
|
44
|
+
volumeName,
|
45
|
+
setupCommands = [],
|
46
|
+
useApi = true,
|
47
|
+
interactive = false,
|
48
|
+
showExamples = false
|
49
|
+
} = options;
|
40
50
|
|
41
51
|
// Get the path to the Python script
|
42
52
|
const scriptPath = getPythonScriptPath();
|
@@ -48,10 +58,44 @@ async function runModalSandbox(options) {
|
|
48
58
|
|
49
59
|
// Prepare command arguments
|
50
60
|
const args = [
|
51
|
-
scriptPath
|
52
|
-
'--gpu', gpuType,
|
53
|
-
'--repo-url', repoUrl
|
61
|
+
scriptPath
|
54
62
|
];
|
63
|
+
|
64
|
+
// If show examples is true, only pass that flag
|
65
|
+
if (showExamples) {
|
66
|
+
args.push('--show-examples');
|
67
|
+
|
68
|
+
// Log the command being executed
|
69
|
+
console.log(chalk.dim(`\nExecuting: python ${args.join(' ')}`));
|
70
|
+
|
71
|
+
// Run the Python script with show examples flag
|
72
|
+
const pythonProcess = spawn('python', args, {
|
73
|
+
stdio: 'inherit' // Inherit stdio to show real-time output
|
74
|
+
});
|
75
|
+
|
76
|
+
return new Promise((resolve, reject) => {
|
77
|
+
pythonProcess.on('close', (code) => {
|
78
|
+
if (code === 0) {
|
79
|
+
resolve();
|
80
|
+
} else {
|
81
|
+
reject(new Error(`Process exited with code ${code}`));
|
82
|
+
}
|
83
|
+
});
|
84
|
+
|
85
|
+
pythonProcess.on('error', (error) => {
|
86
|
+
reject(error);
|
87
|
+
});
|
88
|
+
});
|
89
|
+
}
|
90
|
+
|
91
|
+
// Add interactive flag if specified
|
92
|
+
if (interactive) {
|
93
|
+
args.push('--interactive');
|
94
|
+
} else {
|
95
|
+
// Only add these arguments in non-interactive mode
|
96
|
+
if (gpuType) args.push('--gpu', gpuType);
|
97
|
+
if (repoUrl) args.push('--repo-url', repoUrl);
|
98
|
+
}
|
55
99
|
|
56
100
|
if (volumeName) {
|
57
101
|
args.push('--volume-name', volumeName);
|
@@ -74,7 +118,7 @@ async function runModalSandbox(options) {
|
|
74
118
|
console.log(chalk.dim(`\nExecuting: python ${args.join(' ')}`));
|
75
119
|
|
76
120
|
// Start the spinner
|
77
|
-
const spinner = ora('Launching
|
121
|
+
const spinner = ora('Launching container...').start();
|
78
122
|
|
79
123
|
try {
|
80
124
|
// Run the Python script
|
@@ -86,10 +130,10 @@ async function runModalSandbox(options) {
|
|
86
130
|
return new Promise((resolve, reject) => {
|
87
131
|
pythonProcess.on('close', (code) => {
|
88
132
|
if (code === 0) {
|
89
|
-
spinner.succeed('
|
133
|
+
spinner.succeed('Container launched successfully');
|
90
134
|
resolve();
|
91
135
|
} else {
|
92
|
-
spinner.fail(`
|
136
|
+
spinner.fail(`Container launch failed with exit code ${code}`);
|
93
137
|
reject(new Error(`Process exited with code ${code}`));
|
94
138
|
}
|
95
139
|
});
|
@@ -101,12 +145,12 @@ async function runModalSandbox(options) {
|
|
101
145
|
});
|
102
146
|
});
|
103
147
|
} catch (error) {
|
104
|
-
spinner.fail(`Error launching
|
148
|
+
spinner.fail(`Error launching container: ${error.message}`);
|
105
149
|
throw error;
|
106
150
|
}
|
107
151
|
}
|
108
152
|
|
109
153
|
module.exports = {
|
110
|
-
|
154
|
+
runContainer,
|
111
155
|
getPythonScriptPath
|
112
156
|
};
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "gitarsenal-cli",
|
3
|
-
"version": "1.2
|
3
|
+
"version": "1.3.2",
|
4
4
|
"description": "CLI tool for creating Modal sandboxes with GitHub repositories",
|
5
5
|
"main": "index.js",
|
6
6
|
"bin": {
|
@@ -34,4 +34,4 @@
|
|
34
34
|
"engines": {
|
35
35
|
"node": ">=14.0.0"
|
36
36
|
}
|
37
|
-
}
|
37
|
+
}
|
@@ -0,0 +1,79 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
This script removes emojis from the test_modalSandboxScript.py file to fix syntax errors.
|
4
|
+
"""
|
5
|
+
|
6
|
+
import re
|
7
|
+
import sys
|
8
|
+
import shutil
|
9
|
+
from pathlib import Path
|
10
|
+
|
11
|
+
def remove_emojis(script_path):
|
12
|
+
"""
|
13
|
+
Remove emojis from the Python script to fix syntax errors.
|
14
|
+
"""
|
15
|
+
print(f"Removing emojis from {script_path}...")
|
16
|
+
|
17
|
+
# Make a backup of the original file
|
18
|
+
backup_path = f"{script_path}.emoji_backup"
|
19
|
+
shutil.copy2(script_path, backup_path)
|
20
|
+
|
21
|
+
# Read the file
|
22
|
+
with open(script_path, 'r', encoding='utf-8', errors='ignore') as f:
|
23
|
+
content = f.read()
|
24
|
+
|
25
|
+
# Emoji pattern - matches most common emoji characters
|
26
|
+
emoji_pattern = re.compile(
|
27
|
+
"["
|
28
|
+
"\U0001F1E0-\U0001F1FF" # flags (iOS)
|
29
|
+
"\U0001F300-\U0001F5FF" # symbols & pictographs
|
30
|
+
"\U0001F600-\U0001F64F" # emoticons
|
31
|
+
"\U0001F680-\U0001F6FF" # transport & map symbols
|
32
|
+
"\U0001F700-\U0001F77F" # alchemical symbols
|
33
|
+
"\U0001F780-\U0001F7FF" # Geometric Shapes
|
34
|
+
"\U0001F800-\U0001F8FF" # Supplemental Arrows-C
|
35
|
+
"\U0001F900-\U0001F9FF" # Supplemental Symbols and Pictographs
|
36
|
+
"\U0001FA00-\U0001FA6F" # Chess Symbols
|
37
|
+
"\U0001FA70-\U0001FAFF" # Symbols and Pictographs Extended-A
|
38
|
+
"\U00002702-\U000027B0" # Dingbats
|
39
|
+
"\U000024C2-\U0001F251"
|
40
|
+
"]+", flags=re.UNICODE)
|
41
|
+
|
42
|
+
# Simply remove all emojis
|
43
|
+
content = emoji_pattern.sub('', content)
|
44
|
+
|
45
|
+
# Fix common syntax issues after emoji removal
|
46
|
+
content = re.sub(r'print\(\s*\)', r'print()', content)
|
47
|
+
content = re.sub(r'print\(\s*"', r'print("', content)
|
48
|
+
|
49
|
+
# Fix specific syntax errors
|
50
|
+
content = re.sub(r'print\(\s*container\'s', r'print("container\'s', content)
|
51
|
+
|
52
|
+
# Write the modified content back to the file
|
53
|
+
with open(script_path, 'w', encoding='utf-8') as f:
|
54
|
+
f.write(content)
|
55
|
+
|
56
|
+
print(f"Emoji removal complete. Original file backed up to {backup_path}")
|
57
|
+
return True
|
58
|
+
|
59
|
+
def main():
|
60
|
+
"""
|
61
|
+
Main entry point for the script.
|
62
|
+
"""
|
63
|
+
# Get the path to the script
|
64
|
+
script_dir = Path(__file__).parent
|
65
|
+
script_path = script_dir / "test_modalSandboxScript.py"
|
66
|
+
|
67
|
+
if not script_path.exists():
|
68
|
+
print(f"Error: Script not found at {script_path}")
|
69
|
+
return 1
|
70
|
+
|
71
|
+
try:
|
72
|
+
remove_emojis(script_path)
|
73
|
+
return 0
|
74
|
+
except Exception as e:
|
75
|
+
print(f"Error removing emojis: {e}")
|
76
|
+
return 1
|
77
|
+
|
78
|
+
if __name__ == "__main__":
|
79
|
+
sys.exit(main())
|