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,780 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ultra-dex cloud command
|
|
3
|
+
* Hosted cloud dashboard for team collaboration and SaaS mode
|
|
4
|
+
* This enables Ultra-Dex to run as a service, not just a local CLI
|
|
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 http from 'http';
|
|
12
|
+
import { WebSocketServer } from 'ws';
|
|
13
|
+
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// CLOUD CONFIGURATION
|
|
16
|
+
// ============================================================================
|
|
17
|
+
|
|
18
|
+
const CLOUD_CONFIG = {
|
|
19
|
+
// Default ports
|
|
20
|
+
ports: {
|
|
21
|
+
api: 4001,
|
|
22
|
+
websocket: 4002,
|
|
23
|
+
dashboard: 4003,
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
// State storage
|
|
27
|
+
stateDir: '.ultra-dex/cloud',
|
|
28
|
+
sessionsFile: '.ultra-dex/cloud/sessions.json',
|
|
29
|
+
teamsFile: '.ultra-dex/cloud/teams.json',
|
|
30
|
+
|
|
31
|
+
// Rate limits
|
|
32
|
+
rateLimit: {
|
|
33
|
+
requestsPerMinute: 60,
|
|
34
|
+
swarmRunsPerHour: 10,
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
// Session settings
|
|
38
|
+
sessionTimeout: 24 * 60 * 60 * 1000, // 24 hours
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// ============================================================================
|
|
42
|
+
// SESSION MANAGER
|
|
43
|
+
// ============================================================================
|
|
44
|
+
|
|
45
|
+
class SessionManager {
|
|
46
|
+
constructor() {
|
|
47
|
+
this.sessions = new Map();
|
|
48
|
+
this.teams = new Map();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async load() {
|
|
52
|
+
try {
|
|
53
|
+
const sessionsData = await fs.readFile(CLOUD_CONFIG.sessionsFile, 'utf8');
|
|
54
|
+
const sessions = JSON.parse(sessionsData);
|
|
55
|
+
for (const [id, session] of Object.entries(sessions)) {
|
|
56
|
+
this.sessions.set(id, session);
|
|
57
|
+
}
|
|
58
|
+
} catch {
|
|
59
|
+
// No existing sessions
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const teamsData = await fs.readFile(CLOUD_CONFIG.teamsFile, 'utf8');
|
|
64
|
+
const teams = JSON.parse(teamsData);
|
|
65
|
+
for (const [id, team] of Object.entries(teams)) {
|
|
66
|
+
this.teams.set(id, team);
|
|
67
|
+
}
|
|
68
|
+
} catch {
|
|
69
|
+
// No existing teams
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async save() {
|
|
74
|
+
await fs.mkdir(CLOUD_CONFIG.stateDir, { recursive: true });
|
|
75
|
+
|
|
76
|
+
const sessions = Object.fromEntries(this.sessions);
|
|
77
|
+
await fs.writeFile(CLOUD_CONFIG.sessionsFile, JSON.stringify(sessions, null, 2));
|
|
78
|
+
|
|
79
|
+
const teams = Object.fromEntries(this.teams);
|
|
80
|
+
await fs.writeFile(CLOUD_CONFIG.teamsFile, JSON.stringify(teams, null, 2));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
createSession(userId, teamId = null) {
|
|
84
|
+
const sessionId = `sess_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
85
|
+
const session = {
|
|
86
|
+
id: sessionId,
|
|
87
|
+
userId,
|
|
88
|
+
teamId,
|
|
89
|
+
createdAt: new Date().toISOString(),
|
|
90
|
+
expiresAt: new Date(Date.now() + CLOUD_CONFIG.sessionTimeout).toISOString(),
|
|
91
|
+
state: {},
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
this.sessions.set(sessionId, session);
|
|
95
|
+
return session;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
getSession(sessionId) {
|
|
99
|
+
const session = this.sessions.get(sessionId);
|
|
100
|
+
if (!session) return null;
|
|
101
|
+
|
|
102
|
+
// Check expiration
|
|
103
|
+
if (new Date(session.expiresAt) < new Date()) {
|
|
104
|
+
this.sessions.delete(sessionId);
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return session;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
createTeam(name, ownerId) {
|
|
112
|
+
const teamId = `team_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
113
|
+
const team = {
|
|
114
|
+
id: teamId,
|
|
115
|
+
name,
|
|
116
|
+
ownerId,
|
|
117
|
+
members: [ownerId],
|
|
118
|
+
createdAt: new Date().toISOString(),
|
|
119
|
+
projects: [],
|
|
120
|
+
settings: {},
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
this.teams.set(teamId, team);
|
|
124
|
+
return team;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
addTeamMember(teamId, userId) {
|
|
128
|
+
const team = this.teams.get(teamId);
|
|
129
|
+
if (!team) return false;
|
|
130
|
+
|
|
131
|
+
if (!team.members.includes(userId)) {
|
|
132
|
+
team.members.push(userId);
|
|
133
|
+
}
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const sessionManager = new SessionManager();
|
|
139
|
+
|
|
140
|
+
// ============================================================================
|
|
141
|
+
// API SERVER
|
|
142
|
+
// ============================================================================
|
|
143
|
+
|
|
144
|
+
function createAPIServer(options = {}) {
|
|
145
|
+
const { port = CLOUD_CONFIG.ports.api } = options;
|
|
146
|
+
|
|
147
|
+
const server = http.createServer(async (req, res) => {
|
|
148
|
+
// CORS headers
|
|
149
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
150
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
151
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
152
|
+
|
|
153
|
+
if (req.method === 'OPTIONS') {
|
|
154
|
+
res.writeHead(204);
|
|
155
|
+
res.end();
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const url = new URL(req.url, `http://localhost:${port}`);
|
|
160
|
+
const path = url.pathname;
|
|
161
|
+
|
|
162
|
+
// Parse body for POST/PUT
|
|
163
|
+
let body = '';
|
|
164
|
+
if (req.method === 'POST' || req.method === 'PUT') {
|
|
165
|
+
for await (const chunk of req) {
|
|
166
|
+
body += chunk;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
// Route handling
|
|
172
|
+
if (path === '/api/health') {
|
|
173
|
+
return sendJSON(res, { status: 'ok', version: '3.3.0' });
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (path === '/api/session' && req.method === 'POST') {
|
|
177
|
+
const { userId, teamId } = JSON.parse(body);
|
|
178
|
+
const session = sessionManager.createSession(userId, teamId);
|
|
179
|
+
await sessionManager.save();
|
|
180
|
+
return sendJSON(res, session);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (path === '/api/team' && req.method === 'POST') {
|
|
184
|
+
const { name, ownerId } = JSON.parse(body);
|
|
185
|
+
const team = sessionManager.createTeam(name, ownerId);
|
|
186
|
+
await sessionManager.save();
|
|
187
|
+
return sendJSON(res, team);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (path === '/api/state' && req.method === 'GET') {
|
|
191
|
+
const sessionId = req.headers.authorization?.replace('Bearer ', '');
|
|
192
|
+
const session = sessionManager.getSession(sessionId);
|
|
193
|
+
|
|
194
|
+
if (!session) {
|
|
195
|
+
return sendJSON(res, { error: 'Invalid session' }, 401);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return sendJSON(res, { session, state: session.state });
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (path === '/api/state' && req.method === 'PUT') {
|
|
202
|
+
const sessionId = req.headers.authorization?.replace('Bearer ', '');
|
|
203
|
+
const session = sessionManager.getSession(sessionId);
|
|
204
|
+
|
|
205
|
+
if (!session) {
|
|
206
|
+
return sendJSON(res, { error: 'Invalid session' }, 401);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
session.state = JSON.parse(body);
|
|
210
|
+
await sessionManager.save();
|
|
211
|
+
return sendJSON(res, { success: true });
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (path === '/api/swarm' && req.method === 'POST') {
|
|
215
|
+
const sessionId = req.headers.authorization?.replace('Bearer ', '');
|
|
216
|
+
const session = sessionManager.getSession(sessionId);
|
|
217
|
+
|
|
218
|
+
if (!session) {
|
|
219
|
+
return sendJSON(res, { error: 'Invalid session' }, 401);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const { task, options } = JSON.parse(body);
|
|
223
|
+
|
|
224
|
+
// Queue swarm run (in real implementation, would use job queue)
|
|
225
|
+
const runId = `run_${Date.now()}`;
|
|
226
|
+
|
|
227
|
+
return sendJSON(res, {
|
|
228
|
+
runId,
|
|
229
|
+
status: 'queued',
|
|
230
|
+
task,
|
|
231
|
+
message: 'Swarm run queued for execution',
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// 404
|
|
236
|
+
sendJSON(res, { error: 'Not found' }, 404);
|
|
237
|
+
|
|
238
|
+
} catch (err) {
|
|
239
|
+
sendJSON(res, { error: err.message }, 500);
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
return server;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function sendJSON(res, data, statusCode = 200) {
|
|
247
|
+
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
|
|
248
|
+
res.end(JSON.stringify(data));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ============================================================================
|
|
252
|
+
// WEBSOCKET SERVER (Real-time sync)
|
|
253
|
+
// ============================================================================
|
|
254
|
+
|
|
255
|
+
function createWebSocketServer(options = {}) {
|
|
256
|
+
const { port = CLOUD_CONFIG.ports.websocket } = options;
|
|
257
|
+
|
|
258
|
+
const wss = new WebSocketServer({ port });
|
|
259
|
+
|
|
260
|
+
const clients = new Map(); // sessionId -> WebSocket
|
|
261
|
+
|
|
262
|
+
wss.on('connection', (ws) => {
|
|
263
|
+
let sessionId = null;
|
|
264
|
+
|
|
265
|
+
ws.on('message', async (data) => {
|
|
266
|
+
try {
|
|
267
|
+
const message = JSON.parse(data.toString());
|
|
268
|
+
|
|
269
|
+
if (message.type === 'auth') {
|
|
270
|
+
sessionId = message.sessionId;
|
|
271
|
+
const session = sessionManager.getSession(sessionId);
|
|
272
|
+
|
|
273
|
+
if (session) {
|
|
274
|
+
clients.set(sessionId, ws);
|
|
275
|
+
ws.send(JSON.stringify({ type: 'auth_success', session }));
|
|
276
|
+
} else {
|
|
277
|
+
ws.send(JSON.stringify({ type: 'auth_failed' }));
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (message.type === 'state_update' && sessionId) {
|
|
282
|
+
const session = sessionManager.getSession(sessionId);
|
|
283
|
+
if (session) {
|
|
284
|
+
session.state = { ...session.state, ...message.data };
|
|
285
|
+
await sessionManager.save();
|
|
286
|
+
|
|
287
|
+
// Broadcast to team members
|
|
288
|
+
if (session.teamId) {
|
|
289
|
+
const team = sessionManager.teams.get(session.teamId);
|
|
290
|
+
if (team) {
|
|
291
|
+
for (const [sid, client] of clients) {
|
|
292
|
+
const clientSession = sessionManager.getSession(sid);
|
|
293
|
+
if (clientSession?.teamId === session.teamId && sid !== sessionId) {
|
|
294
|
+
client.send(JSON.stringify({
|
|
295
|
+
type: 'state_sync',
|
|
296
|
+
from: sessionId,
|
|
297
|
+
data: message.data,
|
|
298
|
+
}));
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (message.type === 'swarm_event' && sessionId) {
|
|
307
|
+
// Broadcast swarm events to team
|
|
308
|
+
const session = sessionManager.getSession(sessionId);
|
|
309
|
+
if (session?.teamId) {
|
|
310
|
+
for (const [sid, client] of clients) {
|
|
311
|
+
const clientSession = sessionManager.getSession(sid);
|
|
312
|
+
if (clientSession?.teamId === session.teamId) {
|
|
313
|
+
client.send(JSON.stringify({
|
|
314
|
+
type: 'swarm_event',
|
|
315
|
+
from: sessionId,
|
|
316
|
+
event: message.event,
|
|
317
|
+
}));
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
} catch (err) {
|
|
324
|
+
ws.send(JSON.stringify({ type: 'error', message: err.message }));
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
ws.on('close', () => {
|
|
329
|
+
if (sessionId) {
|
|
330
|
+
clients.delete(sessionId);
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
return wss;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// ============================================================================
|
|
339
|
+
// CLOUD DASHBOARD HTML
|
|
340
|
+
// ============================================================================
|
|
341
|
+
|
|
342
|
+
const CLOUD_DASHBOARD_HTML = `<!DOCTYPE html>
|
|
343
|
+
<html lang="en">
|
|
344
|
+
<head>
|
|
345
|
+
<meta charset="UTF-8">
|
|
346
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
347
|
+
<title>Ultra-Dex Cloud Dashboard</title>
|
|
348
|
+
<style>
|
|
349
|
+
:root {
|
|
350
|
+
--bg: #0f0f23;
|
|
351
|
+
--surface: #1a1a2e;
|
|
352
|
+
--primary: #6366f1;
|
|
353
|
+
--secondary: #8b5cf6;
|
|
354
|
+
--text: #e2e8f0;
|
|
355
|
+
--muted: #64748b;
|
|
356
|
+
--success: #22c55e;
|
|
357
|
+
--warning: #f59e0b;
|
|
358
|
+
--error: #ef4444;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
362
|
+
|
|
363
|
+
body {
|
|
364
|
+
font-family: 'SF Mono', 'Fira Code', monospace;
|
|
365
|
+
background: var(--bg);
|
|
366
|
+
color: var(--text);
|
|
367
|
+
min-height: 100vh;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
.container {
|
|
371
|
+
max-width: 1400px;
|
|
372
|
+
margin: 0 auto;
|
|
373
|
+
padding: 2rem;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
header {
|
|
377
|
+
display: flex;
|
|
378
|
+
justify-content: space-between;
|
|
379
|
+
align-items: center;
|
|
380
|
+
margin-bottom: 2rem;
|
|
381
|
+
padding-bottom: 1rem;
|
|
382
|
+
border-bottom: 1px solid var(--surface);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
h1 {
|
|
386
|
+
background: linear-gradient(135deg, var(--primary), var(--secondary));
|
|
387
|
+
-webkit-background-clip: text;
|
|
388
|
+
-webkit-text-fill-color: transparent;
|
|
389
|
+
font-size: 2rem;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
.status-badge {
|
|
393
|
+
display: inline-flex;
|
|
394
|
+
align-items: center;
|
|
395
|
+
gap: 0.5rem;
|
|
396
|
+
padding: 0.5rem 1rem;
|
|
397
|
+
background: var(--surface);
|
|
398
|
+
border-radius: 9999px;
|
|
399
|
+
font-size: 0.875rem;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
.status-dot {
|
|
403
|
+
width: 8px;
|
|
404
|
+
height: 8px;
|
|
405
|
+
border-radius: 50%;
|
|
406
|
+
background: var(--success);
|
|
407
|
+
animation: pulse 2s infinite;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
@keyframes pulse {
|
|
411
|
+
0%, 100% { opacity: 1; }
|
|
412
|
+
50% { opacity: 0.5; }
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
.grid {
|
|
416
|
+
display: grid;
|
|
417
|
+
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
418
|
+
gap: 1.5rem;
|
|
419
|
+
margin-bottom: 2rem;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
.card {
|
|
423
|
+
background: var(--surface);
|
|
424
|
+
border-radius: 12px;
|
|
425
|
+
padding: 1.5rem;
|
|
426
|
+
border: 1px solid rgba(255,255,255,0.05);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
.card h2 {
|
|
430
|
+
font-size: 1rem;
|
|
431
|
+
color: var(--muted);
|
|
432
|
+
margin-bottom: 1rem;
|
|
433
|
+
text-transform: uppercase;
|
|
434
|
+
letter-spacing: 0.05em;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
.metric {
|
|
438
|
+
font-size: 2.5rem;
|
|
439
|
+
font-weight: bold;
|
|
440
|
+
background: linear-gradient(135deg, var(--primary), var(--secondary));
|
|
441
|
+
-webkit-background-clip: text;
|
|
442
|
+
-webkit-text-fill-color: transparent;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
.agent-list {
|
|
446
|
+
display: flex;
|
|
447
|
+
flex-direction: column;
|
|
448
|
+
gap: 0.75rem;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
.agent-item {
|
|
452
|
+
display: flex;
|
|
453
|
+
justify-content: space-between;
|
|
454
|
+
align-items: center;
|
|
455
|
+
padding: 0.75rem;
|
|
456
|
+
background: rgba(255,255,255,0.02);
|
|
457
|
+
border-radius: 8px;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
.agent-name {
|
|
461
|
+
font-weight: 600;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
.agent-status {
|
|
465
|
+
font-size: 0.75rem;
|
|
466
|
+
padding: 0.25rem 0.5rem;
|
|
467
|
+
border-radius: 4px;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
.agent-status.idle { background: var(--muted); }
|
|
471
|
+
.agent-status.running { background: var(--primary); }
|
|
472
|
+
.agent-status.complete { background: var(--success); }
|
|
473
|
+
|
|
474
|
+
.timeline {
|
|
475
|
+
display: flex;
|
|
476
|
+
flex-direction: column;
|
|
477
|
+
gap: 1rem;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
.timeline-item {
|
|
481
|
+
display: flex;
|
|
482
|
+
gap: 1rem;
|
|
483
|
+
padding-left: 1rem;
|
|
484
|
+
border-left: 2px solid var(--primary);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
.timeline-time {
|
|
488
|
+
color: var(--muted);
|
|
489
|
+
font-size: 0.75rem;
|
|
490
|
+
min-width: 60px;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
.timeline-content {
|
|
494
|
+
flex: 1;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
.btn {
|
|
498
|
+
display: inline-flex;
|
|
499
|
+
align-items: center;
|
|
500
|
+
gap: 0.5rem;
|
|
501
|
+
padding: 0.75rem 1.5rem;
|
|
502
|
+
background: linear-gradient(135deg, var(--primary), var(--secondary));
|
|
503
|
+
color: white;
|
|
504
|
+
border: none;
|
|
505
|
+
border-radius: 8px;
|
|
506
|
+
font-family: inherit;
|
|
507
|
+
font-size: 0.875rem;
|
|
508
|
+
cursor: pointer;
|
|
509
|
+
transition: transform 0.2s, box-shadow 0.2s;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
.btn:hover {
|
|
513
|
+
transform: translateY(-2px);
|
|
514
|
+
box-shadow: 0 4px 20px rgba(99, 102, 241, 0.4);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
.team-section {
|
|
518
|
+
margin-top: 2rem;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
.team-members {
|
|
522
|
+
display: flex;
|
|
523
|
+
gap: 0.5rem;
|
|
524
|
+
flex-wrap: wrap;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
.member-avatar {
|
|
528
|
+
width: 40px;
|
|
529
|
+
height: 40px;
|
|
530
|
+
border-radius: 50%;
|
|
531
|
+
background: linear-gradient(135deg, var(--primary), var(--secondary));
|
|
532
|
+
display: flex;
|
|
533
|
+
align-items: center;
|
|
534
|
+
justify-content: center;
|
|
535
|
+
font-weight: bold;
|
|
536
|
+
font-size: 0.875rem;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
footer {
|
|
540
|
+
text-align: center;
|
|
541
|
+
padding-top: 2rem;
|
|
542
|
+
color: var(--muted);
|
|
543
|
+
font-size: 0.875rem;
|
|
544
|
+
}
|
|
545
|
+
</style>
|
|
546
|
+
</head>
|
|
547
|
+
<body>
|
|
548
|
+
<div class="container">
|
|
549
|
+
<header>
|
|
550
|
+
<h1>Ultra-Dex Cloud</h1>
|
|
551
|
+
<div class="status-badge">
|
|
552
|
+
<span class="status-dot"></span>
|
|
553
|
+
<span>Connected</span>
|
|
554
|
+
</div>
|
|
555
|
+
</header>
|
|
556
|
+
|
|
557
|
+
<div class="grid">
|
|
558
|
+
<div class="card">
|
|
559
|
+
<h2>Active Swarms</h2>
|
|
560
|
+
<div class="metric" id="activeSwarms">0</div>
|
|
561
|
+
</div>
|
|
562
|
+
<div class="card">
|
|
563
|
+
<h2>Tasks Completed</h2>
|
|
564
|
+
<div class="metric" id="tasksCompleted">0</div>
|
|
565
|
+
</div>
|
|
566
|
+
<div class="card">
|
|
567
|
+
<h2>Team Members</h2>
|
|
568
|
+
<div class="metric" id="teamMembers">1</div>
|
|
569
|
+
</div>
|
|
570
|
+
<div class="card">
|
|
571
|
+
<h2>API Calls Today</h2>
|
|
572
|
+
<div class="metric" id="apiCalls">0</div>
|
|
573
|
+
</div>
|
|
574
|
+
</div>
|
|
575
|
+
|
|
576
|
+
<div class="grid">
|
|
577
|
+
<div class="card">
|
|
578
|
+
<h2>Agent Status</h2>
|
|
579
|
+
<div class="agent-list" id="agentList">
|
|
580
|
+
<div class="agent-item">
|
|
581
|
+
<span class="agent-name">@Planner</span>
|
|
582
|
+
<span class="agent-status idle">Idle</span>
|
|
583
|
+
</div>
|
|
584
|
+
<div class="agent-item">
|
|
585
|
+
<span class="agent-name">@Backend</span>
|
|
586
|
+
<span class="agent-status idle">Idle</span>
|
|
587
|
+
</div>
|
|
588
|
+
<div class="agent-item">
|
|
589
|
+
<span class="agent-name">@Frontend</span>
|
|
590
|
+
<span class="agent-status idle">Idle</span>
|
|
591
|
+
</div>
|
|
592
|
+
<div class="agent-item">
|
|
593
|
+
<span class="agent-name">@Reviewer</span>
|
|
594
|
+
<span class="agent-status idle">Idle</span>
|
|
595
|
+
</div>
|
|
596
|
+
</div>
|
|
597
|
+
</div>
|
|
598
|
+
|
|
599
|
+
<div class="card">
|
|
600
|
+
<h2>Recent Activity</h2>
|
|
601
|
+
<div class="timeline" id="timeline">
|
|
602
|
+
<div class="timeline-item">
|
|
603
|
+
<span class="timeline-time">Now</span>
|
|
604
|
+
<span class="timeline-content">Cloud dashboard started</span>
|
|
605
|
+
</div>
|
|
606
|
+
</div>
|
|
607
|
+
</div>
|
|
608
|
+
</div>
|
|
609
|
+
|
|
610
|
+
<div class="team-section card">
|
|
611
|
+
<h2>Team Collaboration</h2>
|
|
612
|
+
<p style="color: var(--muted); margin-bottom: 1rem;">
|
|
613
|
+
Invite team members to collaborate in real-time on agent swarms.
|
|
614
|
+
</p>
|
|
615
|
+
<div class="team-members" id="teamMembers">
|
|
616
|
+
<div class="member-avatar">You</div>
|
|
617
|
+
</div>
|
|
618
|
+
<button class="btn" style="margin-top: 1rem;">
|
|
619
|
+
+ Invite Member
|
|
620
|
+
</button>
|
|
621
|
+
</div>
|
|
622
|
+
|
|
623
|
+
<footer>
|
|
624
|
+
Ultra-Dex v3.3.0 | Cloud Dashboard | <a href="https://github.com/Srujan0798/Ultra-Dex" style="color: var(--primary);">GitHub</a>
|
|
625
|
+
</footer>
|
|
626
|
+
</div>
|
|
627
|
+
|
|
628
|
+
<script>
|
|
629
|
+
// WebSocket connection for real-time updates
|
|
630
|
+
let ws;
|
|
631
|
+
|
|
632
|
+
function connect() {
|
|
633
|
+
ws = new WebSocket('ws://localhost:4002');
|
|
634
|
+
|
|
635
|
+
ws.onopen = () => {
|
|
636
|
+
console.log('Connected to Ultra-Dex Cloud');
|
|
637
|
+
addTimelineItem('Connected to cloud server');
|
|
638
|
+
};
|
|
639
|
+
|
|
640
|
+
ws.onmessage = (event) => {
|
|
641
|
+
const data = JSON.parse(event.data);
|
|
642
|
+
handleMessage(data);
|
|
643
|
+
};
|
|
644
|
+
|
|
645
|
+
ws.onclose = () => {
|
|
646
|
+
console.log('Disconnected, reconnecting...');
|
|
647
|
+
setTimeout(connect, 3000);
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
function handleMessage(data) {
|
|
652
|
+
if (data.type === 'state_sync') {
|
|
653
|
+
addTimelineItem('State synced from ' + data.from);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
if (data.type === 'swarm_event') {
|
|
657
|
+
updateAgentStatus(data.event.agent, data.event.status);
|
|
658
|
+
addTimelineItem(data.event.agent + ': ' + data.event.message);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
function updateAgentStatus(agent, status) {
|
|
663
|
+
const agentList = document.getElementById('agentList');
|
|
664
|
+
const items = agentList.querySelectorAll('.agent-item');
|
|
665
|
+
|
|
666
|
+
items.forEach(item => {
|
|
667
|
+
const name = item.querySelector('.agent-name').textContent;
|
|
668
|
+
if (name === agent) {
|
|
669
|
+
const statusEl = item.querySelector('.agent-status');
|
|
670
|
+
statusEl.textContent = status;
|
|
671
|
+
statusEl.className = 'agent-status ' + status.toLowerCase();
|
|
672
|
+
}
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
function addTimelineItem(content) {
|
|
677
|
+
const timeline = document.getElementById('timeline');
|
|
678
|
+
const item = document.createElement('div');
|
|
679
|
+
item.className = 'timeline-item';
|
|
680
|
+
item.innerHTML = '<span class="timeline-time">' + new Date().toLocaleTimeString() + '</span>' +
|
|
681
|
+
'<span class="timeline-content">' + content + '</span>';
|
|
682
|
+
timeline.insertBefore(item, timeline.firstChild);
|
|
683
|
+
|
|
684
|
+
// Keep only last 10 items
|
|
685
|
+
while (timeline.children.length > 10) {
|
|
686
|
+
timeline.removeChild(timeline.lastChild);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// Initialize
|
|
691
|
+
connect();
|
|
692
|
+
</script>
|
|
693
|
+
</body>
|
|
694
|
+
</html>`;
|
|
695
|
+
|
|
696
|
+
// ============================================================================
|
|
697
|
+
// DASHBOARD SERVER
|
|
698
|
+
// ============================================================================
|
|
699
|
+
|
|
700
|
+
function createDashboardServer(options = {}) {
|
|
701
|
+
const { port = CLOUD_CONFIG.ports.dashboard } = options;
|
|
702
|
+
|
|
703
|
+
const server = http.createServer((req, res) => {
|
|
704
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
705
|
+
res.end(CLOUD_DASHBOARD_HTML);
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
return server;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// ============================================================================
|
|
712
|
+
// CLI COMMAND
|
|
713
|
+
// ============================================================================
|
|
714
|
+
|
|
715
|
+
export function registerCloudCommand(program) {
|
|
716
|
+
program
|
|
717
|
+
.command('cloud')
|
|
718
|
+
.description('Start Ultra-Dex cloud server for team collaboration')
|
|
719
|
+
.option('-p, --port <port>', 'API port', '4001')
|
|
720
|
+
.option('--ws-port <port>', 'WebSocket port', '4002')
|
|
721
|
+
.option('--dashboard-port <port>', 'Dashboard port', '4003')
|
|
722
|
+
.option('--no-dashboard', 'Disable dashboard server')
|
|
723
|
+
.action(async (options) => {
|
|
724
|
+
console.log(chalk.cyan('\n☁️ Ultra-Dex Cloud Server\n'));
|
|
725
|
+
|
|
726
|
+
const spinner = ora('Starting cloud services...').start();
|
|
727
|
+
|
|
728
|
+
try {
|
|
729
|
+
// Load existing sessions
|
|
730
|
+
await sessionManager.load();
|
|
731
|
+
|
|
732
|
+
// Start API server
|
|
733
|
+
const apiPort = parseInt(options.port, 10);
|
|
734
|
+
const apiServer = createAPIServer({ port: apiPort });
|
|
735
|
+
apiServer.listen(apiPort);
|
|
736
|
+
|
|
737
|
+
// Start WebSocket server
|
|
738
|
+
const wsPort = parseInt(options.wsPort, 10);
|
|
739
|
+
createWebSocketServer({ port: wsPort });
|
|
740
|
+
|
|
741
|
+
// Start dashboard server
|
|
742
|
+
let dashboardPort = null;
|
|
743
|
+
if (options.dashboard !== false) {
|
|
744
|
+
dashboardPort = parseInt(options.dashboardPort, 10);
|
|
745
|
+
const dashboardServer = createDashboardServer({ port: dashboardPort });
|
|
746
|
+
dashboardServer.listen(dashboardPort);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
spinner.succeed('Cloud services started');
|
|
750
|
+
|
|
751
|
+
console.log(chalk.bold('\n📡 Endpoints:'));
|
|
752
|
+
console.log(` API: ${chalk.cyan(`http://localhost:${apiPort}`)}`);
|
|
753
|
+
console.log(` WebSocket: ${chalk.cyan(`ws://localhost:${wsPort}`)}`);
|
|
754
|
+
if (dashboardPort) {
|
|
755
|
+
console.log(` Dashboard: ${chalk.cyan(`http://localhost:${dashboardPort}`)}`);
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
console.log(chalk.gray('\n✨ Cloud server running. Press Ctrl+C to stop.\n'));
|
|
759
|
+
|
|
760
|
+
// Keep process running
|
|
761
|
+
process.on('SIGINT', async () => {
|
|
762
|
+
console.log(chalk.yellow('\n\nShutting down...'));
|
|
763
|
+
await sessionManager.save();
|
|
764
|
+
process.exit(0);
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
} catch (err) {
|
|
768
|
+
spinner.fail(`Failed to start: ${err.message}`);
|
|
769
|
+
}
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
export default {
|
|
774
|
+
registerCloudCommand,
|
|
775
|
+
createAPIServer,
|
|
776
|
+
createWebSocketServer,
|
|
777
|
+
createDashboardServer,
|
|
778
|
+
sessionManager,
|
|
779
|
+
CLOUD_CONFIG,
|
|
780
|
+
};
|