gitarsenal-cli 1.9.106 → 1.9.108
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/README-TYPING.md +91 -0
- package/START-HERE.md +230 -0
- package/bin/gitarsenal-tui.js +147 -0
- package/bin/gitarsenal.js +58 -15
- package/launch-tui.sh +18 -0
- package/package.json +9 -3
- package/scripts/ensure-dependencies.sh +46 -0
- package/scripts/postinstall.js +22 -7
- package/tui/App.jsx +326 -0
- package/tui/index.js +37 -0
- package/tui/simple-test.js +41 -0
- package/tui-app/bun.lock +200 -0
- package/tui-app/index-manual.js.bak +609 -0
- package/tui-app/index.jsx +848 -0
- package/tui-app/package-lock.json +1720 -0
- package/tui-app/package.json +16 -0
|
@@ -0,0 +1,609 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { execSync, spawn } from 'child_process';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import { dirname } from 'path';
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = dirname(__filename);
|
|
10
|
+
|
|
11
|
+
// Helper function to center text
|
|
12
|
+
function center(text, stripAnsi = false) {
|
|
13
|
+
const terminalWidth = process.stdout.columns || 80;
|
|
14
|
+
// Remove ANSI codes for width calculation if needed
|
|
15
|
+
const visibleText = stripAnsi ? text.replace(/\x1b\[[0-9;]*m/g, '') : text;
|
|
16
|
+
const padding = Math.max(0, Math.floor((terminalWidth - visibleText.length) / 2));
|
|
17
|
+
return ' '.repeat(padding) + text;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
console.clear();
|
|
21
|
+
console.log(center('\x1b[32m╭────────────────────────────────────────────────────────────────────────────────╮\x1b[0m', true));
|
|
22
|
+
console.log(center('\x1b[32m│ │\x1b[0m', true));
|
|
23
|
+
console.log(center('\x1b[32m│ \x1b[1m\x1b[32m██████╗ ██╗████████╗ █████╗ ██████╗ ███████╗███████╗███╗ ██╗ █████╗ ██╗\x1b[0m\x1b[32m │\x1b[0m', true));
|
|
24
|
+
console.log(center('\x1b[32m│ \x1b[1m\x1b[32m██╔════╝ ██║╚══██╔══╝██╔══██╗██╔══██╗██╔════╝██╔════╝████╗ ██║██╔══██╗██║\x1b[0m\x1b[32m │\x1b[0m', true));
|
|
25
|
+
console.log(center('\x1b[32m│ \x1b[1m\x1b[32m██║ ███╗██║ ██║ ███████║██████╔╝███████╗█████╗ ██╔██╗ ██║███████║██║\x1b[0m\x1b[32m │\x1b[0m', true));
|
|
26
|
+
console.log(center('\x1b[32m│ \x1b[1m\x1b[32m██║ ██║██║ ██║ ██╔══██║██╔══██╗╚════██║██╔══╝ ██║╚██╗██║██╔══██║██║\x1b[0m\x1b[32m │\x1b[0m', true));
|
|
27
|
+
console.log(center('\x1b[32m│ \x1b[1m\x1b[32m╚██████╔╝██║ ██║ ██║ ██║██║ ██║███████║███████╗██║ ╚████║██║ ██║███████╗\x1b[0m\x1b[32m│\x1b[0m', true));
|
|
28
|
+
console.log(center('\x1b[32m│ \x1b[1m\x1b[32m╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚══════╝╚═╝ ╚═══╝╚═╝ ╚═╝╚══════╝\x1b[0m\x1b[32m│\x1b[0m', true));
|
|
29
|
+
console.log(center('\x1b[32m│ │\x1b[0m', true));
|
|
30
|
+
console.log(center('\x1b[32m│ \x1b[1m\x1b[37m GPU-Accelerated Development Environments\x1b[0m\x1b[32m │\x1b[0m', true));
|
|
31
|
+
console.log(center('\x1b[32m│ \x1b[90m 🎨 Powered by OpenTUI Framework\x1b[0m\x1b[32m │\x1b[0m', true));
|
|
32
|
+
console.log(center('\x1b[32m│ │\x1b[0m', true));
|
|
33
|
+
console.log(center('\x1b[32m╰────────────────────────────────────────────────────────────────────────────────╯\x1b[0m', true));
|
|
34
|
+
console.log('');
|
|
35
|
+
|
|
36
|
+
const menuItems = [
|
|
37
|
+
'🚀 Create New Sandbox',
|
|
38
|
+
'📋 View Running Sandboxes',
|
|
39
|
+
'🔑 API Keys Management',
|
|
40
|
+
'⚙️ Settings',
|
|
41
|
+
'❓ Help & Examples',
|
|
42
|
+
'🚪 Exit'
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
const gpuOptions = [
|
|
46
|
+
{ name: 'T4 (16GB VRAM)', value: 'T4' },
|
|
47
|
+
{ name: 'L4 (24GB VRAM)', value: 'L4' },
|
|
48
|
+
{ name: 'A10G (24GB VRAM)', value: 'A10G' },
|
|
49
|
+
{ name: 'A100-40 (40GB VRAM)', value: 'A100-40GB' },
|
|
50
|
+
{ name: 'A100-80 (80GB VRAM)', value: 'A100-80GB' },
|
|
51
|
+
{ name: 'L40S (48GB VRAM)', value: 'L40S' },
|
|
52
|
+
{ name: 'H100 (80GB VRAM)', value: 'H100' },
|
|
53
|
+
{ name: 'H200 (141GB VRAM)', value: 'H200' },
|
|
54
|
+
{ name: 'B200 (141GB VRAM)', value: 'B200' }
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
const gpuCountOptions = [
|
|
58
|
+
{ name: '1 GPU', value: 1 },
|
|
59
|
+
{ name: '2 GPUs', value: 2 },
|
|
60
|
+
{ name: '3 GPUs', value: 3 },
|
|
61
|
+
{ name: '4 GPUs', value: 4 },
|
|
62
|
+
{ name: '6 GPUs', value: 6 },
|
|
63
|
+
{ name: '8 GPUs', value: 8 }
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
const providerOptions = [
|
|
67
|
+
{ name: 'Modal (GPU support, persistent volumes)', value: 'modal' },
|
|
68
|
+
{ name: 'E2B (Faster startup, no GPU)', value: 'e2b' }
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
let selectedIndex = 0;
|
|
72
|
+
let currentScreen = 'menu';
|
|
73
|
+
let inputText = '';
|
|
74
|
+
let config = {
|
|
75
|
+
repoUrl: '',
|
|
76
|
+
gpuType: 'A10G',
|
|
77
|
+
gpuCount: 1,
|
|
78
|
+
sandboxProvider: 'modal'
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// Track running sandboxes
|
|
82
|
+
let sandboxes = [];
|
|
83
|
+
let sandboxIdCounter = 1;
|
|
84
|
+
|
|
85
|
+
// Track currently active process (for logs viewing)
|
|
86
|
+
let activeProcess = null;
|
|
87
|
+
|
|
88
|
+
function renderMenu() {
|
|
89
|
+
console.clear();
|
|
90
|
+
console.log(center('\x1b[32m╭────────────────────────────────────────────────────────────────────────────────╮\x1b[0m', true));
|
|
91
|
+
console.log(center('\x1b[32m│ │\x1b[0m', true));
|
|
92
|
+
console.log(center('\x1b[32m│ \x1b[1m\x1b[32m██████╗ ██╗████████╗ █████╗ ██████╗ ███████╗███████╗███╗ ██╗ █████╗ ██╗\x1b[0m\x1b[32m │\x1b[0m', true));
|
|
93
|
+
console.log(center('\x1b[32m│ \x1b[1m\x1b[32m██╔════╝ ██║╚══██╔══╝██╔══██╗██╔══██╗██╔════╝██╔════╝████╗ ██║██╔══██╗██║\x1b[0m\x1b[32m │\x1b[0m', true));
|
|
94
|
+
console.log(center('\x1b[32m│ \x1b[1m\x1b[32m██║ ███╗██║ ██║ ███████║██████╔╝███████╗█████╗ ██╔██╗ ██║███████║██║\x1b[0m\x1b[32m │\x1b[0m', true));
|
|
95
|
+
console.log(center('\x1b[32m│ \x1b[1m\x1b[32m██║ ██║██║ ██║ ██╔══██║██╔══██╗╚════██║██╔══╝ ██║╚██╗██║██╔══██║██║\x1b[0m\x1b[32m │\x1b[0m', true));
|
|
96
|
+
console.log(center('\x1b[32m│ \x1b[1m\x1b[32m╚██████╔╝██║ ██║ ██║ ██║██║ ██║███████║███████╗██║ ╚████║██║ ██║███████╗\x1b[0m\x1b[32m│\x1b[0m', true));
|
|
97
|
+
console.log(center('\x1b[32m│ \x1b[1m\x1b[32m╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚══════╝╚═╝ ╚═══╝╚═╝ ╚═╝╚══════╝\x1b[0m\x1b[32m│\x1b[0m', true));
|
|
98
|
+
console.log(center('\x1b[32m│ │\x1b[0m', true));
|
|
99
|
+
console.log(center('\x1b[32m│ \x1b[1m\x1b[37m GPU-Accelerated Development Environments\x1b[0m\x1b[32m │\x1b[0m', true));
|
|
100
|
+
console.log(center('\x1b[32m│ \x1b[90m 🎨 Powered by OpenTUI Framework\x1b[0m\x1b[32m │\x1b[0m', true));
|
|
101
|
+
console.log(center('\x1b[32m│ │\x1b[0m', true));
|
|
102
|
+
console.log(center('\x1b[32m╰────────────────────────────────────────────────────────────────────────────────╯\x1b[0m', true));
|
|
103
|
+
console.log('');
|
|
104
|
+
|
|
105
|
+
// Show active sandboxes count
|
|
106
|
+
const runningSandboxes = sandboxes.filter(s => s.status === 'running' || s.status === 'initializing');
|
|
107
|
+
if (runningSandboxes.length > 0) {
|
|
108
|
+
console.log(center(`\x1b[32m📦 ${runningSandboxes.length} active sandbox${runningSandboxes.length > 1 ? 'es' : ''}\x1b[0m`, true));
|
|
109
|
+
console.log('');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
menuItems.forEach((item, index) => {
|
|
113
|
+
if (index === selectedIndex) {
|
|
114
|
+
console.log(center(`\x1b[1m\x1b[36m❯ ${item}\x1b[0m`, true));
|
|
115
|
+
} else {
|
|
116
|
+
console.log(center(` ${item}`, false));
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
console.log('');
|
|
121
|
+
console.log(center('\x1b[90mUse ↑↓ arrows to navigate, Enter to select, Ctrl+C to exit\x1b[0m', true));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function renderSandboxList() {
|
|
125
|
+
console.clear();
|
|
126
|
+
console.log(center('╔════════════════════════════════════════════════════════════╗', false));
|
|
127
|
+
console.log(center('║ 📋 Running Sandboxes ║', false));
|
|
128
|
+
console.log(center('╚════════════════════════════════════════════════════════════╝', false));
|
|
129
|
+
console.log('');
|
|
130
|
+
|
|
131
|
+
if (sandboxes.length === 0) {
|
|
132
|
+
console.log(center('\x1b[90mNo sandboxes running.\x1b[0m', true));
|
|
133
|
+
console.log('');
|
|
134
|
+
console.log(center('\x1b[90mPress Esc to go back to menu\x1b[0m', true));
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
sandboxes.forEach((sandbox, index) => {
|
|
139
|
+
const isSelected = index === selectedIndex;
|
|
140
|
+
const statusColor = sandbox.status === 'running' ? '\x1b[32m' :
|
|
141
|
+
sandbox.status === 'initializing' ? '\x1b[33m' :
|
|
142
|
+
'\x1b[31m';
|
|
143
|
+
const statusIcon = sandbox.status === 'running' ? '✅' :
|
|
144
|
+
sandbox.status === 'initializing' ? '🔄' :
|
|
145
|
+
'❌';
|
|
146
|
+
|
|
147
|
+
if (isSelected) {
|
|
148
|
+
console.log(center(`\x1b[1m\x1b[36m❯ #${sandbox.id} ${statusIcon} ${sandbox.repo}\x1b[0m`, true));
|
|
149
|
+
console.log(center(`\x1b[36m${statusColor}${sandbox.status}\x1b[0m \x1b[36m| ${sandbox.provider} | ${sandbox.gpu || 'N/A'}\x1b[0m`, true));
|
|
150
|
+
} else {
|
|
151
|
+
console.log(center(`#${sandbox.id} ${statusIcon} ${sandbox.repo}`, false));
|
|
152
|
+
console.log(center(`${statusColor}${sandbox.status}\x1b[0m | ${sandbox.provider} | ${sandbox.gpu || 'N/A'}`, true));
|
|
153
|
+
}
|
|
154
|
+
console.log('');
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
console.log(center('\x1b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m', true));
|
|
158
|
+
console.log('');
|
|
159
|
+
console.log(center('\x1b[1mControls:\x1b[0m', true));
|
|
160
|
+
console.log(center('\x1b[36mEnter\x1b[0m - View logs / Connect to sandbox', true));
|
|
161
|
+
console.log(center('\x1b[36mD\x1b[0m - Delete sandbox', true));
|
|
162
|
+
console.log(center('\x1b[36mR\x1b[0m - Refresh list', true));
|
|
163
|
+
console.log(center('\x1b[36mEsc\x1b[0m - Back to menu', true));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function renderRepoInput() {
|
|
167
|
+
console.clear();
|
|
168
|
+
console.log(center('╔════════════════════════════════════════════════════════════╗', false));
|
|
169
|
+
console.log(center('║ 🚀 Create New Sandbox ║', false));
|
|
170
|
+
console.log(center('╚════════════════════════════════════════════════════════════╝', false));
|
|
171
|
+
console.log('');
|
|
172
|
+
|
|
173
|
+
console.log(center('\x1b[1mEnter GitHub repository URL:\x1b[0m', true));
|
|
174
|
+
console.log('');
|
|
175
|
+
console.log(center(`\x1b[36m${inputText}\x1b[7m \x1b[0m`, true));
|
|
176
|
+
|
|
177
|
+
console.log('');
|
|
178
|
+
console.log(center('\x1b[90mExamples:\x1b[0m', true));
|
|
179
|
+
console.log(center('\x1b[90m • https://github.com/pytorch/examples\x1b[0m', true));
|
|
180
|
+
console.log(center('\x1b[90m • https://github.com/huggingface/transformers\x1b[0m', true));
|
|
181
|
+
console.log(center('\x1b[90m • https://github.com/openai/whisper\x1b[0m', true));
|
|
182
|
+
console.log('');
|
|
183
|
+
console.log(center('\x1b[90mPress Enter to continue • Esc to go back • Ctrl+C to exit\x1b[0m', true));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function renderProviderSelection() {
|
|
187
|
+
console.clear();
|
|
188
|
+
console.log(center('╔════════════════════════════════════════════════════════════╗', false));
|
|
189
|
+
console.log(center('║ 🔧 Select Sandbox Provider ║', false));
|
|
190
|
+
console.log(center('╚════════════════════════════════════════════════════════════╝', false));
|
|
191
|
+
console.log('');
|
|
192
|
+
|
|
193
|
+
console.log(center(`\x1b[1mRepository:\x1b[0m \x1b[32m${config.repoUrl}\x1b[0m`, true));
|
|
194
|
+
console.log('');
|
|
195
|
+
|
|
196
|
+
providerOptions.forEach((option, index) => {
|
|
197
|
+
if (index === selectedIndex) {
|
|
198
|
+
console.log(center(`\x1b[1m\x1b[36m❯ ${option.name}\x1b[0m`, true));
|
|
199
|
+
} else {
|
|
200
|
+
console.log(center(` ${option.name}`, false));
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
console.log('');
|
|
205
|
+
console.log(center('\x1b[90mPress Enter to select • Esc to go back\x1b[0m', true));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function renderGpuSelection() {
|
|
209
|
+
console.clear();
|
|
210
|
+
console.log(center('╔════════════════════════════════════════════════════════════╗', false));
|
|
211
|
+
console.log(center('║ ⚡ Select GPU Configuration ║', false));
|
|
212
|
+
console.log(center('╚════════════════════════════════════════════════════════════╝', false));
|
|
213
|
+
console.log('');
|
|
214
|
+
|
|
215
|
+
console.log(center(`\x1b[1mRepository:\x1b[0m \x1b[32m${config.repoUrl}\x1b[0m`, true));
|
|
216
|
+
console.log(center(`\x1b[1mProvider:\x1b[0m \x1b[32m${config.sandboxProvider}\x1b[0m`, true));
|
|
217
|
+
console.log('');
|
|
218
|
+
|
|
219
|
+
gpuOptions.forEach((option, index) => {
|
|
220
|
+
if (index === selectedIndex) {
|
|
221
|
+
console.log(center(`\x1b[1m\x1b[36m❯ ${option.name}\x1b[0m`, true));
|
|
222
|
+
} else {
|
|
223
|
+
console.log(center(` ${option.name}`, false));
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
console.log('');
|
|
228
|
+
console.log(center('\x1b[90mPress Enter to select • Esc to go back\x1b[0m', true));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function renderGpuCountSelection() {
|
|
232
|
+
console.clear();
|
|
233
|
+
console.log(center('╔════════════════════════════════════════════════════════════╗', false));
|
|
234
|
+
console.log(center('║ ⚡ Select GPU Count ║', false));
|
|
235
|
+
console.log(center('╚════════════════════════════════════════════════════════════╝', false));
|
|
236
|
+
console.log('');
|
|
237
|
+
|
|
238
|
+
console.log(center(`\x1b[1mGPU Type:\x1b[0m \x1b[32m${config.gpuType}\x1b[0m`, true));
|
|
239
|
+
console.log('');
|
|
240
|
+
|
|
241
|
+
gpuCountOptions.forEach((option, index) => {
|
|
242
|
+
if (index === selectedIndex) {
|
|
243
|
+
console.log(center(`\x1b[1m\x1b[36m❯ ${option.name}\x1b[0m`, true));
|
|
244
|
+
} else {
|
|
245
|
+
console.log(center(` ${option.name}`, false));
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
console.log('');
|
|
250
|
+
console.log(center('\x1b[90mPress Enter to select • Esc to go back\x1b[0m', true));
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function renderConfirmation() {
|
|
254
|
+
console.clear();
|
|
255
|
+
console.log(center('╔════════════════════════════════════════════════════════════╗', false));
|
|
256
|
+
console.log(center('║ 📋 Configuration Summary ║', false));
|
|
257
|
+
console.log(center('╚════════════════════════════════════════════════════════════╝', false));
|
|
258
|
+
console.log('');
|
|
259
|
+
|
|
260
|
+
console.log(center('\x1b[1mRepository URL:\x1b[0m', true));
|
|
261
|
+
console.log(center(`\x1b[36m${config.repoUrl}\x1b[0m`, true));
|
|
262
|
+
console.log('');
|
|
263
|
+
|
|
264
|
+
console.log(center('\x1b[1mSandbox Provider:\x1b[0m', true));
|
|
265
|
+
console.log(center(`\x1b[36m${config.sandboxProvider}\x1b[0m`, true));
|
|
266
|
+
console.log('');
|
|
267
|
+
|
|
268
|
+
if (config.sandboxProvider === 'modal') {
|
|
269
|
+
console.log(center('\x1b[1mGPU Configuration:\x1b[0m', true));
|
|
270
|
+
console.log(center(`\x1b[36m${config.gpuCount > 1 ? config.gpuCount + 'x ' : ''}${config.gpuType}\x1b[0m`, true));
|
|
271
|
+
console.log('');
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
console.log(center('\x1b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m', true));
|
|
275
|
+
console.log('');
|
|
276
|
+
|
|
277
|
+
console.log(center('\x1b[1m\x1b[32m✅ Press Enter to create sandbox in background\x1b[0m', true));
|
|
278
|
+
console.log(center('\x1b[90mPress Esc to go back and change settings\x1b[0m', true));
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function createSandbox() {
|
|
282
|
+
const sandboxId = sandboxIdCounter++;
|
|
283
|
+
const sandbox = {
|
|
284
|
+
id: sandboxId,
|
|
285
|
+
repo: config.repoUrl.split('/').pop() || config.repoUrl,
|
|
286
|
+
fullRepo: config.repoUrl,
|
|
287
|
+
provider: config.sandboxProvider,
|
|
288
|
+
gpu: config.sandboxProvider === 'modal' ? `${config.gpuCount}x ${config.gpuType}` : null,
|
|
289
|
+
status: 'initializing',
|
|
290
|
+
process: null,
|
|
291
|
+
startTime: new Date()
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
sandboxes.push(sandbox);
|
|
295
|
+
|
|
296
|
+
console.clear();
|
|
297
|
+
console.log('\x1b[1m\x1b[32m🚀 Starting sandbox in background...\x1b[0m\n');
|
|
298
|
+
console.log(`\x1b[36mSandbox ID:\x1b[0m #${sandboxId}`);
|
|
299
|
+
console.log(`\x1b[36mRepository:\x1b[0m ${config.repoUrl}`);
|
|
300
|
+
console.log(`\x1b[36mProvider:\x1b[0m ${config.sandboxProvider}`);
|
|
301
|
+
if (config.sandboxProvider === 'modal') {
|
|
302
|
+
console.log(`\x1b[36mGPU:\x1b[0m ${config.gpuCount}x ${config.gpuType}`);
|
|
303
|
+
}
|
|
304
|
+
console.log();
|
|
305
|
+
|
|
306
|
+
const args = [
|
|
307
|
+
'--yes',
|
|
308
|
+
'--repo', config.repoUrl,
|
|
309
|
+
'--sandbox-provider', config.sandboxProvider
|
|
310
|
+
];
|
|
311
|
+
|
|
312
|
+
if (config.sandboxProvider === 'modal') {
|
|
313
|
+
args.push('--gpu', config.gpuType);
|
|
314
|
+
args.push('--gpu-count', config.gpuCount.toString());
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const cliPath = join(__dirname, '..', 'bin', 'gitarsenal.js');
|
|
318
|
+
|
|
319
|
+
// Spawn in background
|
|
320
|
+
const child = spawn('node', [cliPath, ...args], {
|
|
321
|
+
cwd: join(__dirname, '..'),
|
|
322
|
+
detached: true,
|
|
323
|
+
stdio: 'pipe'
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
sandbox.process = child;
|
|
327
|
+
|
|
328
|
+
// Update status based on output
|
|
329
|
+
let output = '';
|
|
330
|
+
child.stdout.on('data', (data) => {
|
|
331
|
+
output += data.toString();
|
|
332
|
+
if (output.includes('Sandbox created') || output.includes('successfully')) {
|
|
333
|
+
sandbox.status = 'running';
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
child.stderr.on('data', (data) => {
|
|
338
|
+
output += data.toString();
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
child.on('close', (code) => {
|
|
342
|
+
if (code === 0) {
|
|
343
|
+
sandbox.status = 'running';
|
|
344
|
+
} else {
|
|
345
|
+
sandbox.status = 'failed';
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
console.log('\x1b[32m✅ Sandbox started in background!\x1b[0m');
|
|
350
|
+
console.log('\x1b[90mCheck status in "View Running Sandboxes"\x1b[0m\n');
|
|
351
|
+
console.log('\x1b[90mPress any key to return to menu...\x1b[0m');
|
|
352
|
+
|
|
353
|
+
currentScreen = 'result';
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function viewSandboxLogs(sandbox) {
|
|
357
|
+
console.clear();
|
|
358
|
+
console.log(`\x1b[1m\x1b[36m📋 Sandbox #${sandbox.id} - ${sandbox.repo}\x1b[0m\n`);
|
|
359
|
+
console.log(`\x1b[90mStatus: ${sandbox.status} | Provider: ${sandbox.provider}\x1b[0m`);
|
|
360
|
+
console.log('\x1b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m\n');
|
|
361
|
+
console.log('\x1b[33m💡 Press Esc to exit logs and return to sandbox list\x1b[0m');
|
|
362
|
+
console.log('\x1b[33m💡 Press Ctrl+C to exit TUI entirely\x1b[0m\n');
|
|
363
|
+
console.log('\x1b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m\n');
|
|
364
|
+
|
|
365
|
+
if (sandbox.process && !sandbox.process.killed) {
|
|
366
|
+
console.log('\x1b[1mLive Output:\x1b[0m\n');
|
|
367
|
+
|
|
368
|
+
// Switch to raw mode to capture Esc
|
|
369
|
+
process.stdin.setRawMode(true);
|
|
370
|
+
|
|
371
|
+
// Pipe the process output
|
|
372
|
+
sandbox.process.stdout.pipe(process.stdout);
|
|
373
|
+
sandbox.process.stderr.pipe(process.stderr);
|
|
374
|
+
|
|
375
|
+
activeProcess = sandbox.process;
|
|
376
|
+
} else {
|
|
377
|
+
console.log('\x1b[90mNo live output available.\x1b[0m\n');
|
|
378
|
+
console.log('\x1b[90mPress Esc to go back\x1b[0m');
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function deleteSandbox(index) {
|
|
383
|
+
const sandbox = sandboxes[index];
|
|
384
|
+
if (sandbox.process && !sandbox.process.killed) {
|
|
385
|
+
sandbox.process.kill('SIGTERM');
|
|
386
|
+
}
|
|
387
|
+
sandboxes.splice(index, 1);
|
|
388
|
+
|
|
389
|
+
console.clear();
|
|
390
|
+
console.log('\x1b[32m✅ Sandbox deleted\x1b[0m\n');
|
|
391
|
+
console.log('\x1b[90mPress any key to continue...\x1b[0m');
|
|
392
|
+
currentScreen = 'result-after-delete';
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function handleMenuSelection() {
|
|
396
|
+
if (selectedIndex === 0) {
|
|
397
|
+
currentScreen = 'repo-input';
|
|
398
|
+
inputText = '';
|
|
399
|
+
selectedIndex = 0;
|
|
400
|
+
renderRepoInput();
|
|
401
|
+
} else if (selectedIndex === 1) {
|
|
402
|
+
currentScreen = 'sandbox-list';
|
|
403
|
+
selectedIndex = 0;
|
|
404
|
+
renderSandboxList();
|
|
405
|
+
} else if (selectedIndex === 2) {
|
|
406
|
+
console.clear();
|
|
407
|
+
console.log('\n\x1b[1m\x1b[33m🔑 API Keys Management\x1b[0m\n');
|
|
408
|
+
console.log('Launching gitarsenal keys command...\n');
|
|
409
|
+
console.log('\x1b[33m💡 Press Ctrl+C to cancel\x1b[0m\n');
|
|
410
|
+
|
|
411
|
+
process.stdin.setRawMode(false);
|
|
412
|
+
const child = spawn('node', [join(__dirname, '..', 'bin', 'gitarsenal.js'), 'keys', 'list'], {
|
|
413
|
+
stdio: 'inherit',
|
|
414
|
+
cwd: join(__dirname, '..')
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
child.on('close', () => {
|
|
418
|
+
console.log('\n\x1b[90mPress any key to return to menu...\x1b[0m');
|
|
419
|
+
process.stdin.setRawMode(true);
|
|
420
|
+
currentScreen = 'result';
|
|
421
|
+
});
|
|
422
|
+
} else if (selectedIndex === menuItems.length - 1) {
|
|
423
|
+
console.clear();
|
|
424
|
+
console.log('\n👋 Goodbye!\n');
|
|
425
|
+
if (sandboxes.length > 0) {
|
|
426
|
+
const count = sandboxes.length;
|
|
427
|
+
const plural = count > 1 ? "es" : "";
|
|
428
|
+
console.log("\x1b[33m⚠️ " + count + " sandbox" + plural + " still running in background\x1b[0m\n"); }
|
|
429
|
+
process.exit(0);
|
|
430
|
+
} else {
|
|
431
|
+
console.clear();
|
|
432
|
+
console.log('\n\x1b[33m⚠️ ' + menuItems[selectedIndex] + '\x1b[0m');
|
|
433
|
+
console.log('\x1b[90mThis feature is coming soon!\x1b[0m');
|
|
434
|
+
console.log('\n\x1b[90mPress any key to return to menu...\x1b[0m');
|
|
435
|
+
currentScreen = 'result';
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Initial render
|
|
440
|
+
renderMenu();
|
|
441
|
+
|
|
442
|
+
// Handle keyboard input
|
|
443
|
+
process.stdin.setRawMode(true);
|
|
444
|
+
process.stdin.resume();
|
|
445
|
+
process.stdin.setEncoding('utf8');
|
|
446
|
+
|
|
447
|
+
process.stdin.on('data', (key) => {
|
|
448
|
+
if (key === '\u0003') {
|
|
449
|
+
// Ctrl+C - Kill all sandboxes and exit
|
|
450
|
+
console.clear();
|
|
451
|
+
console.log('\n\x1b[33m⚠️ Stopping all sandboxes...\x1b[0m\n');
|
|
452
|
+
sandboxes.forEach(sandbox => {
|
|
453
|
+
if (sandbox.process && !sandbox.process.killed) {
|
|
454
|
+
sandbox.process.kill('SIGTERM');
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
setTimeout(() => {
|
|
458
|
+
console.clear();
|
|
459
|
+
console.log('\n👋 Goodbye!\n');
|
|
460
|
+
process.exit(0);
|
|
461
|
+
}, 500);
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
if (currentScreen === 'menu') {
|
|
466
|
+
if (key === '\u001b[A') {
|
|
467
|
+
selectedIndex = Math.max(0, selectedIndex - 1);
|
|
468
|
+
renderMenu();
|
|
469
|
+
} else if (key === '\u001b[B') {
|
|
470
|
+
selectedIndex = Math.min(menuItems.length - 1, selectedIndex + 1);
|
|
471
|
+
renderMenu();
|
|
472
|
+
} else if (key === '\r' || key === '\n') {
|
|
473
|
+
handleMenuSelection();
|
|
474
|
+
}
|
|
475
|
+
} else if (currentScreen === 'sandbox-list') {
|
|
476
|
+
if (key === '\u001b') {
|
|
477
|
+
// Esc - back to menu
|
|
478
|
+
currentScreen = 'menu';
|
|
479
|
+
selectedIndex = 0;
|
|
480
|
+
renderMenu();
|
|
481
|
+
} else if (key === '\u001b[A') {
|
|
482
|
+
selectedIndex = Math.max(0, selectedIndex - 1);
|
|
483
|
+
renderSandboxList();
|
|
484
|
+
} else if (key === '\u001b[B') {
|
|
485
|
+
selectedIndex = Math.min(sandboxes.length - 1, selectedIndex + 1);
|
|
486
|
+
renderSandboxList();
|
|
487
|
+
} else if (key === '\r' || key === '\n') {
|
|
488
|
+
if (sandboxes.length > 0) {
|
|
489
|
+
currentScreen = 'viewing-logs';
|
|
490
|
+
viewSandboxLogs(sandboxes[selectedIndex]);
|
|
491
|
+
}
|
|
492
|
+
} else if (key.toLowerCase() === 'd') {
|
|
493
|
+
if (sandboxes.length > 0) {
|
|
494
|
+
deleteSandbox(selectedIndex);
|
|
495
|
+
}
|
|
496
|
+
} else if (key.toLowerCase() === 'r') {
|
|
497
|
+
renderSandboxList();
|
|
498
|
+
}
|
|
499
|
+
} else if (currentScreen === 'viewing-logs') {
|
|
500
|
+
if (key === '\u001b') {
|
|
501
|
+
// Esc - exit logs and return to sandbox list
|
|
502
|
+
if (activeProcess) {
|
|
503
|
+
activeProcess.stdout.unpipe(process.stdout);
|
|
504
|
+
activeProcess.stderr.unpipe(process.stderr);
|
|
505
|
+
activeProcess = null;
|
|
506
|
+
}
|
|
507
|
+
currentScreen = 'sandbox-list';
|
|
508
|
+
renderSandboxList();
|
|
509
|
+
}
|
|
510
|
+
} else if (currentScreen === 'repo-input') {
|
|
511
|
+
if (key === '\u001b') {
|
|
512
|
+
currentScreen = 'menu';
|
|
513
|
+
selectedIndex = 0;
|
|
514
|
+
inputText = '';
|
|
515
|
+
renderMenu();
|
|
516
|
+
} else if (key === '\r' || key === '\n') {
|
|
517
|
+
if (inputText.trim()) {
|
|
518
|
+
config.repoUrl = inputText.trim();
|
|
519
|
+
currentScreen = 'provider-select';
|
|
520
|
+
selectedIndex = 0;
|
|
521
|
+
renderProviderSelection();
|
|
522
|
+
}
|
|
523
|
+
} else if (key === '\u007f' || key === '\b') {
|
|
524
|
+
inputText = inputText.slice(0, -1);
|
|
525
|
+
renderRepoInput();
|
|
526
|
+
} else if (key.charCodeAt(0) >= 32 && key.charCodeAt(0) <= 126) {
|
|
527
|
+
inputText += key;
|
|
528
|
+
renderRepoInput();
|
|
529
|
+
}
|
|
530
|
+
} else if (currentScreen === 'provider-select') {
|
|
531
|
+
if (key === '\u001b') {
|
|
532
|
+
currentScreen = 'repo-input';
|
|
533
|
+
inputText = config.repoUrl;
|
|
534
|
+
renderRepoInput();
|
|
535
|
+
} else if (key === '\u001b[A') {
|
|
536
|
+
selectedIndex = Math.max(0, selectedIndex - 1);
|
|
537
|
+
renderProviderSelection();
|
|
538
|
+
} else if (key === '\u001b[B') {
|
|
539
|
+
selectedIndex = Math.min(providerOptions.length - 1, selectedIndex + 1);
|
|
540
|
+
renderProviderSelection();
|
|
541
|
+
} else if (key === '\r' || key === '\n') {
|
|
542
|
+
config.sandboxProvider = providerOptions[selectedIndex].value;
|
|
543
|
+
if (config.sandboxProvider === 'modal') {
|
|
544
|
+
currentScreen = 'gpu-select';
|
|
545
|
+
selectedIndex = 2;
|
|
546
|
+
renderGpuSelection();
|
|
547
|
+
} else {
|
|
548
|
+
currentScreen = 'confirm';
|
|
549
|
+
renderConfirmation();
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
} else if (currentScreen === 'gpu-select') {
|
|
553
|
+
if (key === '\u001b') {
|
|
554
|
+
currentScreen = 'provider-select';
|
|
555
|
+
selectedIndex = 0;
|
|
556
|
+
renderProviderSelection();
|
|
557
|
+
} else if (key === '\u001b[A') {
|
|
558
|
+
selectedIndex = Math.max(0, selectedIndex - 1);
|
|
559
|
+
renderGpuSelection();
|
|
560
|
+
} else if (key === '\u001b[B') {
|
|
561
|
+
selectedIndex = Math.min(gpuOptions.length - 1, selectedIndex + 1);
|
|
562
|
+
renderGpuSelection();
|
|
563
|
+
} else if (key === '\r' || key === '\n') {
|
|
564
|
+
config.gpuType = gpuOptions[selectedIndex].value;
|
|
565
|
+
currentScreen = 'gpu-count';
|
|
566
|
+
selectedIndex = 0;
|
|
567
|
+
renderGpuCountSelection();
|
|
568
|
+
}
|
|
569
|
+
} else if (currentScreen === 'gpu-count') {
|
|
570
|
+
if (key === '\u001b') {
|
|
571
|
+
currentScreen = 'gpu-select';
|
|
572
|
+
selectedIndex = 2;
|
|
573
|
+
renderGpuSelection();
|
|
574
|
+
} else if (key === '\u001b[A') {
|
|
575
|
+
selectedIndex = Math.max(0, selectedIndex - 1);
|
|
576
|
+
renderGpuCountSelection();
|
|
577
|
+
} else if (key === '\u001b[B') {
|
|
578
|
+
selectedIndex = Math.min(gpuCountOptions.length - 1, selectedIndex + 1);
|
|
579
|
+
renderGpuCountSelection();
|
|
580
|
+
} else if (key === '\r' || key === '\n') {
|
|
581
|
+
config.gpuCount = gpuCountOptions[selectedIndex].value;
|
|
582
|
+
currentScreen = 'confirm';
|
|
583
|
+
renderConfirmation();
|
|
584
|
+
}
|
|
585
|
+
} else if (currentScreen === 'confirm') {
|
|
586
|
+
if (key === '\u001b') {
|
|
587
|
+
if (config.sandboxProvider === 'modal') {
|
|
588
|
+
currentScreen = 'gpu-count';
|
|
589
|
+
selectedIndex = 0;
|
|
590
|
+
renderGpuCountSelection();
|
|
591
|
+
} else {
|
|
592
|
+
currentScreen = 'provider-select';
|
|
593
|
+
selectedIndex = 0;
|
|
594
|
+
renderProviderSelection();
|
|
595
|
+
}
|
|
596
|
+
} else if (key === '\r' || key === '\n') {
|
|
597
|
+
createSandbox();
|
|
598
|
+
}
|
|
599
|
+
} else if (currentScreen === 'result' || currentScreen === 'result-after-delete') {
|
|
600
|
+
if (currentScreen === 'result-after-delete') {
|
|
601
|
+
currentScreen = 'sandbox-list';
|
|
602
|
+
renderSandboxList();
|
|
603
|
+
} else {
|
|
604
|
+
currentScreen = 'menu';
|
|
605
|
+
selectedIndex = 0;
|
|
606
|
+
renderMenu();
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
});
|