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.
- package/bin/ultra-dex.js +14 -2
- package/lib/commands/cloud.js +780 -0
- package/lib/commands/exec.js +434 -0
- package/lib/commands/github.js +475 -0
- package/lib/commands/search.js +477 -0
- package/lib/mcp/client.js +502 -0
- package/lib/providers/agent-sdk.js +630 -0
- package/lib/providers/anthropic-agents.js +580 -0
- package/lib/utils/browser.js +373 -0
- package/package.json +10 -4
|
@@ -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
|
+
};
|