ultra-dex 3.2.0 → 3.3.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.
@@ -0,0 +1,434 @@
1
+ /**
2
+ * ultra-dex exec command
3
+ * Docker-based code execution sandbox for running generated code safely
4
+ * This is what makes Ultra-Dex truly autonomous - it can VERIFY code works
5
+ */
6
+
7
+ import chalk from 'chalk';
8
+ import ora from 'ora';
9
+ import fs from 'fs/promises';
10
+ import path from 'path';
11
+ import { spawn, exec as execCallback } from 'child_process';
12
+ import { promisify } from 'util';
13
+
14
+ const execAsync = promisify(execCallback);
15
+
16
+ // ============================================================================
17
+ // SANDBOX CONFIGURATION
18
+ // ============================================================================
19
+
20
+ const SANDBOX_CONFIG = {
21
+ // Docker image for sandboxed execution
22
+ defaultImage: 'node:20-alpine',
23
+
24
+ // Language-specific images
25
+ images: {
26
+ javascript: 'node:20-alpine',
27
+ typescript: 'node:20-alpine',
28
+ python: 'python:3.12-alpine',
29
+ rust: 'rust:1.75-alpine',
30
+ go: 'golang:1.22-alpine',
31
+ ruby: 'ruby:3.3-alpine',
32
+ },
33
+
34
+ // Resource limits
35
+ limits: {
36
+ memory: '512m',
37
+ cpus: '1.0',
38
+ timeout: 60000, // 60 seconds
39
+ networkDisabled: true,
40
+ },
41
+
42
+ // Workspace settings
43
+ workspace: {
44
+ containerPath: '/workspace',
45
+ tempDir: '.ultra-dex/sandbox',
46
+ }
47
+ };
48
+
49
+ // ============================================================================
50
+ // DOCKER UTILITIES
51
+ // ============================================================================
52
+
53
+ /**
54
+ * Check if Docker is available
55
+ */
56
+ async function checkDocker() {
57
+ try {
58
+ await execAsync('docker --version');
59
+ return true;
60
+ } catch {
61
+ return false;
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Pull Docker image if not available
67
+ */
68
+ async function ensureImage(image, spinner) {
69
+ try {
70
+ await execAsync(`docker image inspect ${image} > /dev/null 2>&1`);
71
+ return true;
72
+ } catch {
73
+ spinner.text = `Pulling Docker image: ${image}...`;
74
+ try {
75
+ await execAsync(`docker pull ${image}`);
76
+ return true;
77
+ } catch (err) {
78
+ return false;
79
+ }
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Detect language from file extension
85
+ */
86
+ function detectLanguage(filepath) {
87
+ const ext = path.extname(filepath).toLowerCase();
88
+ const langMap = {
89
+ '.js': 'javascript',
90
+ '.mjs': 'javascript',
91
+ '.ts': 'typescript',
92
+ '.tsx': 'typescript',
93
+ '.py': 'python',
94
+ '.rs': 'rust',
95
+ '.go': 'go',
96
+ '.rb': 'ruby',
97
+ };
98
+ return langMap[ext] || 'javascript';
99
+ }
100
+
101
+ /**
102
+ * Get execution command for language
103
+ */
104
+ function getExecCommand(language, filename) {
105
+ const commands = {
106
+ javascript: `node ${filename}`,
107
+ typescript: `npx tsx ${filename}`,
108
+ python: `python ${filename}`,
109
+ rust: `rustc ${filename} -o /tmp/out && /tmp/out`,
110
+ go: `go run ${filename}`,
111
+ ruby: `ruby ${filename}`,
112
+ };
113
+ return commands[language] || `node ${filename}`;
114
+ }
115
+
116
+ // ============================================================================
117
+ // SANDBOX EXECUTOR
118
+ // ============================================================================
119
+
120
+ /**
121
+ * Execute code in Docker sandbox
122
+ */
123
+ export async function executeInSandbox(code, options = {}) {
124
+ const {
125
+ language = 'javascript',
126
+ filename = 'main.js',
127
+ timeout = SANDBOX_CONFIG.limits.timeout,
128
+ allowNetwork = false,
129
+ env = {},
130
+ workdir = process.cwd(),
131
+ } = options;
132
+
133
+ const image = SANDBOX_CONFIG.images[language] || SANDBOX_CONFIG.defaultImage;
134
+ const tempDir = path.join(workdir, SANDBOX_CONFIG.workspace.tempDir);
135
+ const tempFile = path.join(tempDir, filename);
136
+
137
+ // Ensure temp directory exists
138
+ await fs.mkdir(tempDir, { recursive: true });
139
+
140
+ // Write code to temp file
141
+ await fs.writeFile(tempFile, code, 'utf8');
142
+
143
+ // Build Docker command
144
+ const dockerArgs = [
145
+ 'run',
146
+ '--rm',
147
+ '-i',
148
+ `--memory=${SANDBOX_CONFIG.limits.memory}`,
149
+ `--cpus=${SANDBOX_CONFIG.limits.cpus}`,
150
+ allowNetwork ? '' : '--network=none',
151
+ `-v`, `${tempDir}:${SANDBOX_CONFIG.workspace.containerPath}:ro`,
152
+ `-w`, SANDBOX_CONFIG.workspace.containerPath,
153
+ ];
154
+
155
+ // Add environment variables
156
+ for (const [key, value] of Object.entries(env)) {
157
+ dockerArgs.push('-e', `${key}=${value}`);
158
+ }
159
+
160
+ dockerArgs.push(image);
161
+
162
+ // Add execution command
163
+ const execCmd = getExecCommand(language, filename);
164
+ dockerArgs.push('sh', '-c', execCmd);
165
+
166
+ // Filter empty strings
167
+ const filteredArgs = dockerArgs.filter(Boolean);
168
+
169
+ return new Promise((resolve, reject) => {
170
+ const result = {
171
+ stdout: '',
172
+ stderr: '',
173
+ exitCode: null,
174
+ timedOut: false,
175
+ duration: 0,
176
+ };
177
+
178
+ const startTime = Date.now();
179
+ const proc = spawn('docker', filteredArgs);
180
+
181
+ const timeoutId = setTimeout(() => {
182
+ result.timedOut = true;
183
+ proc.kill('SIGKILL');
184
+ }, timeout);
185
+
186
+ proc.stdout.on('data', (data) => {
187
+ result.stdout += data.toString();
188
+ });
189
+
190
+ proc.stderr.on('data', (data) => {
191
+ result.stderr += data.toString();
192
+ });
193
+
194
+ proc.on('close', (code) => {
195
+ clearTimeout(timeoutId);
196
+ result.exitCode = code;
197
+ result.duration = Date.now() - startTime;
198
+ resolve(result);
199
+ });
200
+
201
+ proc.on('error', (err) => {
202
+ clearTimeout(timeoutId);
203
+ reject(err);
204
+ });
205
+ });
206
+ }
207
+
208
+ /**
209
+ * Execute a file in sandbox
210
+ */
211
+ export async function executeFile(filepath, options = {}) {
212
+ const code = await fs.readFile(filepath, 'utf8');
213
+ const language = detectLanguage(filepath);
214
+ const filename = path.basename(filepath);
215
+
216
+ return executeInSandbox(code, {
217
+ ...options,
218
+ language,
219
+ filename,
220
+ });
221
+ }
222
+
223
+ /**
224
+ * Execute npm/shell command in sandbox
225
+ */
226
+ export async function executeCommand(command, options = {}) {
227
+ const {
228
+ timeout = SANDBOX_CONFIG.limits.timeout,
229
+ workdir = process.cwd(),
230
+ allowNetwork = true, // Commands often need network
231
+ } = options;
232
+
233
+ const tempDir = path.join(workdir, SANDBOX_CONFIG.workspace.tempDir);
234
+ await fs.mkdir(tempDir, { recursive: true });
235
+
236
+ const dockerArgs = [
237
+ 'run',
238
+ '--rm',
239
+ '-i',
240
+ `--memory=${SANDBOX_CONFIG.limits.memory}`,
241
+ `--cpus=${SANDBOX_CONFIG.limits.cpus}`,
242
+ allowNetwork ? '' : '--network=none',
243
+ `-v`, `${workdir}:${SANDBOX_CONFIG.workspace.containerPath}`,
244
+ `-w`, SANDBOX_CONFIG.workspace.containerPath,
245
+ SANDBOX_CONFIG.defaultImage,
246
+ 'sh', '-c', command,
247
+ ].filter(Boolean);
248
+
249
+ return new Promise((resolve, reject) => {
250
+ const result = {
251
+ stdout: '',
252
+ stderr: '',
253
+ exitCode: null,
254
+ timedOut: false,
255
+ duration: 0,
256
+ };
257
+
258
+ const startTime = Date.now();
259
+ const proc = spawn('docker', dockerArgs);
260
+
261
+ const timeoutId = setTimeout(() => {
262
+ result.timedOut = true;
263
+ proc.kill('SIGKILL');
264
+ }, timeout);
265
+
266
+ proc.stdout.on('data', (data) => {
267
+ result.stdout += data.toString();
268
+ });
269
+
270
+ proc.stderr.on('data', (data) => {
271
+ result.stderr += data.toString();
272
+ });
273
+
274
+ proc.on('close', (code) => {
275
+ clearTimeout(timeoutId);
276
+ result.exitCode = code;
277
+ result.duration = Date.now() - startTime;
278
+ resolve(result);
279
+ });
280
+
281
+ proc.on('error', (err) => {
282
+ clearTimeout(timeoutId);
283
+ reject(err);
284
+ });
285
+ });
286
+ }
287
+
288
+ // ============================================================================
289
+ // TEST RUNNER
290
+ // ============================================================================
291
+
292
+ /**
293
+ * Run tests in sandbox
294
+ */
295
+ export async function runTests(testCommand = 'npm test', options = {}) {
296
+ const spinner = ora('Running tests in sandbox...').start();
297
+
298
+ try {
299
+ const result = await executeCommand(testCommand, {
300
+ ...options,
301
+ allowNetwork: true, // Tests may need to install deps
302
+ });
303
+
304
+ if (result.exitCode === 0) {
305
+ spinner.succeed(chalk.green('Tests passed!'));
306
+ } else {
307
+ spinner.fail(chalk.red('Tests failed'));
308
+ }
309
+
310
+ return result;
311
+ } catch (err) {
312
+ spinner.fail(chalk.red(`Test execution failed: ${err.message}`));
313
+ throw err;
314
+ }
315
+ }
316
+
317
+ // ============================================================================
318
+ // CLI COMMAND
319
+ // ============================================================================
320
+
321
+ export function registerExecCommand(program) {
322
+ program
323
+ .command('exec [file]')
324
+ .description('Execute code in isolated Docker sandbox')
325
+ .option('-c, --code <code>', 'Execute inline code')
326
+ .option('-l, --language <lang>', 'Language (js, ts, py, go, rs, rb)')
327
+ .option('-t, --timeout <ms>', 'Timeout in milliseconds', '60000')
328
+ .option('--allow-network', 'Allow network access in sandbox')
329
+ .option('--command <cmd>', 'Run shell command instead of file')
330
+ .option('--test', 'Run npm test in sandbox')
331
+ .action(async (file, options) => {
332
+ console.log(chalk.cyan('\n🐳 Ultra-Dex Code Sandbox\n'));
333
+
334
+ // Check Docker availability
335
+ const spinner = ora('Checking Docker...').start();
336
+ const hasDocker = await checkDocker();
337
+
338
+ if (!hasDocker) {
339
+ spinner.fail(chalk.red('Docker not found. Please install Docker to use the sandbox.'));
340
+ console.log(chalk.yellow('\nInstall Docker: https://docs.docker.com/get-docker/'));
341
+ return;
342
+ }
343
+ spinner.succeed('Docker available');
344
+
345
+ const timeout = parseInt(options.timeout, 10);
346
+
347
+ try {
348
+ let result;
349
+
350
+ if (options.test) {
351
+ // Run tests
352
+ result = await runTests('npm test', { timeout, allowNetwork: true });
353
+ } else if (options.command) {
354
+ // Run shell command
355
+ spinner.start(`Executing: ${options.command}`);
356
+ result = await executeCommand(options.command, {
357
+ timeout,
358
+ allowNetwork: options.allowNetwork,
359
+ });
360
+ } else if (options.code) {
361
+ // Execute inline code
362
+ const language = options.language || 'javascript';
363
+ const image = SANDBOX_CONFIG.images[language];
364
+
365
+ spinner.start('Preparing sandbox...');
366
+ await ensureImage(image, spinner);
367
+
368
+ spinner.text = 'Executing code...';
369
+ result = await executeInSandbox(options.code, {
370
+ language,
371
+ timeout,
372
+ allowNetwork: options.allowNetwork,
373
+ });
374
+ } else if (file) {
375
+ // Execute file
376
+ const language = options.language || detectLanguage(file);
377
+ const image = SANDBOX_CONFIG.images[language];
378
+
379
+ spinner.start('Preparing sandbox...');
380
+ await ensureImage(image, spinner);
381
+
382
+ spinner.text = `Executing ${file}...`;
383
+ result = await executeFile(file, {
384
+ timeout,
385
+ allowNetwork: options.allowNetwork,
386
+ });
387
+ } else {
388
+ spinner.fail('No code, file, or command specified');
389
+ console.log(chalk.yellow('\nUsage:'));
390
+ console.log(' ultra-dex exec script.js');
391
+ console.log(' ultra-dex exec -c "console.log(1+1)"');
392
+ console.log(' ultra-dex exec --command "npm test"');
393
+ console.log(' ultra-dex exec --test');
394
+ return;
395
+ }
396
+
397
+ // Show results
398
+ if (result.timedOut) {
399
+ spinner.fail(chalk.red(`Execution timed out after ${timeout}ms`));
400
+ } else if (result.exitCode === 0) {
401
+ spinner.succeed(chalk.green(`Completed in ${result.duration}ms`));
402
+ } else {
403
+ spinner.warn(chalk.yellow(`Exited with code ${result.exitCode}`));
404
+ }
405
+
406
+ // Print output
407
+ if (result.stdout) {
408
+ console.log(chalk.bold('\n📤 Output:'));
409
+ console.log(result.stdout);
410
+ }
411
+
412
+ if (result.stderr) {
413
+ console.log(chalk.bold('\n⚠️ Stderr:'));
414
+ console.log(chalk.red(result.stderr));
415
+ }
416
+
417
+ // Summary
418
+ console.log(chalk.gray(`\n⏱️ Duration: ${result.duration}ms`));
419
+ console.log(chalk.gray(`🔒 Network: ${options.allowNetwork ? 'Enabled' : 'Disabled'}`));
420
+
421
+ } catch (err) {
422
+ spinner.fail(chalk.red(`Execution failed: ${err.message}`));
423
+ }
424
+ });
425
+ }
426
+
427
+ export default {
428
+ registerExecCommand,
429
+ executeInSandbox,
430
+ executeFile,
431
+ executeCommand,
432
+ runTests,
433
+ checkDocker,
434
+ };