ultra-dex 2.2.1 → 3.2.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.
Files changed (87) hide show
  1. package/README.md +112 -151
  2. package/assets/agents/00-AGENT_INDEX.md +1 -1
  3. package/assets/code-patterns/clerk-middleware.ts +138 -0
  4. package/assets/code-patterns/prisma-schema.prisma +224 -0
  5. package/assets/code-patterns/rls-policies.sql +246 -0
  6. package/assets/code-patterns/server-actions.ts +191 -0
  7. package/assets/code-patterns/trpc-router.ts +258 -0
  8. package/assets/cursor-rules/13-ai-integration.mdc +155 -0
  9. package/assets/cursor-rules/14-server-components.mdc +81 -0
  10. package/assets/cursor-rules/15-server-actions.mdc +102 -0
  11. package/assets/cursor-rules/16-edge-middleware.mdc +105 -0
  12. package/assets/cursor-rules/17-streaming-ssr.mdc +138 -0
  13. package/assets/docs/LAUNCH-POSTS.md +1 -1
  14. package/assets/docs/QUICK-REFERENCE.md +9 -4
  15. package/assets/docs/VISION-V2.md +1 -1
  16. package/assets/hooks/pre-commit +98 -0
  17. package/assets/saas-plan/04-Imp-Template.md +1 -1
  18. package/bin/ultra-dex.js +132 -4
  19. package/lib/commands/advanced.js +471 -0
  20. package/lib/commands/agent-builder.js +226 -0
  21. package/lib/commands/agents.js +102 -42
  22. package/lib/commands/auto-implement.js +68 -0
  23. package/lib/commands/banner.js +43 -21
  24. package/lib/commands/build.js +78 -183
  25. package/lib/commands/ci-monitor.js +84 -0
  26. package/lib/commands/config.js +207 -0
  27. package/lib/commands/dashboard.js +770 -0
  28. package/lib/commands/diff.js +233 -0
  29. package/lib/commands/doctor.js +416 -0
  30. package/lib/commands/export.js +408 -0
  31. package/lib/commands/fix.js +96 -0
  32. package/lib/commands/generate.js +105 -78
  33. package/lib/commands/hooks.js +251 -76
  34. package/lib/commands/init.js +102 -54
  35. package/lib/commands/memory.js +80 -0
  36. package/lib/commands/plan.js +82 -0
  37. package/lib/commands/review.js +34 -5
  38. package/lib/commands/run.js +233 -0
  39. package/lib/commands/scaffold.js +151 -0
  40. package/lib/commands/serve.js +179 -146
  41. package/lib/commands/state.js +327 -0
  42. package/lib/commands/swarm.js +306 -0
  43. package/lib/commands/sync.js +82 -23
  44. package/lib/commands/team.js +275 -0
  45. package/lib/commands/upgrade.js +190 -0
  46. package/lib/commands/validate.js +34 -0
  47. package/lib/commands/verify.js +81 -0
  48. package/lib/commands/watch.js +79 -0
  49. package/lib/config/theme.js +47 -0
  50. package/lib/mcp/graph.js +92 -0
  51. package/lib/mcp/memory.js +95 -0
  52. package/lib/mcp/resources.js +152 -0
  53. package/lib/mcp/server.js +34 -0
  54. package/lib/mcp/tools.js +481 -0
  55. package/lib/mcp/websocket.js +117 -0
  56. package/lib/providers/index.js +49 -4
  57. package/lib/providers/ollama.js +136 -0
  58. package/lib/providers/router.js +63 -0
  59. package/lib/quality/scanner.js +128 -0
  60. package/lib/swarm/coordinator.js +97 -0
  61. package/lib/swarm/index.js +598 -0
  62. package/lib/swarm/protocol.js +677 -0
  63. package/lib/swarm/tiers.js +485 -0
  64. package/lib/templates/code/clerk-middleware.ts +138 -0
  65. package/lib/templates/code/prisma-schema.prisma +224 -0
  66. package/lib/templates/code/rls-policies.sql +246 -0
  67. package/lib/templates/code/server-actions.ts +191 -0
  68. package/lib/templates/code/trpc-router.ts +258 -0
  69. package/lib/templates/custom-agent.md +10 -0
  70. package/lib/themes/doomsday.js +229 -0
  71. package/lib/ui/index.js +5 -0
  72. package/lib/ui/interface.js +241 -0
  73. package/lib/ui/spinners.js +116 -0
  74. package/lib/ui/theme.js +183 -0
  75. package/lib/utils/agents.js +32 -0
  76. package/lib/utils/files.js +14 -0
  77. package/lib/utils/graph.js +108 -0
  78. package/lib/utils/help.js +64 -0
  79. package/lib/utils/messages.js +35 -0
  80. package/lib/utils/progress.js +24 -0
  81. package/lib/utils/prompts.js +47 -0
  82. package/lib/utils/spinners.js +46 -0
  83. package/lib/utils/status.js +31 -0
  84. package/lib/utils/tables.js +41 -0
  85. package/lib/utils/theme-state.js +9 -0
  86. package/lib/utils/version-display.js +32 -0
  87. package/package.json +31 -13
@@ -0,0 +1,471 @@
1
+ /**
2
+ * ultra-dex diff & export commands
3
+ * Compare plan vs code, export to various formats
4
+ */
5
+
6
+ import chalk from 'chalk';
7
+ import ora from 'ora';
8
+ import fs from 'fs/promises';
9
+ import path from 'path';
10
+ import { execSync } from 'child_process';
11
+
12
+ async function readFileSafe(filePath) {
13
+ try {
14
+ return await fs.readFile(filePath, 'utf8');
15
+ } catch {
16
+ return null;
17
+ }
18
+ }
19
+
20
+ async function getProjectStructure(dir, depth = 3) {
21
+ const structure = [];
22
+
23
+ async function scan(currentDir, currentDepth, prefix = '') {
24
+ if (currentDepth <= 0) return;
25
+
26
+ try {
27
+ const entries = await fs.readdir(currentDir, { withFileTypes: true });
28
+ const filtered = entries.filter(e =>
29
+ !['node_modules', '.git', '.next', 'dist', 'build', '.ultra-dex', '.ultra'].includes(e.name) &&
30
+ !e.name.startsWith('.')
31
+ );
32
+
33
+ for (const entry of filtered) {
34
+ const fullPath = path.join(currentDir, entry.name);
35
+ const relativePath = path.relative(dir, fullPath);
36
+
37
+ if (entry.isDirectory()) {
38
+ structure.push({ type: 'dir', path: relativePath });
39
+ await scan(fullPath, currentDepth - 1, prefix + ' ');
40
+ } else {
41
+ structure.push({ type: 'file', path: relativePath });
42
+ }
43
+ }
44
+ } catch { /* permission denied or other error */ }
45
+ }
46
+
47
+ await scan(dir, depth);
48
+ return structure;
49
+ }
50
+
51
+ function extractPlanExpectations(planContent) {
52
+ const expectations = {
53
+ database: [],
54
+ api: [],
55
+ frontend: [],
56
+ auth: [],
57
+ testing: [],
58
+ other: []
59
+ };
60
+
61
+ if (!planContent) return expectations;
62
+
63
+ // Extract database expectations (look for schema, tables, models)
64
+ const dbPatterns = /(?:table|schema|model|entity|database|prisma)[\s:]+[`"]?(\w+)[`"]?/gi;
65
+ let match;
66
+ while ((match = dbPatterns.exec(planContent)) !== null) {
67
+ if (!expectations.database.includes(match[1].toLowerCase())) {
68
+ expectations.database.push(match[1].toLowerCase());
69
+ }
70
+ }
71
+
72
+ // Extract API endpoints
73
+ const apiPatterns = /(?:endpoint|route|api|POST|GET|PUT|DELETE|PATCH)[\s:]+[`"]?([\/\w-]+)[`"]?/gi;
74
+ while ((match = apiPatterns.exec(planContent)) !== null) {
75
+ if (!expectations.api.includes(match[1])) {
76
+ expectations.api.push(match[1]);
77
+ }
78
+ }
79
+
80
+ // Extract frontend components
81
+ const componentPatterns = /(?:component|page|view|screen)[\s:]+[`"]?(\w+)[`"]?/gi;
82
+ while ((match = componentPatterns.exec(planContent)) !== null) {
83
+ if (!expectations.frontend.includes(match[1])) {
84
+ expectations.frontend.push(match[1]);
85
+ }
86
+ }
87
+
88
+ // Extract auth features
89
+ const authPatterns = /(?:auth|login|signup|session|jwt|oauth|password)/gi;
90
+ while ((match = authPatterns.exec(planContent)) !== null) {
91
+ if (!expectations.auth.includes(match[0].toLowerCase())) {
92
+ expectations.auth.push(match[0].toLowerCase());
93
+ }
94
+ }
95
+
96
+ // Extract testing expectations
97
+ const testPatterns = /(?:test|spec|coverage|jest|vitest|cypress)/gi;
98
+ while ((match = testPatterns.exec(planContent)) !== null) {
99
+ if (!expectations.testing.includes(match[0].toLowerCase())) {
100
+ expectations.testing.push(match[0].toLowerCase());
101
+ }
102
+ }
103
+
104
+ return expectations;
105
+ }
106
+
107
+ function checkCodeImplementation(structure, expectations) {
108
+ const findings = {
109
+ implemented: [],
110
+ missing: [],
111
+ extra: []
112
+ };
113
+
114
+ const fileNames = structure.filter(s => s.type === 'file').map(s => s.path.toLowerCase());
115
+ const dirNames = structure.filter(s => s.type === 'dir').map(s => s.path.toLowerCase());
116
+
117
+ // Check database
118
+ const hasSchema = fileNames.some(f => f.includes('schema') || f.includes('prisma'));
119
+ if (expectations.database.length > 0 && hasSchema) {
120
+ findings.implemented.push('Database schema exists');
121
+ } else if (expectations.database.length > 0) {
122
+ findings.missing.push('Database schema (expected models: ' + expectations.database.slice(0, 3).join(', ') + ')');
123
+ }
124
+
125
+ // Check API
126
+ const hasApi = dirNames.some(d => d.includes('api') || d.includes('routes'));
127
+ if (expectations.api.length > 0 && hasApi) {
128
+ findings.implemented.push('API routes directory exists');
129
+ } else if (expectations.api.length > 0) {
130
+ findings.missing.push('API routes directory');
131
+ }
132
+
133
+ // Check frontend
134
+ const hasFrontend = dirNames.some(d => d.includes('components') || d.includes('pages') || d.includes('app'));
135
+ if (expectations.frontend.length > 0 && hasFrontend) {
136
+ findings.implemented.push('Frontend components exist');
137
+ } else if (expectations.frontend.length > 0) {
138
+ findings.missing.push('Frontend components');
139
+ }
140
+
141
+ // Check auth
142
+ const hasAuth = fileNames.some(f => f.includes('auth') || f.includes('login') || f.includes('session'));
143
+ if (expectations.auth.length > 0 && hasAuth) {
144
+ findings.implemented.push('Authentication implemented');
145
+ } else if (expectations.auth.length > 0) {
146
+ findings.missing.push('Authentication (expected: ' + expectations.auth.slice(0, 3).join(', ') + ')');
147
+ }
148
+
149
+ // Check testing
150
+ const hasTests = fileNames.some(f => f.includes('.test.') || f.includes('.spec.')) ||
151
+ dirNames.some(d => d.includes('test') || d.includes('__tests__'));
152
+ if (expectations.testing.length > 0 && hasTests) {
153
+ findings.implemented.push('Tests exist');
154
+ } else if (expectations.testing.length > 0) {
155
+ findings.missing.push('Tests');
156
+ }
157
+
158
+ return findings;
159
+ }
160
+
161
+ export function registerDiffCommand(program) {
162
+ program
163
+ .command('diff')
164
+ .description('Compare implementation plan vs actual code')
165
+ .option('-d, --dir <directory>', 'Directory to analyze', '.')
166
+ .option('--json', 'Output as JSON')
167
+ .action(async (options) => {
168
+ console.log(chalk.cyan('\n🔍 Ultra-Dex Diff: Plan vs Code\n'));
169
+
170
+ const spinner = ora('Analyzing project...').start();
171
+
172
+ // Load plan
173
+ const plan = await readFileSafe(path.join(options.dir, 'IMPLEMENTATION-PLAN.md'));
174
+ if (!plan) {
175
+ spinner.fail('No IMPLEMENTATION-PLAN.md found');
176
+ return;
177
+ }
178
+
179
+ // Get project structure
180
+ const structure = await getProjectStructure(path.resolve(options.dir));
181
+
182
+ // Extract expectations and check implementation
183
+ const expectations = extractPlanExpectations(plan);
184
+ const findings = checkCodeImplementation(structure, expectations);
185
+
186
+ spinner.succeed('Analysis complete');
187
+
188
+ // Calculate alignment
189
+ const totalExpected = Object.values(expectations).flat().length;
190
+ const implementedCount = findings.implemented.length;
191
+ const missingCount = findings.missing.length;
192
+ const alignmentScore = totalExpected > 0
193
+ ? Math.round((implementedCount / (implementedCount + missingCount)) * 100)
194
+ : 0;
195
+
196
+ if (options.json) {
197
+ console.log(JSON.stringify({
198
+ score: alignmentScore,
199
+ expectations,
200
+ findings,
201
+ structure: structure.slice(0, 50)
202
+ }, null, 2));
203
+ return;
204
+ }
205
+
206
+ // Display results
207
+ console.log(chalk.bold('\n📊 Alignment Analysis\n'));
208
+ console.log(chalk.gray('─'.repeat(50)));
209
+
210
+ const scoreColor = alignmentScore >= 80 ? 'green' : alignmentScore >= 50 ? 'yellow' : 'red';
211
+ console.log(chalk[scoreColor](` Code-to-Plan Alignment: ${alignmentScore}%`));
212
+
213
+ console.log(chalk.gray('─'.repeat(50)));
214
+
215
+ if (findings.implemented.length > 0) {
216
+ console.log(chalk.green('\n✅ Implemented:'));
217
+ findings.implemented.forEach(item => console.log(chalk.gray(` • ${item}`)));
218
+ }
219
+
220
+ if (findings.missing.length > 0) {
221
+ console.log(chalk.red('\n❌ Missing:'));
222
+ findings.missing.forEach(item => console.log(chalk.yellow(` • ${item}`)));
223
+ }
224
+
225
+ console.log(chalk.bold('\n📋 Plan Expectations Found:\n'));
226
+ Object.entries(expectations).forEach(([category, items]) => {
227
+ if (items.length > 0) {
228
+ console.log(chalk.cyan(` ${category}: `) + chalk.gray(items.slice(0, 5).join(', ') + (items.length > 5 ? '...' : '')));
229
+ }
230
+ });
231
+
232
+ console.log(chalk.gray('\n─'.repeat(50)));
233
+ console.log(chalk.cyan('\n💡 Run `ultra-dex run planner` to get tasks for missing items.\n'));
234
+ });
235
+ }
236
+
237
+ export function registerExportCommand(program) {
238
+ program
239
+ .command('export')
240
+ .description('Export project to various formats')
241
+ .option('-f, --format <format>', 'Export format: json, markdown, html', 'json')
242
+ .option('-o, --output <file>', 'Output file path')
243
+ .option('--include-code', 'Include code snippets in export')
244
+ .action(async (options) => {
245
+ console.log(chalk.cyan('\n📦 Ultra-Dex Export\n'));
246
+
247
+ const spinner = ora('Gathering project data...').start();
248
+
249
+ // Gather all project data
250
+ const data = {
251
+ exportedAt: new Date().toISOString(),
252
+ project: { name: path.basename(process.cwd()) },
253
+ files: {},
254
+ state: null,
255
+ git: null
256
+ };
257
+
258
+ // Load all markdown files
259
+ const mdFiles = ['CONTEXT.md', 'IMPLEMENTATION-PLAN.md', 'CHECKLIST.md', 'QUICK-START.md'];
260
+ for (const file of mdFiles) {
261
+ const content = await readFileSafe(file);
262
+ if (content) {
263
+ data.files[file] = content;
264
+ }
265
+ }
266
+
267
+ // Load state
268
+ const stateContent = await readFileSafe('.ultra/state.json');
269
+ if (stateContent) {
270
+ try {
271
+ data.state = JSON.parse(stateContent);
272
+ } catch { /* invalid json */ }
273
+ }
274
+
275
+ // Get git info
276
+ try {
277
+ data.git = {
278
+ branch: execSync('git branch --show-current', { encoding: 'utf8' }).trim(),
279
+ remoteUrl: execSync('git remote get-url origin 2>/dev/null', { encoding: 'utf8' }).trim(),
280
+ lastCommit: execSync('git log -1 --format="%H %s"', { encoding: 'utf8' }).trim()
281
+ };
282
+ } catch { /* not a git repo */ }
283
+
284
+ spinner.succeed('Data gathered');
285
+
286
+ let output;
287
+ let extension;
288
+
289
+ switch (options.format) {
290
+ case 'json':
291
+ output = JSON.stringify(data, null, 2);
292
+ extension = '.json';
293
+ break;
294
+
295
+ case 'markdown':
296
+ output = `# ${data.project.name} - Ultra-Dex Export
297
+
298
+ Exported: ${new Date(data.exportedAt).toLocaleString()}
299
+
300
+ ${data.git ? `## Git Info
301
+ - Branch: ${data.git.branch}
302
+ - Last Commit: ${data.git.lastCommit}
303
+ ` : ''}
304
+
305
+ ${data.state ? `## Project State
306
+ - Score: ${data.state.score}/100
307
+ - Sections: ${data.state.sections?.completed || 0}/34
308
+ ` : ''}
309
+
310
+ ${Object.entries(data.files).map(([name, content]) => `
311
+ ---
312
+
313
+ ## ${name}
314
+
315
+ ${content}
316
+ `).join('\n')}
317
+ `;
318
+ extension = '.md';
319
+ break;
320
+
321
+ case 'html':
322
+ output = `<!DOCTYPE html>
323
+ <html>
324
+ <head>
325
+ <title>${data.project.name} - Ultra-Dex Export</title>
326
+ <style>
327
+ body { font-family: system-ui, sans-serif; max-width: 800px; margin: 0 auto; padding: 2rem; }
328
+ h1 { color: #06b6d4; }
329
+ pre { background: #1e1e1e; color: #d4d4d4; padding: 1rem; overflow-x: auto; border-radius: 0.5rem; }
330
+ .meta { color: #666; font-size: 0.875rem; }
331
+ .section { margin: 2rem 0; padding: 1rem; border: 1px solid #e5e5e5; border-radius: 0.5rem; }
332
+ </style>
333
+ </head>
334
+ <body>
335
+ <h1>🚀 ${data.project.name}</h1>
336
+ <p class="meta">Exported: ${new Date(data.exportedAt).toLocaleString()}</p>
337
+
338
+ ${data.state ? `
339
+ <div class="section">
340
+ <h2>📊 Project State</h2>
341
+ <p>Score: <strong>${data.state.score}/100</strong></p>
342
+ <p>Sections: ${data.state.sections?.completed || 0}/34</p>
343
+ </div>
344
+ ` : ''}
345
+
346
+ ${Object.entries(data.files).map(([name, content]) => `
347
+ <div class="section">
348
+ <h2>📄 ${name}</h2>
349
+ <pre>${content.replace(/</g, '&lt;').replace(/>/g, '&gt;')}</pre>
350
+ </div>
351
+ `).join('\n')}
352
+ </body>
353
+ </html>`;
354
+ extension = '.html';
355
+ break;
356
+
357
+ default:
358
+ console.log(chalk.red(`Unknown format: ${options.format}`));
359
+ return;
360
+ }
361
+
362
+ if (options.output) {
363
+ await fs.writeFile(options.output, output);
364
+ console.log(chalk.green(`✅ Exported to: ${options.output}`));
365
+ } else {
366
+ const defaultPath = `ultra-dex-export${extension}`;
367
+ await fs.writeFile(defaultPath, output);
368
+ console.log(chalk.green(`✅ Exported to: ${defaultPath}`));
369
+ }
370
+
371
+ console.log(chalk.gray(` Format: ${options.format}`));
372
+ console.log(chalk.gray(` Size: ${(output.length / 1024).toFixed(1)}KB\n`));
373
+ });
374
+ }
375
+
376
+ export function registerCheckCommand(program) {
377
+ program
378
+ .command('check')
379
+ .description('Repository health and alignment check (God Mode)')
380
+ .action(async () => {
381
+ console.log(chalk.cyan('\n🩺 Ultra-Dex Repository Check\n'));
382
+
383
+ const { buildGraph } = await import('../utils/graph.js');
384
+ const { loadState } = await import('./state.js');
385
+
386
+ // 1. Check Graph
387
+ const graphSpinner = (await import('ora')).default('Checking Code Property Graph...').start();
388
+ try {
389
+ const graph = await buildGraph();
390
+ graphSpinner.succeed(chalk.green(`CPG Healthy: ${graph.nodes.length} nodes, ${graph.edges.length} edges`));
391
+ } catch (e) {
392
+ graphSpinner.fail(chalk.red(`CPG Corrupt: ${e.message}`));
393
+ }
394
+
395
+ // 2. Check State
396
+ const stateSpinner = (await import('ora')).default('Checking Project State...').start();
397
+ const state = await loadState();
398
+ if (state) {
399
+ stateSpinner.succeed(chalk.green('Project state loaded'));
400
+ } else {
401
+ stateSpinner.warn(chalk.yellow('No .ultra/state.json found. System is stateless.'));
402
+ }
403
+
404
+ // 3. Check Core Files
405
+ const files = ['CONTEXT.md', 'IMPLEMENTATION-PLAN.md', 'QUICK-START.md'];
406
+ console.log(chalk.bold('\nCore Documents:'));
407
+ for (const file of files) {
408
+ try {
409
+ await fs.access(file);
410
+ console.log(chalk.green(` ✅ ${file}`));
411
+ } catch {
412
+ console.log(chalk.red(` ❌ ${file} (Required)`));
413
+ }
414
+ }
415
+
416
+ console.log(chalk.cyan('\n💡 Run "ultra-dex audit" for a detailed scoring report.\n'));
417
+ });
418
+ }
419
+
420
+ export function registerUpgradeCommand(program) {
421
+ program
422
+ .command('upgrade')
423
+ .description('Check for and install Ultra-Dex updates')
424
+ .option('--check', 'Only check for updates, don\'t install')
425
+ .action(async (options) => {
426
+ console.log(chalk.cyan('\n🔄 Ultra-Dex Upgrade\n'));
427
+
428
+ const spinner = ora('Checking for updates...').start();
429
+
430
+ try {
431
+ // Get current version
432
+ const currentVersion = '2.4.0';
433
+
434
+ // Check npm for latest version
435
+ const latestInfo = execSync('npm view ultra-dex version 2>/dev/null', { encoding: 'utf8' }).trim();
436
+ const latestVersion = latestInfo || currentVersion;
437
+
438
+ spinner.succeed('Version check complete');
439
+
440
+ console.log(chalk.gray(` Current: v${currentVersion}`));
441
+ console.log(chalk.gray(` Latest: v${latestVersion}`));
442
+
443
+ if (currentVersion === latestVersion) {
444
+ console.log(chalk.green('\n✅ You\'re on the latest version!\n'));
445
+ return;
446
+ }
447
+
448
+ if (options.check) {
449
+ console.log(chalk.yellow(`\n⬆️ Update available: v${currentVersion} → v${latestVersion}`));
450
+ console.log(chalk.gray(' Run `ultra-dex upgrade` to install.\n'));
451
+ return;
452
+ }
453
+
454
+ // Install update
455
+ const installSpinner = ora('Installing update...').start();
456
+ execSync('npm install -g ultra-dex@latest', { encoding: 'utf8' });
457
+ installSpinner.succeed('Update installed!');
458
+
459
+ console.log(chalk.green(`\n✅ Upgraded to v${latestVersion}!\n`));
460
+ console.log(chalk.gray(' Run `ultra-dex --version` to verify.\n'));
461
+
462
+ } catch (err) {
463
+ spinner.fail('Upgrade check failed');
464
+ console.log(chalk.yellow('\n⚠️ Could not check for updates.'));
465
+ console.log(chalk.gray(' You may not be connected to npm, or ultra-dex is not published yet.'));
466
+ console.log(chalk.gray(` Current version: v3.0.0\n`));
467
+ }
468
+ });
469
+ }
470
+
471
+ export default { registerDiffCommand, registerExportCommand, registerUpgradeCommand };
@@ -0,0 +1,226 @@
1
+ import chalk from 'chalk';
2
+ import fs from 'fs/promises';
3
+ import path from 'path';
4
+ import inquirer from 'inquirer';
5
+ import { fileURLToPath } from 'url';
6
+ import { AGENTS, findBuiltInAgent, listCustomAgents, readCustomAgent, getCustomAgentPath, readAgentPrompt } from './agents.js';
7
+ import { validateProjectName } from '../utils/validation.js';
8
+
9
+ const CUSTOM_AGENTS_DIR = path.join(process.cwd(), '.ultra-dex', 'custom-agents');
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = path.dirname(__filename);
12
+ const TEMPLATE_PATH = path.join(__dirname, '../templates/custom-agent.md');
13
+
14
+ const TIERS = [
15
+ { name: '0 - Orchestration', value: '0' },
16
+ { name: '1 - Leadership', value: '1' },
17
+ { name: '2 - Development', value: '2' },
18
+ { name: '3 - Security', value: '3' },
19
+ { name: '4 - DevOps', value: '4' },
20
+ { name: '5 - Quality', value: '5' },
21
+ { name: '6 - Specialist', value: '6' },
22
+ ];
23
+
24
+ function validateKebabCase(name) {
25
+ const result = validateProjectName(name);
26
+ if (result !== true) return result;
27
+ if (name !== name.toLowerCase()) {
28
+ return 'Agent name must be kebab-case (lowercase letters, numbers, dashes)';
29
+ }
30
+ return true;
31
+ }
32
+
33
+ async function readTemplate() {
34
+ return fs.readFile(TEMPLATE_PATH, 'utf-8');
35
+ }
36
+
37
+ function renderTemplate(template, data) {
38
+ return template
39
+ .replace(/{{name}}/g, data.name)
40
+ .replace(/{{tier}}/g, data.tier)
41
+ .replace(/{{role}}/g, data.role)
42
+ .replace(/{{expertise}}/g, data.expertise)
43
+ .replace(/{{prompt}}/g, data.prompt);
44
+ }
45
+
46
+ async function ensureCustomDir() {
47
+ await fs.mkdir(CUSTOM_AGENTS_DIR, { recursive: true });
48
+ }
49
+
50
+ async function listBuiltinAgents() {
51
+ return AGENTS.map(agent => ({
52
+ name: agent.name,
53
+ description: agent.description,
54
+ tier: agent.tier,
55
+ }));
56
+ }
57
+
58
+ export function registerAgentBuilderCommand(program) {
59
+ const agentCommand = program
60
+ .command('agent')
61
+ .description('Manage AI agents');
62
+
63
+ agentCommand
64
+ .command('create <name>')
65
+ .description('Create a custom agent with an interactive wizard')
66
+ .action(async (name) => {
67
+ const validation = validateKebabCase(name);
68
+ if (validation !== true) {
69
+ console.log(chalk.red(`\n❌ ${validation}\n`));
70
+ process.exit(1);
71
+ }
72
+
73
+ if (findBuiltInAgent(name)) {
74
+ console.log(chalk.red(`\n❌ "${name}" conflicts with a built-in agent.\n`));
75
+ process.exit(1);
76
+ }
77
+
78
+ await ensureCustomDir();
79
+ const existing = await getCustomAgentPath(name);
80
+ if (existing) {
81
+ console.log(chalk.red(`\n❌ Custom agent "${name}" already exists.\n`));
82
+ process.exit(1);
83
+ }
84
+
85
+ const answers = await inquirer.prompt([
86
+ {
87
+ type: 'input',
88
+ name: 'role',
89
+ message: 'Role description (1 sentence):',
90
+ validate: input => input.trim().length > 0 || 'Role description is required',
91
+ },
92
+ {
93
+ type: 'list',
94
+ name: 'tier',
95
+ message: 'Select tier:',
96
+ choices: TIERS,
97
+ },
98
+ {
99
+ type: 'input',
100
+ name: 'expertise',
101
+ message: 'Expertise areas (comma-separated):',
102
+ validate: input => input.trim().length > 0 || 'Expertise is required',
103
+ },
104
+ {
105
+ type: 'editor',
106
+ name: 'prompt',
107
+ message: 'Base system prompt:',
108
+ validate: input => input.trim().length > 0 || 'System prompt is required',
109
+ },
110
+ ]);
111
+
112
+ const template = await readTemplate();
113
+ const content = renderTemplate(template, {
114
+ name,
115
+ tier: answers.tier,
116
+ role: answers.role.trim(),
117
+ expertise: answers.expertise.trim(),
118
+ prompt: answers.prompt.trim(),
119
+ });
120
+
121
+ const outputPath = path.join(CUSTOM_AGENTS_DIR, `${name}.md`);
122
+ await fs.writeFile(outputPath, content);
123
+ console.log(chalk.green(`\n✅ Custom agent created: ${outputPath}\n`));
124
+ });
125
+
126
+ agentCommand
127
+ .command('list')
128
+ .description('List built-in and custom agents')
129
+ .option('--all', 'Show built-in and custom agents')
130
+ .option('--custom', 'Show custom agents only')
131
+ .option('--builtin', 'Show built-in agents only')
132
+ .action(async (options) => {
133
+ const showCustom = options.custom || options.all || (!options.custom && !options.builtin);
134
+ const showBuiltin = options.builtin || options.all || (!options.custom && !options.builtin);
135
+
136
+ if (showBuiltin) {
137
+ console.log(chalk.bold('\n🤖 Built-in Agents\n'));
138
+ const builtinAgents = await listBuiltinAgents();
139
+ let currentTier = '';
140
+ builtinAgents.forEach((agent) => {
141
+ if (agent.tier !== currentTier) {
142
+ currentTier = agent.tier;
143
+ console.log(chalk.bold(` ${currentTier} Tier:`));
144
+ }
145
+ console.log(chalk.cyan(` ${agent.name}`) + chalk.gray(` - ${agent.description}`));
146
+ });
147
+ }
148
+
149
+ if (showCustom) {
150
+ const customAgents = await listCustomAgents();
151
+ console.log(chalk.bold('\n✨ Custom Agents\n'));
152
+ if (customAgents.length === 0) {
153
+ console.log(chalk.gray(' (none found)'));
154
+ } else {
155
+ customAgents.forEach((name) => console.log(chalk.cyan(` ${name}`)));
156
+ }
157
+ }
158
+ console.log('');
159
+ });
160
+
161
+ agentCommand
162
+ .command('show <name>')
163
+ .description('Show a specific agent prompt')
164
+ .action(async (name) => {
165
+ const agent = findBuiltInAgent(name);
166
+ if (agent) {
167
+ try {
168
+ const content = await readAgentPrompt(agent);
169
+ console.log(chalk.bold(`\n🤖 ${agent.name.toUpperCase()} Agent\n`));
170
+ console.log(chalk.gray('─'.repeat(60)));
171
+ console.log(content);
172
+ console.log(chalk.gray('─'.repeat(60)));
173
+ console.log(chalk.bold('\n📋 Copy the above prompt and paste into your AI tool.\n'));
174
+ } catch {
175
+ console.log(chalk.bold(`\n🤖 ${agent.name.toUpperCase()} Agent\n`));
176
+ console.log(chalk.gray('View full prompt on GitHub:'));
177
+ console.log(chalk.blue(` https://github.com/Srujan0798/Ultra-Dex/blob/main/agents/${agent.file}\n`));
178
+ }
179
+ return;
180
+ }
181
+
182
+ try {
183
+ const content = await readCustomAgent(name);
184
+ console.log(chalk.bold(`\n✨ ${name.toUpperCase()} Custom Agent\n`));
185
+ console.log(chalk.gray('─'.repeat(60)));
186
+ console.log(content);
187
+ console.log(chalk.gray('─'.repeat(60)));
188
+ } catch {
189
+ console.log(chalk.red(`\n❌ Agent "${name}" not found.\n`));
190
+ process.exit(1);
191
+ }
192
+ });
193
+
194
+ agentCommand
195
+ .command('delete <name>')
196
+ .description('Delete a custom agent')
197
+ .action(async (name) => {
198
+ if (findBuiltInAgent(name)) {
199
+ console.log(chalk.red(`\n❌ Cannot delete built-in agent "${name}".\n`));
200
+ process.exit(1);
201
+ }
202
+
203
+ const filePath = await getCustomAgentPath(name);
204
+ if (!filePath) {
205
+ console.log(chalk.red(`\n❌ Custom agent "${name}" not found.\n`));
206
+ process.exit(1);
207
+ }
208
+
209
+ const { confirmDelete } = await inquirer.prompt([
210
+ {
211
+ type: 'confirm',
212
+ name: 'confirmDelete',
213
+ message: `Delete custom agent "${name}"?`,
214
+ default: false,
215
+ },
216
+ ]);
217
+
218
+ if (!confirmDelete) {
219
+ console.log(chalk.gray('\nAborted.\n'));
220
+ return;
221
+ }
222
+
223
+ await fs.unlink(filePath);
224
+ console.log(chalk.green(`\n✅ Deleted custom agent "${name}".\n`));
225
+ });
226
+ }