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,233 @@
1
+ // cli/lib/commands/diff.js
2
+ import chalk from 'chalk';
3
+ import { readFileSync, existsSync, readdirSync } from 'fs';
4
+ import { join } from 'path';
5
+ import { execSync } from 'child_process';
6
+ import { loadConfig } from './config.js';
7
+
8
+ const STATUS = {
9
+ DONE: 'done',
10
+ PARTIAL: 'partial',
11
+ MISSING: 'missing'
12
+ };
13
+
14
+ export function diffCommand(options) {
15
+ if (!options.json) {
16
+ console.log(chalk.cyan.bold('\nšŸ“Š Ultra-Dex Diff - Plan vs Code v3.0\n'));
17
+ }
18
+
19
+ const planPath = join(process.cwd(), 'IMPLEMENTATION-PLAN.md');
20
+ if (!existsSync(planPath)) {
21
+ if (options.json) {
22
+ console.log(JSON.stringify({ error: 'No IMPLEMENTATION-PLAN.md found', sections: [], alignment: 0 }));
23
+ } else {
24
+ console.log(chalk.red('No IMPLEMENTATION-PLAN.md found'));
25
+ }
26
+ return;
27
+ }
28
+
29
+ const plan = readFileSync(planPath, 'utf-8');
30
+
31
+ // Extract planned sections
32
+ const plannedSections = extractSections(plan);
33
+
34
+ if (plannedSections.length === 0) {
35
+ if (options.json) {
36
+ console.log(JSON.stringify({ error: 'No sections found', sections: [], alignment: 0 }));
37
+ } else {
38
+ console.log(chalk.yellow('No sections found in IMPLEMENTATION-PLAN.md (looking for ## or ### headings)'));
39
+ }
40
+ return;
41
+ }
42
+
43
+ // Check implementation status
44
+ const config = loadConfig();
45
+ const results = checkImplementationStatus(plannedSections, config);
46
+
47
+ // Calculate alignment
48
+ const doneCount = results.filter(r => r.status === STATUS.DONE).length;
49
+ const partialCount = results.filter(r => r.status === STATUS.PARTIAL).length;
50
+ const missingCount = results.filter(r => r.status === STATUS.MISSING).length;
51
+ const alignment = Math.round(((doneCount + partialCount * 0.5) / results.length) * 100);
52
+
53
+ if (options.json) {
54
+ console.log(JSON.stringify({
55
+ alignment,
56
+ totalSections: results.length,
57
+ done: doneCount,
58
+ partial: partialCount,
59
+ missing: missingCount,
60
+ sections: results.map(r => ({
61
+ title: r.title,
62
+ status: r.status,
63
+ matches: r.matches
64
+ }))
65
+ }, null, 2));
66
+ return;
67
+ }
68
+
69
+ // Color output
70
+ console.log(chalk.white.bold('Planned vs Implemented:\n'));
71
+
72
+ results.forEach(({ title, status, matches }) => {
73
+ let icon, color;
74
+ switch (status) {
75
+ case STATUS.DONE:
76
+ icon = 'āœ…';
77
+ color = 'green';
78
+ break;
79
+ case STATUS.PARTIAL:
80
+ icon = 'āš ļø';
81
+ color = 'yellow';
82
+ break;
83
+ case STATUS.MISSING:
84
+ icon = 'āŒ';
85
+ color = 'red';
86
+ break;
87
+ }
88
+ console.log(` ${icon} ${chalk[color](title)}`);
89
+ if (matches && matches.length > 0 && status !== STATUS.MISSING) {
90
+ matches.slice(0, 2).forEach(m => {
91
+ console.log(chalk.gray(` → ${m}`));
92
+ });
93
+ }
94
+ });
95
+
96
+ // Summary
97
+ console.log(chalk.white.bold('\n─────────────────────────────────────'));
98
+ const alignColor = alignment >= 80 ? 'green' : alignment >= 50 ? 'yellow' : 'red';
99
+ console.log(chalk[alignColor].bold(`Alignment: ${alignment}%`));
100
+ console.log(chalk.gray(` ${chalk.green(`Done: ${doneCount}`)} | ${chalk.yellow(`Partial: ${partialCount}`)} | ${chalk.red(`Missing: ${missingCount}`)}`));
101
+ }
102
+
103
+ function extractSections(plan) {
104
+ const sections = [];
105
+ const lines = plan.split('\n');
106
+ let currentSection = null;
107
+ let sectionContent = [];
108
+
109
+ lines.forEach(line => {
110
+ const headingMatch = line.match(/^(#{2,3})\s+(.+)$/);
111
+ if (headingMatch) {
112
+ if (currentSection) {
113
+ sections.push({
114
+ level: currentSection.level,
115
+ title: currentSection.title,
116
+ content: sectionContent.join('\n'),
117
+ keywords: extractKeywords(currentSection.title, sectionContent.join('\n'))
118
+ });
119
+ }
120
+ currentSection = {
121
+ level: headingMatch[1].length,
122
+ title: headingMatch[2].trim()
123
+ };
124
+ sectionContent = [];
125
+ } else if (currentSection) {
126
+ sectionContent.push(line);
127
+ }
128
+ });
129
+
130
+ if (currentSection) {
131
+ sections.push({
132
+ level: currentSection.level,
133
+ title: currentSection.title,
134
+ content: sectionContent.join('\n'),
135
+ keywords: extractKeywords(currentSection.title, sectionContent.join('\n'))
136
+ });
137
+ }
138
+
139
+ return sections;
140
+ }
141
+
142
+ function extractKeywords(title, content) {
143
+ const combined = `${title} ${content}`.toLowerCase();
144
+ const words = combined.match(/\b[a-z][a-z0-9]+\b/g) || [];
145
+
146
+ // Filter common words and keep meaningful ones
147
+ const stopwords = new Set(['the', 'and', 'for', 'with', 'this', 'that', 'from', 'will', 'have', 'has', 'are', 'was', 'were', 'been', 'being', 'would', 'could', 'should', 'can', 'may', 'might', 'must', 'shall', 'need', 'use', 'used', 'using', 'make', 'made', 'get', 'set', 'add', 'new', 'each', 'all', 'any', 'some', 'one', 'two']);
148
+
149
+ return [...new Set(words.filter(w => w.length > 3 && !stopwords.has(w)))];
150
+ }
151
+
152
+ function checkImplementationStatus(sections, config = {}) {
153
+ // Use configured directories or fallback to defaults
154
+ const searchDirs = config.includeDirs || [config.srcDir || (
155
+ existsSync(join(process.cwd(), 'src')) ? 'src' :
156
+ existsSync(join(process.cwd(), 'app')) ? 'app' :
157
+ existsSync(join(process.cwd(), 'lib')) ? 'lib' : null
158
+ )].filter(Boolean);
159
+
160
+ return sections.map(section => {
161
+ const { keywords } = section;
162
+ const matches = [];
163
+ let matchCount = 0;
164
+
165
+ if (searchDirs.length > 0 && keywords.length > 0) {
166
+ // Search codebase for keywords
167
+ for (const keyword of keywords.slice(0, 5)) {
168
+ for (const dir of searchDirs) {
169
+ try {
170
+ const result = execSync(`grep -ril "${keyword}" ${dir} 2>/dev/null || true`, {
171
+ encoding: 'utf-8',
172
+ maxBuffer: 1024 * 1024
173
+ }).trim();
174
+
175
+ if (result) {
176
+ const files = result.split('\n').filter(Boolean);
177
+ if (files.length > 0) {
178
+ matchCount++;
179
+ matches.push(...files.slice(0, 2).map(f => f.replace(process.cwd() + '/', '')));
180
+ }
181
+ }
182
+ } catch (e) {
183
+ // grep failed, continue
184
+ }
185
+ }
186
+ }
187
+ }
188
+
189
+ // Also check for matching file names
190
+ for (const dir of searchDirs) {
191
+ if (!existsSync(join(process.cwd(), dir))) continue;
192
+ try {
193
+ const files = readdirSync(join(process.cwd(), dir), { recursive: true });
194
+ const titleWords = section.title.toLowerCase().split(/\s+/).filter(w => w.length > 3);
195
+ titleWords.forEach(word => {
196
+ const matchingFiles = files.filter(f => f.toLowerCase().includes(word));
197
+ if (matchingFiles.length > 0) {
198
+ matchCount++;
199
+ matches.push(...matchingFiles.slice(0, 2).map(f => `${dir}/${f}`));
200
+ }
201
+ });
202
+ } catch (e) {
203
+ // Error reading dir
204
+ }
205
+ }
206
+
207
+ // Determine status based on matches
208
+ const uniqueMatches = [...new Set(matches)];
209
+ let status;
210
+ if (matchCount >= 2 || uniqueMatches.length >= 2) {
211
+ status = STATUS.DONE;
212
+ } else if (matchCount > 0 || uniqueMatches.length > 0) {
213
+ status = STATUS.PARTIAL;
214
+ } else {
215
+ status = STATUS.MISSING;
216
+ }
217
+
218
+ return {
219
+ title: section.title,
220
+ status,
221
+ matches: uniqueMatches.slice(0, 3)
222
+ };
223
+ });
224
+ }
225
+
226
+ function searchInCode(keyword, dir) {
227
+ try {
228
+ const files = readdirSync(join(process.cwd(), dir), { recursive: true });
229
+ return files.some(f => f.toLowerCase().includes(keyword));
230
+ } catch (e) {
231
+ return false;
232
+ }
233
+ }
@@ -0,0 +1,416 @@
1
+ /**
2
+ * ultra-dex doctor & config commands
3
+ * Diagnose setup issues and manage configuration
4
+ */
5
+
6
+ import chalk from 'chalk';
7
+ import inquirer from 'inquirer';
8
+ import fs from 'fs/promises';
9
+ import path from 'path';
10
+ import { execSync } from 'child_process';
11
+ import { checkConfiguredProviders } from '../providers/index.js';
12
+ import { icons, header, statusLine } from '../utils/status.js';
13
+ import { createSpinner } from '../utils/spinners.js';
14
+
15
+ // Default configuration
16
+ const DEFAULT_CONFIG = {
17
+ version: '2.4.0',
18
+ provider: 'claude',
19
+ model: null, // Use provider default
20
+ minScore: 70,
21
+ autoWatch: false,
22
+ mcpPort: 3001,
23
+ hooks: {
24
+ preCommit: true,
25
+ prePush: false,
26
+ },
27
+ };
28
+
29
+ async function loadConfig() {
30
+ // Check project-level config first
31
+ try {
32
+ const content = await fs.readFile(path.resolve(process.cwd(), '.ultra-dex.json'), 'utf8');
33
+ return { ...DEFAULT_CONFIG, ...JSON.parse(content), source: 'project' };
34
+ } catch { /* no project config */ }
35
+
36
+ // Check home directory config
37
+ try {
38
+ const homePath = path.join(process.env.HOME || process.env.USERPROFILE, '.ultra-dex.json');
39
+ const content = await fs.readFile(homePath, 'utf8');
40
+ return { ...DEFAULT_CONFIG, ...JSON.parse(content), source: 'global' };
41
+ } catch { /* no global config */ }
42
+
43
+ return { ...DEFAULT_CONFIG, source: 'default' };
44
+ }
45
+
46
+ async function saveConfig(config, global = false) {
47
+ const configPath = global
48
+ ? path.join(process.env.HOME || process.env.USERPROFILE, '.ultra-dex.json')
49
+ : path.resolve(process.cwd(), '.ultra-dex.json');
50
+
51
+ const { source, ...configData } = config;
52
+ await fs.writeFile(configPath, JSON.stringify(configData, null, 2));
53
+ return configPath;
54
+ }
55
+
56
+ export function registerDoctorCommand(program) {
57
+ program
58
+ .command('doctor')
59
+ .description('System Diagnostics - Check System Health')
60
+ .option('--fix', 'Attempt to fix issues automatically')
61
+ .action(async (options) => {
62
+ header('System Health Diagnostics');
63
+ console.log(chalk.gray(' Analyzing system components...\n'));
64
+
65
+ const checks = [];
66
+ let hasErrors = false;
67
+
68
+ // Check 1: Node.js version
69
+ const nodeSpinner = createSpinner('Scanning Node.js environment...');
70
+ nodeSpinner.start();
71
+ try {
72
+ const nodeVersion = process.version;
73
+ const major = parseInt(nodeVersion.slice(1).split('.')[0]);
74
+ if (major >= 18) {
75
+ nodeSpinner.succeed(`Node.js ${nodeVersion} āœ“`);
76
+ checks.push({ name: 'Node.js', status: 'ok', detail: nodeVersion });
77
+ } else {
78
+ nodeSpinner.warn(`Node.js ${nodeVersion} (recommend >= 18)`);
79
+ checks.push({ name: 'Node.js', status: 'warn', detail: `${nodeVersion} - upgrade recommended` });
80
+ }
81
+ } catch (e) {
82
+ nodeSpinner.fail('Node.js check failed');
83
+ checks.push({ name: 'Node.js', status: 'error', detail: e.message });
84
+ hasErrors = true;
85
+ }
86
+
87
+ // Check 2: Git
88
+ const gitSpinner = createSpinner('Checking Git repository...');
89
+ gitSpinner.start();
90
+ try {
91
+ const gitVersion = execSync('git --version', { encoding: 'utf8' }).trim();
92
+ gitSpinner.succeed(`${gitVersion} āœ“`);
93
+ checks.push({ name: 'Git', status: 'ok', detail: gitVersion });
94
+ } catch {
95
+ gitSpinner.fail('Git not found');
96
+ checks.push({ name: 'Git', status: 'error', detail: 'Not installed' });
97
+ hasErrors = true;
98
+ }
99
+
100
+ // Check 3: AI Providers
101
+ const providerSpinner = createSpinner('Locating AI Providers...');
102
+ providerSpinner.start();
103
+ const providers = checkConfiguredProviders();
104
+ const configuredProviders = providers.filter(p => p.configured);
105
+
106
+ if (configuredProviders.length > 0) {
107
+ providerSpinner.succeed(`Providers found: ${configuredProviders.map(p => p.name).join(', ')} āœ“`);
108
+ checks.push({ name: 'AI Providers', status: 'ok', detail: configuredProviders.map(p => p.name).join(', ') });
109
+ } else {
110
+ providerSpinner.warn('No AI Providers found');
111
+ checks.push({ name: 'AI Providers', status: 'warn', detail: 'Set ANTHROPIC_API_KEY, OPENAI_API_KEY, or GEMINI_API_KEY' });
112
+ }
113
+
114
+ // Check 4: Project structure
115
+ const structureSpinner = createSpinner('Verifying Project Structure...');
116
+ structureSpinner.start();
117
+ const requiredFiles = ['CONTEXT.md', 'IMPLEMENTATION-PLAN.md'];
118
+ const optionalFiles = ['CHECKLIST.md', 'QUICK-START.md', '.ultra/state.json'];
119
+ const foundRequired = [];
120
+ const foundOptional = [];
121
+
122
+ for (const file of requiredFiles) {
123
+ try {
124
+ await fs.access(path.resolve(process.cwd(), file));
125
+ foundRequired.push(file);
126
+ } catch { /* not found */ }
127
+ }
128
+
129
+ for (const file of optionalFiles) {
130
+ try {
131
+ await fs.access(path.resolve(process.cwd(), file));
132
+ foundOptional.push(file);
133
+ } catch { /* not found */ }
134
+ }
135
+
136
+ if (foundRequired.length === requiredFiles.length) {
137
+ structureSpinner.succeed(`Structure valid: ${foundRequired.length}/${requiredFiles.length} required artifacts āœ“`);
138
+ checks.push({ name: 'Project Structure', status: 'ok', detail: `${foundRequired.join(', ')}` });
139
+ } else if (foundRequired.length > 0) {
140
+ structureSpinner.warn(`Structure incomplete: ${foundRequired.length}/${requiredFiles.length} required artifacts`);
141
+ checks.push({ name: 'Project Structure', status: 'warn', detail: `Missing: ${requiredFiles.filter(f => !foundRequired.includes(f)).join(', ')}` });
142
+ } else {
143
+ structureSpinner.info('No Ultra-Dex project found');
144
+ checks.push({ name: 'Project Structure', status: 'info', detail: 'Run `ultra-dex init` to create a new project' });
145
+ }
146
+
147
+ // Check 5: Git hooks
148
+ const hooksSpinner = createSpinner('Checking Git hooks...');
149
+ hooksSpinner.start();
150
+ try {
151
+ const hookPath = path.resolve(process.cwd(), '.git/hooks/pre-commit');
152
+ const hookContent = await fs.readFile(hookPath, 'utf8');
153
+ if (hookContent.includes('ultra-dex')) {
154
+ hooksSpinner.succeed('Pre-commit active āœ“');
155
+ checks.push({ name: 'Git Hooks', status: 'ok', detail: 'Pre-commit active' });
156
+ } else {
157
+ hooksSpinner.info('Pre-commit active but not Ultra-Dex');
158
+ checks.push({ name: 'Git Hooks', status: 'info', detail: 'Custom hook present' });
159
+ }
160
+ } catch {
161
+ hooksSpinner.info('No pre-commit hook');
162
+ checks.push({ name: 'Git Hooks', status: 'info', detail: 'Run `ultra-dex pre-commit --install`' });
163
+ }
164
+
165
+ // Check 6: Configuration
166
+ const configSpinner = createSpinner('Reading Configuration...');
167
+ configSpinner.start();
168
+ const config = await loadConfig();
169
+ configSpinner.succeed(`Configuration loaded from: ${config.source}`);
170
+ checks.push({ name: 'Configuration', status: 'ok', detail: `Source: ${config.source}` });
171
+
172
+ // Check 7: MCP Server port
173
+ const portSpinner = createSpinner('Checking MCP Port...');
174
+ portSpinner.start();
175
+ try {
176
+ const net = await import('net');
177
+ const server = net.createServer();
178
+ await new Promise((resolve, reject) => {
179
+ server.once('error', reject);
180
+ server.once('listening', () => {
181
+ server.close();
182
+ resolve();
183
+ });
184
+ server.listen(config.mcpPort);
185
+ });
186
+ portSpinner.succeed(`Port ${config.mcpPort} open āœ“`);
187
+ checks.push({ name: 'MCP Port', status: 'ok', detail: `Port ${config.mcpPort} free` });
188
+ } catch {
189
+ portSpinner.warn(`Portal ${config.mcpPort} blocked`);
190
+ checks.push({ name: 'MCP Port', status: 'warn', detail: `Port ${config.mcpPort} busy - change with config` });
191
+ }
192
+
193
+ // Summary
194
+ header('Diagnostics Report');
195
+
196
+ const okCount = checks.filter(c => c.status === 'ok').length;
197
+ const warnCount = checks.filter(c => c.status === 'warn').length;
198
+ const errorCount = checks.filter(c => c.status === 'error').length;
199
+
200
+ checks.forEach(check => {
201
+ let icon;
202
+ if (check.status === 'ok') icon = icons.success;
203
+ else if (check.status === 'warn') icon = icons.warning;
204
+ else if (check.status === 'error') icon = icons.error;
205
+ else icon = icons.info;
206
+
207
+ statusLine(icon, `${check.name.padEnd(18)} ${chalk.gray(check.detail)}`);
208
+ });
209
+
210
+ console.log(chalk.gray(' ' + '─'.repeat(50)));
211
+ console.log(` ${chalk.green(okCount + ' passed')} ${chalk.yellow(warnCount + ' warnings')} ${chalk.red(errorCount + ' errors')}`);
212
+
213
+ if (hasErrors) {
214
+ console.log(chalk.red('\nāŒ System check failed. Fix issues above.\n'));
215
+ process.exit(1);
216
+ } else if (warnCount > 0) {
217
+ console.log(chalk.yellow('\nāš ļø System operational but has warnings.\n'));
218
+ } else {
219
+ console.log(chalk.green('\nāœ… All systems operational.\n'));
220
+ }
221
+
222
+ // Suggestions
223
+ if (configuredProviders.length === 0) {
224
+ console.log(chalk.cyan('šŸ’” To configure AI providers, set an API key:'));
225
+ console.log(chalk.gray(' export ANTHROPIC_API_KEY=sk-ant-...'));
226
+ console.log(chalk.gray(' export OPENAI_API_KEY=sk-...'));
227
+ console.log(chalk.gray(' export GEMINI_API_KEY=...\n'));
228
+ }
229
+
230
+ if (foundRequired.length === 0) {
231
+ console.log(chalk.cyan('šŸ’” To initialize a new project:'));
232
+ console.log(chalk.gray(' ultra-dex init\n'));
233
+ }
234
+ });
235
+ }
236
+
237
+ export function registerConfigCommand(program) {
238
+ program
239
+ .command('config')
240
+ .description('Manage Ultra-Dex configuration')
241
+ .option('--get <key>', 'Get a config value')
242
+ .option('--set <key=value>', 'Set a config value')
243
+ .option('--list', 'List all config values')
244
+ .option('--global', 'Use global config (~/.ultra-dex.json)')
245
+ .option('--init', 'Create a new config file')
246
+ .option('--mcp', 'Generate MCP config for Claude Desktop')
247
+ .action(async (options) => {
248
+ const config = await loadConfig();
249
+
250
+ if (options.mcp) {
251
+ // Generate MCP config for Claude Desktop
252
+ console.log(chalk.cyan('\nšŸ”Œ MCP Configuration for Claude Desktop\n'));
253
+
254
+ const mcpConfig = {
255
+ "ultra-dex": {
256
+ "command": "npx",
257
+ "args": ["ultra-dex", "serve", "--port", String(config.mcpPort)],
258
+ "env": {}
259
+ }
260
+ };
261
+
262
+ console.log(chalk.white('Add this to your Claude Desktop config:\n'));
263
+ console.log(chalk.gray('─'.repeat(50)));
264
+ console.log(JSON.stringify({ mcpServers: mcpConfig }, null, 2));
265
+ console.log(chalk.gray('─'.repeat(50)));
266
+
267
+ console.log(chalk.cyan('\nšŸ“ Config file locations:'));
268
+ console.log(chalk.gray(' macOS: ~/Library/Application Support/Claude/claude_desktop_config.json'));
269
+ console.log(chalk.gray(' Windows: %APPDATA%\Claude\claude_desktop_config.json'));
270
+ console.log(chalk.gray(' Linux: ~/.config/Claude/claude_desktop_config.json\n'));
271
+ return;
272
+ }
273
+
274
+ if (options.init) {
275
+ const configPath = await saveConfig(DEFAULT_CONFIG, options.global);
276
+ console.log(chalk.green(`āœ… Config created: ${configPath}`));
277
+ return;
278
+ }
279
+
280
+ if (options.list) {
281
+ console.log(chalk.cyan('\nāš™ļø Ultra-Dex Configuration\n'));
282
+ console.log(chalk.gray(`Source: ${config.source}`));
283
+ console.log(chalk.gray('─'.repeat(40)));
284
+
285
+ const { source, ...displayConfig } = config;
286
+ Object.entries(displayConfig).forEach(([key, value]) => {
287
+ const valueStr = typeof value === 'object' ? JSON.stringify(value) : String(value);
288
+ console.log(` ${chalk.cyan(key.padEnd(15))} ${valueStr}`);
289
+ });
290
+ console.log('');
291
+ return;
292
+ }
293
+
294
+ if (options.get) {
295
+ const value = config[options.get];
296
+ if (value !== undefined) {
297
+ console.log(typeof value === 'object' ? JSON.stringify(value, null, 2) : value);
298
+ } else {
299
+ console.log(chalk.yellow(`Key not found: ${options.get}`));
300
+ }
301
+ return;
302
+ }
303
+
304
+ if (options.set) {
305
+ const [key, ...valueParts] = options.set.split('=');
306
+ const value = valueParts.join('=');
307
+
308
+ // Parse value
309
+ let parsedValue;
310
+ try {
311
+ parsedValue = JSON.parse(value);
312
+ } catch {
313
+ parsedValue = value;
314
+ }
315
+
316
+ config[key] = parsedValue;
317
+ const configPath = await saveConfig(config, options.global);
318
+ console.log(chalk.green(`āœ… Set ${key}=${value} in ${configPath}`));
319
+ return;
320
+ }
321
+
322
+ // Interactive mode
323
+ console.log(chalk.cyan('\nāš™ļø Ultra-Dex Configuration\n'));
324
+
325
+ const { action } = await inquirer.prompt([
326
+ {
327
+ type: 'list',
328
+ name: 'action',
329
+ message: 'What would you like to do?',
330
+ choices: [
331
+ { name: 'View current config', value: 'view' },
332
+ { name: 'Set default AI provider', value: 'provider' },
333
+ { name: 'Set minimum alignment score', value: 'minScore' },
334
+ { name: 'Set MCP server port', value: 'mcpPort' },
335
+ { name: 'Generate MCP config for Claude', value: 'mcp' },
336
+ { name: 'Create new config file', value: 'init' },
337
+ ]
338
+ }
339
+ ]);
340
+
341
+ switch (action) {
342
+ case 'view':
343
+ console.log(chalk.gray('\n' + JSON.stringify(config, null, 2) + '\n'));
344
+ break;
345
+
346
+ case 'provider':
347
+ const { provider } = await inquirer.prompt([
348
+ {
349
+ type: 'list',
350
+ name: 'provider',
351
+ message: 'Select default AI provider:',
352
+ choices: ['claude', 'openai', 'gemini'],
353
+ default: config.provider
354
+ }
355
+ ]);
356
+ config.provider = provider;
357
+ await saveConfig(config, options.global);
358
+ console.log(chalk.green(`\nāœ… Default provider set to: ${provider}\n`));
359
+ break;
360
+
361
+ case 'minScore':
362
+ const { minScore } = await inquirer.prompt([
363
+ {
364
+ type: 'number',
365
+ name: 'minScore',
366
+ message: 'Minimum alignment score (0-100):',
367
+ default: config.minScore,
368
+ validate: n => n >= 0 && n <= 100 || 'Must be 0-100'
369
+ }
370
+ ]);
371
+ config.minScore = minScore;
372
+ await saveConfig(config, options.global);
373
+ console.log(chalk.green(`\nāœ… Minimum score set to: ${minScore}\n`));
374
+ break;
375
+
376
+ case 'mcpPort':
377
+ const { mcpPort } = await inquirer.prompt([
378
+ {
379
+ type: 'number',
380
+ name: 'mcpPort',
381
+ message: 'MCP server port:',
382
+ default: config.mcpPort,
383
+ validate: n => n > 0 && n < 65536 || 'Invalid port'
384
+ }
385
+ ]);
386
+ config.mcpPort = mcpPort;
387
+ await saveConfig(config, options.global);
388
+ console.log(chalk.green(`\nāœ… MCP port set to: ${mcpPort}\n`));
389
+ break;
390
+
391
+ case 'mcp':
392
+ // Call the MCP generation
393
+ options.mcp = true;
394
+ await program.commands.find(c => c.name() === 'config').action(options);
395
+ break;
396
+
397
+ case 'init':
398
+ const { scope } = await inquirer.prompt([
399
+ {
400
+ type: 'list',
401
+ name: 'scope',
402
+ message: 'Create config in:',
403
+ choices: [
404
+ { name: 'This project (.ultra-dex.json)', value: 'project' },
405
+ { name: 'Global (~/.ultra-dex.json)', value: 'global' },
406
+ ]
407
+ }
408
+ ]);
409
+ const configPath = await saveConfig(DEFAULT_CONFIG, scope === 'global');
410
+ console.log(chalk.green(`\nāœ… Config created: ${configPath}\n`));
411
+ break;
412
+ }
413
+ });
414
+ }
415
+
416
+ export default { registerDoctorCommand, registerConfigCommand };