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,373 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser Automation Module (Playwright-based)
|
|
3
|
+
* Enables Research agent to browse the web, take screenshots, and interact with pages
|
|
4
|
+
* This is what makes Ultra-Dex truly intelligent - it can LEARN from the web
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import fs from 'fs/promises';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// BROWSER CONFIGURATION
|
|
13
|
+
// ============================================================================
|
|
14
|
+
|
|
15
|
+
const BROWSER_CONFIG = {
|
|
16
|
+
// Default browser settings
|
|
17
|
+
headless: true,
|
|
18
|
+
timeout: 30000,
|
|
19
|
+
viewport: { width: 1280, height: 720 },
|
|
20
|
+
|
|
21
|
+
// Screenshot settings
|
|
22
|
+
screenshotDir: '.ultra-dex/screenshots',
|
|
23
|
+
|
|
24
|
+
// User agent
|
|
25
|
+
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Ultra-Dex/3.2.0 Research Agent',
|
|
26
|
+
|
|
27
|
+
// Blocked resources (for faster loading)
|
|
28
|
+
blockedResources: ['image', 'stylesheet', 'font', 'media'],
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// ============================================================================
|
|
32
|
+
// BROWSER AUTOMATION CLASS
|
|
33
|
+
// ============================================================================
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Browser automation for Research agent
|
|
37
|
+
* Uses Playwright for reliable cross-browser automation
|
|
38
|
+
*/
|
|
39
|
+
export class BrowserAutomation {
|
|
40
|
+
constructor(options = {}) {
|
|
41
|
+
this.options = { ...BROWSER_CONFIG, ...options };
|
|
42
|
+
this.browser = null;
|
|
43
|
+
this.context = null;
|
|
44
|
+
this.page = null;
|
|
45
|
+
this.playwright = null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Launch browser
|
|
50
|
+
*/
|
|
51
|
+
async launch() {
|
|
52
|
+
try {
|
|
53
|
+
// Dynamic import of playwright
|
|
54
|
+
const { chromium } = await import('playwright');
|
|
55
|
+
this.playwright = { chromium };
|
|
56
|
+
|
|
57
|
+
this.browser = await chromium.launch({
|
|
58
|
+
headless: this.options.headless,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
this.context = await this.browser.newContext({
|
|
62
|
+
userAgent: this.options.userAgent,
|
|
63
|
+
viewport: this.options.viewport,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Block unnecessary resources for faster loading
|
|
67
|
+
if (this.options.blockedResources.length > 0) {
|
|
68
|
+
await this.context.route('**/*', (route) => {
|
|
69
|
+
if (this.options.blockedResources.includes(route.request().resourceType())) {
|
|
70
|
+
route.abort();
|
|
71
|
+
} else {
|
|
72
|
+
route.continue();
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
this.page = await this.context.newPage();
|
|
78
|
+
this.page.setDefaultTimeout(this.options.timeout);
|
|
79
|
+
|
|
80
|
+
return true;
|
|
81
|
+
} catch (err) {
|
|
82
|
+
console.log(chalk.yellow(`Browser launch failed: ${err.message}`));
|
|
83
|
+
console.log(chalk.gray('Install Playwright: npm install playwright'));
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Close browser
|
|
90
|
+
*/
|
|
91
|
+
async close() {
|
|
92
|
+
if (this.browser) {
|
|
93
|
+
await this.browser.close();
|
|
94
|
+
this.browser = null;
|
|
95
|
+
this.context = null;
|
|
96
|
+
this.page = null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Navigate to URL
|
|
102
|
+
*/
|
|
103
|
+
async navigate(url) {
|
|
104
|
+
if (!this.page) {
|
|
105
|
+
await this.launch();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
await this.page.goto(url, { waitUntil: 'domcontentloaded' });
|
|
109
|
+
return { url: this.page.url(), title: await this.page.title() };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Get page content (text extraction)
|
|
114
|
+
*/
|
|
115
|
+
async getPageContent(url) {
|
|
116
|
+
await this.navigate(url);
|
|
117
|
+
|
|
118
|
+
// Extract text content
|
|
119
|
+
const content = await this.page.evaluate(() => {
|
|
120
|
+
// Remove scripts and styles
|
|
121
|
+
const scripts = document.querySelectorAll('script, style, noscript');
|
|
122
|
+
scripts.forEach(el => el.remove());
|
|
123
|
+
|
|
124
|
+
// Get main content areas
|
|
125
|
+
const mainContent = document.querySelector('main, article, .content, #content, .main');
|
|
126
|
+
const target = mainContent || document.body;
|
|
127
|
+
|
|
128
|
+
// Extract text
|
|
129
|
+
const text = target.innerText || target.textContent || '';
|
|
130
|
+
|
|
131
|
+
// Clean up whitespace
|
|
132
|
+
return text
|
|
133
|
+
.replace(/\s+/g, ' ')
|
|
134
|
+
.replace(/\n\s*\n/g, '\n')
|
|
135
|
+
.trim()
|
|
136
|
+
.substring(0, 50000); // Limit content size
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Extract links
|
|
140
|
+
const links = await this.page.evaluate(() => {
|
|
141
|
+
return Array.from(document.querySelectorAll('a[href]'))
|
|
142
|
+
.slice(0, 50)
|
|
143
|
+
.map(a => ({
|
|
144
|
+
text: a.innerText?.trim().substring(0, 100),
|
|
145
|
+
href: a.href,
|
|
146
|
+
}))
|
|
147
|
+
.filter(l => l.text && l.href.startsWith('http'));
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Extract headings
|
|
151
|
+
const headings = await this.page.evaluate(() => {
|
|
152
|
+
return Array.from(document.querySelectorAll('h1, h2, h3'))
|
|
153
|
+
.slice(0, 20)
|
|
154
|
+
.map(h => ({
|
|
155
|
+
level: h.tagName.toLowerCase(),
|
|
156
|
+
text: h.innerText?.trim().substring(0, 200),
|
|
157
|
+
}))
|
|
158
|
+
.filter(h => h.text);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
url: this.page.url(),
|
|
163
|
+
title: await this.page.title(),
|
|
164
|
+
content: content.substring(0, 20000), // Limit for context window
|
|
165
|
+
headings,
|
|
166
|
+
links: links.slice(0, 20),
|
|
167
|
+
wordCount: content.split(/\s+/).length,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Take screenshot
|
|
173
|
+
*/
|
|
174
|
+
async screenshot(url, options = {}) {
|
|
175
|
+
await this.navigate(url);
|
|
176
|
+
|
|
177
|
+
const { fullPage = false, path: customPath } = options;
|
|
178
|
+
|
|
179
|
+
// Ensure screenshot directory exists
|
|
180
|
+
await fs.mkdir(this.options.screenshotDir, { recursive: true });
|
|
181
|
+
|
|
182
|
+
// Generate filename
|
|
183
|
+
const timestamp = Date.now();
|
|
184
|
+
const filename = customPath || path.join(
|
|
185
|
+
this.options.screenshotDir,
|
|
186
|
+
`screenshot-${timestamp}.png`
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
await this.page.screenshot({
|
|
190
|
+
path: filename,
|
|
191
|
+
fullPage,
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
path: filename,
|
|
196
|
+
url: this.page.url(),
|
|
197
|
+
title: await this.page.title(),
|
|
198
|
+
timestamp: new Date().toISOString(),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Click an element
|
|
204
|
+
*/
|
|
205
|
+
async click(selector) {
|
|
206
|
+
await this.page.click(selector);
|
|
207
|
+
await this.page.waitForLoadState('domcontentloaded');
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
clicked: selector,
|
|
211
|
+
url: this.page.url(),
|
|
212
|
+
title: await this.page.title(),
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Fill a form field
|
|
218
|
+
*/
|
|
219
|
+
async fill(selector, value) {
|
|
220
|
+
await this.page.fill(selector, value);
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
filled: selector,
|
|
224
|
+
value: value.substring(0, 50), // Don't expose full value
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Wait for selector
|
|
230
|
+
*/
|
|
231
|
+
async waitFor(selector, options = {}) {
|
|
232
|
+
const { timeout = 10000, state = 'visible' } = options;
|
|
233
|
+
|
|
234
|
+
try {
|
|
235
|
+
await this.page.waitForSelector(selector, { timeout, state });
|
|
236
|
+
return { success: true, selector };
|
|
237
|
+
} catch {
|
|
238
|
+
return { success: false, selector, error: 'Timeout waiting for element' };
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Execute JavaScript on page
|
|
244
|
+
*/
|
|
245
|
+
async evaluate(script) {
|
|
246
|
+
const result = await this.page.evaluate(script);
|
|
247
|
+
return { result };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Get page metadata
|
|
252
|
+
*/
|
|
253
|
+
async getMetadata(url) {
|
|
254
|
+
await this.navigate(url);
|
|
255
|
+
|
|
256
|
+
const metadata = await this.page.evaluate(() => {
|
|
257
|
+
const getMeta = (name) => {
|
|
258
|
+
const el = document.querySelector(`meta[name="${name}"], meta[property="${name}"]`);
|
|
259
|
+
return el?.getAttribute('content') || null;
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
title: document.title,
|
|
264
|
+
description: getMeta('description') || getMeta('og:description'),
|
|
265
|
+
keywords: getMeta('keywords'),
|
|
266
|
+
author: getMeta('author'),
|
|
267
|
+
ogImage: getMeta('og:image'),
|
|
268
|
+
ogTitle: getMeta('og:title'),
|
|
269
|
+
canonical: document.querySelector('link[rel="canonical"]')?.href,
|
|
270
|
+
};
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
return { url: this.page.url(), ...metadata };
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Search Google and get results
|
|
278
|
+
*/
|
|
279
|
+
async searchGoogle(query) {
|
|
280
|
+
const searchUrl = `https://www.google.com/search?q=${encodeURIComponent(query)}`;
|
|
281
|
+
await this.navigate(searchUrl);
|
|
282
|
+
|
|
283
|
+
// Wait for results
|
|
284
|
+
await this.page.waitForSelector('#search', { timeout: 10000 }).catch(() => {});
|
|
285
|
+
|
|
286
|
+
const results = await this.page.evaluate(() => {
|
|
287
|
+
const items = document.querySelectorAll('#search .g');
|
|
288
|
+
return Array.from(items).slice(0, 10).map(item => {
|
|
289
|
+
const linkEl = item.querySelector('a');
|
|
290
|
+
const titleEl = item.querySelector('h3');
|
|
291
|
+
const snippetEl = item.querySelector('.VwiC3b');
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
title: titleEl?.innerText || '',
|
|
295
|
+
url: linkEl?.href || '',
|
|
296
|
+
snippet: snippetEl?.innerText || '',
|
|
297
|
+
};
|
|
298
|
+
}).filter(r => r.url && r.title);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
return { query, results };
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Fetch documentation from common sources
|
|
306
|
+
*/
|
|
307
|
+
async fetchDocs(library, topic = '') {
|
|
308
|
+
const docSources = {
|
|
309
|
+
react: 'https://react.dev/reference',
|
|
310
|
+
nextjs: 'https://nextjs.org/docs',
|
|
311
|
+
prisma: 'https://www.prisma.io/docs',
|
|
312
|
+
typescript: 'https://www.typescriptlang.org/docs',
|
|
313
|
+
tailwind: 'https://tailwindcss.com/docs',
|
|
314
|
+
node: 'https://nodejs.org/docs/latest/api',
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
const baseUrl = docSources[library.toLowerCase()];
|
|
318
|
+
if (!baseUrl) {
|
|
319
|
+
// Fall back to Google search
|
|
320
|
+
return this.searchGoogle(`${library} ${topic} documentation`);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const url = topic ? `${baseUrl}/${topic}` : baseUrl;
|
|
324
|
+
return this.getPageContent(url);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// ============================================================================
|
|
329
|
+
// LIGHTWEIGHT FETCH (No Browser)
|
|
330
|
+
// ============================================================================
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Simple HTTP fetch for when browser is overkill
|
|
334
|
+
*/
|
|
335
|
+
export async function simpleFetch(url) {
|
|
336
|
+
const response = await fetch(url, {
|
|
337
|
+
headers: {
|
|
338
|
+
'User-Agent': BROWSER_CONFIG.userAgent,
|
|
339
|
+
},
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
const contentType = response.headers.get('content-type') || '';
|
|
343
|
+
|
|
344
|
+
if (contentType.includes('application/json')) {
|
|
345
|
+
return { type: 'json', data: await response.json() };
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const text = await response.text();
|
|
349
|
+
|
|
350
|
+
// Simple HTML to text conversion
|
|
351
|
+
if (contentType.includes('text/html')) {
|
|
352
|
+
const cleaned = text
|
|
353
|
+
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
|
|
354
|
+
.replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, '')
|
|
355
|
+
.replace(/<[^>]+>/g, ' ')
|
|
356
|
+
.replace(/\s+/g, ' ')
|
|
357
|
+
.trim();
|
|
358
|
+
|
|
359
|
+
return { type: 'html', data: cleaned.substring(0, 50000) };
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return { type: 'text', data: text.substring(0, 50000) };
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// ============================================================================
|
|
366
|
+
// EXPORTS
|
|
367
|
+
// ============================================================================
|
|
368
|
+
|
|
369
|
+
export default {
|
|
370
|
+
BrowserAutomation,
|
|
371
|
+
simpleFetch,
|
|
372
|
+
BROWSER_CONFIG,
|
|
373
|
+
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ultra-dex",
|
|
3
|
-
"version": "3.
|
|
4
|
-
"description": "AI Orchestration Meta-Layer for SaaS Development",
|
|
3
|
+
"version": "3.3.0",
|
|
4
|
+
"description": "AI Orchestration Meta-Layer for SaaS Development - The Headless CTO",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
7
7
|
"orchestration",
|
|
@@ -12,7 +12,12 @@
|
|
|
12
12
|
"openai",
|
|
13
13
|
"gemini",
|
|
14
14
|
"agents",
|
|
15
|
-
"swarm"
|
|
15
|
+
"swarm",
|
|
16
|
+
"docker",
|
|
17
|
+
"sandbox",
|
|
18
|
+
"github",
|
|
19
|
+
"browser-automation",
|
|
20
|
+
"agent-sdk"
|
|
16
21
|
],
|
|
17
22
|
"author": "Srujan Sai Karna",
|
|
18
23
|
"license": "MIT",
|
|
@@ -60,7 +65,8 @@
|
|
|
60
65
|
"@anthropic-ai/sdk": "^0.30.0",
|
|
61
66
|
"@google/generative-ai": "^0.21.0",
|
|
62
67
|
"dotenv": "^16.4.5",
|
|
63
|
-
"openai": "^4.70.0"
|
|
68
|
+
"openai": "^4.70.0",
|
|
69
|
+
"playwright": "^1.41.0"
|
|
64
70
|
},
|
|
65
71
|
"devDependencies": {
|
|
66
72
|
"eslint": "^8.57.0"
|