ultra-dex 2.2.1 → 3.1.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 (54) hide show
  1. package/README.md +84 -128
  2. package/assets/agents/00-AGENT_INDEX.md +1 -1
  3. package/assets/docs/LAUNCH-POSTS.md +1 -1
  4. package/assets/docs/QUICK-REFERENCE.md +9 -4
  5. package/assets/docs/VISION-V2.md +1 -1
  6. package/assets/hooks/pre-commit +98 -0
  7. package/assets/saas-plan/04-Imp-Template.md +1 -1
  8. package/bin/ultra-dex.js +95 -4
  9. package/lib/commands/advanced.js +471 -0
  10. package/lib/commands/agent-builder.js +226 -0
  11. package/lib/commands/agents.js +99 -42
  12. package/lib/commands/auto-implement.js +68 -0
  13. package/lib/commands/build.js +73 -187
  14. package/lib/commands/ci-monitor.js +84 -0
  15. package/lib/commands/config.js +207 -0
  16. package/lib/commands/dashboard.js +770 -0
  17. package/lib/commands/diff.js +233 -0
  18. package/lib/commands/doctor.js +397 -0
  19. package/lib/commands/export.js +408 -0
  20. package/lib/commands/fix.js +96 -0
  21. package/lib/commands/generate.js +96 -72
  22. package/lib/commands/hooks.js +251 -76
  23. package/lib/commands/init.js +53 -1
  24. package/lib/commands/memory.js +80 -0
  25. package/lib/commands/plan.js +82 -0
  26. package/lib/commands/review.js +34 -5
  27. package/lib/commands/run.js +233 -0
  28. package/lib/commands/serve.js +177 -146
  29. package/lib/commands/state.js +354 -0
  30. package/lib/commands/swarm.js +284 -0
  31. package/lib/commands/sync.js +82 -23
  32. package/lib/commands/team.js +275 -0
  33. package/lib/commands/upgrade.js +190 -0
  34. package/lib/commands/validate.js +34 -0
  35. package/lib/commands/verify.js +81 -0
  36. package/lib/commands/watch.js +79 -0
  37. package/lib/mcp/graph.js +92 -0
  38. package/lib/mcp/memory.js +95 -0
  39. package/lib/mcp/resources.js +152 -0
  40. package/lib/mcp/server.js +34 -0
  41. package/lib/mcp/tools.js +481 -0
  42. package/lib/mcp/websocket.js +117 -0
  43. package/lib/providers/index.js +49 -4
  44. package/lib/providers/ollama.js +136 -0
  45. package/lib/providers/router.js +63 -0
  46. package/lib/quality/scanner.js +128 -0
  47. package/lib/swarm/coordinator.js +97 -0
  48. package/lib/swarm/index.js +598 -0
  49. package/lib/swarm/protocol.js +677 -0
  50. package/lib/swarm/tiers.js +485 -0
  51. package/lib/templates/custom-agent.md +10 -0
  52. package/lib/utils/files.js +14 -0
  53. package/lib/utils/graph.js +108 -0
  54. package/package.json +22 -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,397 @@
1
+ /**
2
+ * ultra-dex doctor & config commands
3
+ * Diagnose setup issues and manage configuration
4
+ */
5
+
6
+ import chalk from 'chalk';
7
+ import ora from 'ora';
8
+ import inquirer from 'inquirer';
9
+ import fs from 'fs/promises';
10
+ import path from 'path';
11
+ import { execSync } from 'child_process';
12
+ import { checkConfiguredProviders } from '../providers/index.js';
13
+
14
+ // Default configuration
15
+ const DEFAULT_CONFIG = {
16
+ version: '2.4.0',
17
+ provider: 'claude',
18
+ model: null, // Use provider default
19
+ minScore: 70,
20
+ autoWatch: false,
21
+ mcpPort: 3001,
22
+ hooks: {
23
+ preCommit: true,
24
+ prePush: false,
25
+ },
26
+ };
27
+
28
+ async function loadConfig() {
29
+ // Check project-level config first
30
+ try {
31
+ const content = await fs.readFile(path.resolve(process.cwd(), '.ultra-dex.json'), 'utf8');
32
+ return { ...DEFAULT_CONFIG, ...JSON.parse(content), source: 'project' };
33
+ } catch { /* no project config */ }
34
+
35
+ // Check home directory config
36
+ try {
37
+ const homePath = path.join(process.env.HOME || process.env.USERPROFILE, '.ultra-dex.json');
38
+ const content = await fs.readFile(homePath, 'utf8');
39
+ return { ...DEFAULT_CONFIG, ...JSON.parse(content), source: 'global' };
40
+ } catch { /* no global config */ }
41
+
42
+ return { ...DEFAULT_CONFIG, source: 'default' };
43
+ }
44
+
45
+ async function saveConfig(config, global = false) {
46
+ const configPath = global
47
+ ? path.join(process.env.HOME || process.env.USERPROFILE, '.ultra-dex.json')
48
+ : path.resolve(process.cwd(), '.ultra-dex.json');
49
+
50
+ const { source, ...configData } = config;
51
+ await fs.writeFile(configPath, JSON.stringify(configData, null, 2));
52
+ return configPath;
53
+ }
54
+
55
+ export function registerDoctorCommand(program) {
56
+ program
57
+ .command('doctor')
58
+ .description('Diagnose Ultra-Dex setup and configuration')
59
+ .option('--fix', 'Attempt to fix issues automatically')
60
+ .action(async (options) => {
61
+ console.log(chalk.cyan('\n🩺 Ultra-Dex Doctor\n'));
62
+ console.log(chalk.gray('Checking your setup...\n'));
63
+
64
+ const checks = [];
65
+ let hasErrors = false;
66
+
67
+ // Check 1: Node.js version
68
+ const nodeSpinner = ora('Checking Node.js version...').start();
69
+ try {
70
+ const nodeVersion = process.version;
71
+ const major = parseInt(nodeVersion.slice(1).split('.')[0]);
72
+ if (major >= 18) {
73
+ nodeSpinner.succeed(`Node.js ${nodeVersion} āœ“`);
74
+ checks.push({ name: 'Node.js', status: 'ok', detail: nodeVersion });
75
+ } else {
76
+ nodeSpinner.warn(`Node.js ${nodeVersion} (recommend >= 18)`);
77
+ checks.push({ name: 'Node.js', status: 'warn', detail: `${nodeVersion} - upgrade recommended` });
78
+ }
79
+ } catch (e) {
80
+ nodeSpinner.fail('Node.js check failed');
81
+ checks.push({ name: 'Node.js', status: 'error', detail: e.message });
82
+ hasErrors = true;
83
+ }
84
+
85
+ // Check 2: Git
86
+ const gitSpinner = ora('Checking Git...').start();
87
+ try {
88
+ const gitVersion = execSync('git --version', { encoding: 'utf8' }).trim();
89
+ gitSpinner.succeed(`${gitVersion} āœ“`);
90
+ checks.push({ name: 'Git', status: 'ok', detail: gitVersion });
91
+ } catch {
92
+ gitSpinner.fail('Git not found');
93
+ checks.push({ name: 'Git', status: 'error', detail: 'Not installed' });
94
+ hasErrors = true;
95
+ }
96
+
97
+ // Check 3: AI Providers
98
+ const providerSpinner = ora('Checking AI providers...').start();
99
+ const providers = checkConfiguredProviders();
100
+ const configuredProviders = providers.filter(p => p.configured);
101
+
102
+ if (configuredProviders.length > 0) {
103
+ providerSpinner.succeed(`AI providers: ${configuredProviders.map(p => p.name).join(', ')} āœ“`);
104
+ checks.push({ name: 'AI Providers', status: 'ok', detail: configuredProviders.map(p => p.name).join(', ') });
105
+ } else {
106
+ providerSpinner.warn('No AI providers configured');
107
+ checks.push({ name: 'AI Providers', status: 'warn', detail: 'Set ANTHROPIC_API_KEY, OPENAI_API_KEY, or GEMINI_API_KEY' });
108
+ }
109
+
110
+ // Check 4: Project structure
111
+ const structureSpinner = ora('Checking project structure...').start();
112
+ const requiredFiles = ['CONTEXT.md', 'IMPLEMENTATION-PLAN.md'];
113
+ const optionalFiles = ['CHECKLIST.md', 'QUICK-START.md', '.ultra/state.json'];
114
+ const foundRequired = [];
115
+ const foundOptional = [];
116
+
117
+ for (const file of requiredFiles) {
118
+ try {
119
+ await fs.access(path.resolve(process.cwd(), file));
120
+ foundRequired.push(file);
121
+ } catch { /* not found */ }
122
+ }
123
+
124
+ for (const file of optionalFiles) {
125
+ try {
126
+ await fs.access(path.resolve(process.cwd(), file));
127
+ foundOptional.push(file);
128
+ } catch { /* not found */ }
129
+ }
130
+
131
+ if (foundRequired.length === requiredFiles.length) {
132
+ structureSpinner.succeed(`Project structure: ${foundRequired.length}/${requiredFiles.length} required files āœ“`);
133
+ checks.push({ name: 'Project Structure', status: 'ok', detail: `${foundRequired.join(', ')}` });
134
+ } else if (foundRequired.length > 0) {
135
+ structureSpinner.warn(`Project structure: ${foundRequired.length}/${requiredFiles.length} required files`);
136
+ checks.push({ name: 'Project Structure', status: 'warn', detail: `Missing: ${requiredFiles.filter(f => !foundRequired.includes(f)).join(', ')}` });
137
+ } else {
138
+ structureSpinner.info('No Ultra-Dex project found');
139
+ checks.push({ name: 'Project Structure', status: 'info', detail: 'Run `ultra-dex init` to create a project' });
140
+ }
141
+
142
+ // Check 5: Git hooks
143
+ const hooksSpinner = ora('Checking git hooks...').start();
144
+ try {
145
+ const hookPath = path.resolve(process.cwd(), '.git/hooks/pre-commit');
146
+ const hookContent = await fs.readFile(hookPath, 'utf8');
147
+ if (hookContent.includes('ultra-dex')) {
148
+ hooksSpinner.succeed('Pre-commit hook installed āœ“');
149
+ checks.push({ name: 'Git Hooks', status: 'ok', detail: 'Pre-commit active' });
150
+ } else {
151
+ hooksSpinner.info('Pre-commit hook exists but not Ultra-Dex');
152
+ checks.push({ name: 'Git Hooks', status: 'info', detail: 'Custom hook present' });
153
+ }
154
+ } catch {
155
+ hooksSpinner.info('No pre-commit hook');
156
+ checks.push({ name: 'Git Hooks', status: 'info', detail: 'Run `ultra-dex pre-commit --install`' });
157
+ }
158
+
159
+ // Check 6: Configuration
160
+ const configSpinner = ora('Checking configuration...').start();
161
+ const config = await loadConfig();
162
+ configSpinner.succeed(`Config loaded from: ${config.source}`);
163
+ checks.push({ name: 'Configuration', status: 'ok', detail: `Source: ${config.source}` });
164
+
165
+ // Check 7: MCP Server port
166
+ const portSpinner = ora('Checking MCP server port...').start();
167
+ try {
168
+ const net = await import('net');
169
+ const server = net.createServer();
170
+ await new Promise((resolve, reject) => {
171
+ server.once('error', reject);
172
+ server.once('listening', () => {
173
+ server.close();
174
+ resolve();
175
+ });
176
+ server.listen(config.mcpPort);
177
+ });
178
+ portSpinner.succeed(`Port ${config.mcpPort} available āœ“`);
179
+ checks.push({ name: 'MCP Port', status: 'ok', detail: `Port ${config.mcpPort} free` });
180
+ } catch {
181
+ portSpinner.warn(`Port ${config.mcpPort} in use`);
182
+ checks.push({ name: 'MCP Port', status: 'warn', detail: `Port ${config.mcpPort} busy - change with config` });
183
+ }
184
+
185
+ // Summary
186
+ console.log(chalk.bold('\nšŸ“‹ Summary\n'));
187
+ console.log(chalk.gray('─'.repeat(50)));
188
+
189
+ const okCount = checks.filter(c => c.status === 'ok').length;
190
+ const warnCount = checks.filter(c => c.status === 'warn').length;
191
+ const errorCount = checks.filter(c => c.status === 'error').length;
192
+
193
+ checks.forEach(check => {
194
+ const icon = check.status === 'ok' ? chalk.green('āœ“') :
195
+ check.status === 'warn' ? chalk.yellow('⚠') :
196
+ check.status === 'error' ? chalk.red('āœ—') :
197
+ chalk.blue('ℹ');
198
+ console.log(` ${icon} ${check.name.padEnd(18)} ${chalk.gray(check.detail)}`);
199
+ });
200
+
201
+ console.log(chalk.gray('─'.repeat(50)));
202
+ console.log(` ${chalk.green(okCount + ' passed')} ${chalk.yellow(warnCount + ' warnings')} ${chalk.red(errorCount + ' errors')}`);
203
+
204
+ if (hasErrors) {
205
+ console.log(chalk.red('\nāŒ Some checks failed. Fix issues above.\n'));
206
+ process.exit(1);
207
+ } else if (warnCount > 0) {
208
+ console.log(chalk.yellow('\nāš ļø Some warnings. Setup works but could be improved.\n'));
209
+ } else {
210
+ console.log(chalk.green('\nāœ… All checks passed! Ultra-Dex is ready.\n'));
211
+ }
212
+
213
+ // Suggestions
214
+ if (configuredProviders.length === 0) {
215
+ console.log(chalk.cyan('šŸ’” To enable AI features, set an API key:'));
216
+ console.log(chalk.gray(' export ANTHROPIC_API_KEY=sk-ant-...'));
217
+ console.log(chalk.gray(' export OPENAI_API_KEY=sk-...'));
218
+ console.log(chalk.gray(' export GEMINI_API_KEY=...\n'));
219
+ }
220
+
221
+ if (foundRequired.length === 0) {
222
+ console.log(chalk.cyan('šŸ’” To start a new project:'));
223
+ console.log(chalk.gray(' ultra-dex init\n'));
224
+ }
225
+ });
226
+ }
227
+
228
+ export function registerConfigCommand(program) {
229
+ program
230
+ .command('config')
231
+ .description('Manage Ultra-Dex configuration')
232
+ .option('--get <key>', 'Get a config value')
233
+ .option('--set <key=value>', 'Set a config value')
234
+ .option('--list', 'List all config values')
235
+ .option('--global', 'Use global config (~/.ultra-dex.json)')
236
+ .option('--init', 'Create a new config file')
237
+ .option('--mcp', 'Generate MCP config for Claude Desktop')
238
+ .action(async (options) => {
239
+ const config = await loadConfig();
240
+
241
+ if (options.mcp) {
242
+ // Generate MCP config for Claude Desktop
243
+ console.log(chalk.cyan('\nšŸ”Œ MCP Configuration for Claude Desktop\n'));
244
+
245
+ const mcpConfig = {
246
+ "ultra-dex": {
247
+ "command": "npx",
248
+ "args": ["ultra-dex", "serve", "--port", String(config.mcpPort)],
249
+ "env": {}
250
+ }
251
+ };
252
+
253
+ console.log(chalk.white('Add this to your Claude Desktop config:\n'));
254
+ console.log(chalk.gray('─'.repeat(50)));
255
+ console.log(JSON.stringify({ mcpServers: mcpConfig }, null, 2));
256
+ console.log(chalk.gray('─'.repeat(50)));
257
+
258
+ console.log(chalk.cyan('\nšŸ“ Config file locations:'));
259
+ console.log(chalk.gray(' macOS: ~/Library/Application Support/Claude/claude_desktop_config.json'));
260
+ console.log(chalk.gray(' Windows: %APPDATA%\\Claude\\claude_desktop_config.json'));
261
+ console.log(chalk.gray(' Linux: ~/.config/Claude/claude_desktop_config.json\n'));
262
+ return;
263
+ }
264
+
265
+ if (options.init) {
266
+ const configPath = await saveConfig(DEFAULT_CONFIG, options.global);
267
+ console.log(chalk.green(`āœ… Config created: ${configPath}`));
268
+ return;
269
+ }
270
+
271
+ if (options.list) {
272
+ console.log(chalk.cyan('\nāš™ļø Ultra-Dex Configuration\n'));
273
+ console.log(chalk.gray(`Source: ${config.source}`));
274
+ console.log(chalk.gray('─'.repeat(40)));
275
+
276
+ const { source, ...displayConfig } = config;
277
+ Object.entries(displayConfig).forEach(([key, value]) => {
278
+ const valueStr = typeof value === 'object' ? JSON.stringify(value) : String(value);
279
+ console.log(` ${chalk.cyan(key.padEnd(15))} ${valueStr}`);
280
+ });
281
+ console.log('');
282
+ return;
283
+ }
284
+
285
+ if (options.get) {
286
+ const value = config[options.get];
287
+ if (value !== undefined) {
288
+ console.log(typeof value === 'object' ? JSON.stringify(value, null, 2) : value);
289
+ } else {
290
+ console.log(chalk.yellow(`Key not found: ${options.get}`));
291
+ }
292
+ return;
293
+ }
294
+
295
+ if (options.set) {
296
+ const [key, ...valueParts] = options.set.split('=');
297
+ const value = valueParts.join('=');
298
+
299
+ // Parse value
300
+ let parsedValue;
301
+ try {
302
+ parsedValue = JSON.parse(value);
303
+ } catch {
304
+ parsedValue = value;
305
+ }
306
+
307
+ config[key] = parsedValue;
308
+ const configPath = await saveConfig(config, options.global);
309
+ console.log(chalk.green(`āœ… Set ${key}=${value} in ${configPath}`));
310
+ return;
311
+ }
312
+
313
+ // Interactive mode
314
+ console.log(chalk.cyan('\nāš™ļø Ultra-Dex Configuration\n'));
315
+
316
+ const { action } = await inquirer.prompt([{
317
+ type: 'list',
318
+ name: 'action',
319
+ message: 'What would you like to do?',
320
+ choices: [
321
+ { name: 'View current config', value: 'view' },
322
+ { name: 'Set default AI provider', value: 'provider' },
323
+ { name: 'Set minimum alignment score', value: 'minScore' },
324
+ { name: 'Set MCP server port', value: 'mcpPort' },
325
+ { name: 'Generate MCP config for Claude', value: 'mcp' },
326
+ { name: 'Create new config file', value: 'init' },
327
+ ]
328
+ }]);
329
+
330
+ switch (action) {
331
+ case 'view':
332
+ console.log(chalk.gray('\n' + JSON.stringify(config, null, 2) + '\n'));
333
+ break;
334
+
335
+ case 'provider':
336
+ const { provider } = await inquirer.prompt([{
337
+ type: 'list',
338
+ name: 'provider',
339
+ message: 'Select default AI provider:',
340
+ choices: ['claude', 'openai', 'gemini'],
341
+ default: config.provider
342
+ }]);
343
+ config.provider = provider;
344
+ await saveConfig(config, options.global);
345
+ console.log(chalk.green(`\nāœ… Default provider set to: ${provider}\n`));
346
+ break;
347
+
348
+ case 'minScore':
349
+ const { minScore } = await inquirer.prompt([{
350
+ type: 'number',
351
+ name: 'minScore',
352
+ message: 'Minimum alignment score (0-100):',
353
+ default: config.minScore,
354
+ validate: n => n >= 0 && n <= 100 || 'Must be 0-100'
355
+ }]);
356
+ config.minScore = minScore;
357
+ await saveConfig(config, options.global);
358
+ console.log(chalk.green(`\nāœ… Minimum score set to: ${minScore}\n`));
359
+ break;
360
+
361
+ case 'mcpPort':
362
+ const { mcpPort } = await inquirer.prompt([{
363
+ type: 'number',
364
+ name: 'mcpPort',
365
+ message: 'MCP server port:',
366
+ default: config.mcpPort,
367
+ validate: n => n > 0 && n < 65536 || 'Invalid port'
368
+ }]);
369
+ config.mcpPort = mcpPort;
370
+ await saveConfig(config, options.global);
371
+ console.log(chalk.green(`\nāœ… MCP port set to: ${mcpPort}\n`));
372
+ break;
373
+
374
+ case 'mcp':
375
+ // Call the MCP generation
376
+ options.mcp = true;
377
+ await program.commands.find(c => c.name() === 'config').action(options);
378
+ break;
379
+
380
+ case 'init':
381
+ const { scope } = await inquirer.prompt([{
382
+ type: 'list',
383
+ name: 'scope',
384
+ message: 'Create config in:',
385
+ choices: [
386
+ { name: 'This project (.ultra-dex.json)', value: 'project' },
387
+ { name: 'Global (~/.ultra-dex.json)', value: 'global' },
388
+ ]
389
+ }]);
390
+ const configPath = await saveConfig(DEFAULT_CONFIG, scope === 'global');
391
+ console.log(chalk.green(`\nāœ… Config created: ${configPath}\n`));
392
+ break;
393
+ }
394
+ });
395
+ }
396
+
397
+ export default { registerDoctorCommand, registerConfigCommand };