@rahul05ranjan/dhruv-cli 1.3.0 → 1.4.6

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 (103) hide show
  1. package/.github/ENTERPRISE.md +275 -0
  2. package/.github/ISSUE_TEMPLATE/bug_report.md +45 -17
  3. package/.github/ISSUE_TEMPLATE/documentation_issue.md +61 -0
  4. package/.github/ISSUE_TEMPLATE/feature_request.md +61 -9
  5. package/.github/ISSUE_TEMPLATE/security_vulnerability.md +74 -0
  6. package/.github/dependabot.yml +42 -2
  7. package/.github/pull_request_template.md +13 -0
  8. package/.github/workflows/build-publish.yml +154 -0
  9. package/.github/workflows/ci.yml +251 -18
  10. package/.github/workflows/contribution.yml +169 -27
  11. package/.github/workflows/dependabot-auto-merge.yml +61 -2
  12. package/.github/workflows/deploy.yml +336 -0
  13. package/.github/workflows/monitoring.yml +270 -0
  14. package/.github/workflows/release.yml +229 -0
  15. package/.github/workflows/security.yml +198 -0
  16. package/.releaserc.json +50 -0
  17. package/AGENTS.md +13 -0
  18. package/CHANGELOG.md +8 -0
  19. package/PUBLISHING_FIX.md +92 -0
  20. package/__tests__/core.test.ts +318 -0
  21. package/__tests__/setup.ts +61 -0
  22. package/__tests__/workflows.test.ts +95 -0
  23. package/dist/commands/explain.js +13 -44
  24. package/dist/commands/fix.js +13 -38
  25. package/dist/commands/generate.js +45 -54
  26. package/dist/commands/health.d.ts +1 -0
  27. package/dist/commands/health.js +376 -0
  28. package/dist/commands/init.js +47 -42
  29. package/dist/commands/menu.js +90 -2
  30. package/dist/commands/metrics.d.ts +1 -0
  31. package/dist/commands/metrics.js +51 -0
  32. package/dist/commands/optimize.js +42 -34
  33. package/dist/commands/review.js +52 -48
  34. package/dist/commands/security-check.js +52 -42
  35. package/dist/commands/status.d.ts +1 -0
  36. package/dist/commands/status.js +45 -0
  37. package/dist/commands/suggest.js +13 -39
  38. package/dist/config/config.js +30 -2
  39. package/dist/core/ai.d.ts +77 -2
  40. package/dist/core/ai.js +207 -30
  41. package/dist/core/command-runner.d.ts +17 -0
  42. package/dist/core/command-runner.js +78 -0
  43. package/dist/core/logger.d.ts +40 -0
  44. package/dist/core/logger.js +138 -0
  45. package/dist/core/metrics.d.ts +34 -0
  46. package/dist/core/metrics.js +206 -0
  47. package/dist/core/prompts.d.ts +1 -0
  48. package/dist/core/prompts.js +121 -0
  49. package/dist/core/security.d.ts +34 -0
  50. package/dist/core/security.js +197 -0
  51. package/dist/index.js +43 -3
  52. package/dist/utils/ux.d.ts +3 -0
  53. package/dist/utils/ux.js +15 -0
  54. package/docs/agents/domain.md +51 -0
  55. package/docs/agents/issue-tracker.md +45 -0
  56. package/docs/agents/triage-labels.md +15 -0
  57. package/docs/api/.nojekyll +1 -0
  58. package/docs/api/assets/hierarchy.js +1 -0
  59. package/docs/api/assets/highlight.css +71 -0
  60. package/docs/api/assets/icons.js +18 -0
  61. package/docs/api/assets/icons.svg +1 -0
  62. package/docs/api/assets/main.js +60 -0
  63. package/docs/api/assets/navigation.js +1 -0
  64. package/docs/api/assets/search.js +1 -0
  65. package/docs/api/assets/style.css +1633 -0
  66. package/docs/api/hierarchy.html +1 -0
  67. package/docs/api/index.html +39 -0
  68. package/docs/api/modules.html +1 -0
  69. package/eslint.config.js +170 -0
  70. package/jest.config.json +37 -0
  71. package/lighthouserc.json +22 -0
  72. package/logs/.8a99b6cf655346317fdbf29f4fffcf91131432f3-audit.json +15 -0
  73. package/logs/.eee104bf8fff5ecd38a6a2842df260de6470a7c3-audit.json +15 -0
  74. package/package.json +62 -8
  75. package/src/commands/explain.ts +13 -42
  76. package/src/commands/fix.ts +13 -31
  77. package/src/commands/generate.ts +47 -46
  78. package/src/commands/health.ts +440 -0
  79. package/src/commands/init.ts +48 -42
  80. package/src/commands/menu.ts +86 -2
  81. package/src/commands/metrics.ts +65 -0
  82. package/src/commands/optimize.ts +40 -28
  83. package/src/commands/review.ts +54 -40
  84. package/src/commands/security-check.ts +54 -34
  85. package/src/commands/status.ts +47 -0
  86. package/src/commands/suggest.ts +13 -32
  87. package/src/config/config.ts +35 -2
  88. package/src/core/ai.ts +237 -26
  89. package/src/core/command-runner.ts +105 -0
  90. package/src/core/logger.ts +194 -0
  91. package/src/core/metrics.ts +232 -0
  92. package/src/core/prompts.ts +128 -0
  93. package/src/core/security.ts +243 -0
  94. package/src/index.ts +50 -3
  95. package/src/utils/ux.ts +18 -0
  96. package/test-suite.sh +147 -0
  97. package/tsconfig.json +3 -2
  98. package/typedoc.json +44 -0
  99. package/types/global.d.ts +13 -0
  100. package/validate-workflows.sh +270 -0
  101. package/.eslintignore +0 -1
  102. package/.eslintrc.cjs +0 -43
  103. package/src/core/ai.test.js +0 -40
@@ -1,5 +1,15 @@
1
1
  import inquirer from 'inquirer';
2
2
  import { themed } from '../utils/ux.js';
3
+ import { explain } from './explain.js';
4
+ import { suggest } from './suggest.js';
5
+ import { fix } from './fix.js';
6
+ import { review } from './review.js';
7
+ import { optimize } from './optimize.js';
8
+ import { securityCheck } from './security-check.js';
9
+ import { generate } from './generate.js';
10
+ import { init } from './init.js';
11
+ import { detectProjectType } from '../utils/projectType.js';
12
+ import chalk from 'chalk';
3
13
  const commands = [
4
14
  { name: 'Explain', value: 'explain' },
5
15
  { name: 'Suggest', value: 'suggest' },
@@ -27,7 +37,85 @@ export async function menu() {
27
37
  running = false;
28
38
  break;
29
39
  }
30
- // For demo, just print the command. In real use, you would call the command handler.
31
- console.log(themed(`You selected: ${cmd}`, 'accent'));
40
+ try {
41
+ switch (cmd) {
42
+ case 'explain': {
43
+ const { query } = await inquirer.prompt([
44
+ { type: 'input', name: 'query', message: 'What would you like me to explain?' }
45
+ ]);
46
+ if (query)
47
+ await explain(query);
48
+ break;
49
+ }
50
+ case 'suggest': {
51
+ const { query } = await inquirer.prompt([
52
+ { type: 'input', name: 'query', message: 'What would you like suggestions for?' }
53
+ ]);
54
+ if (query)
55
+ await suggest(query);
56
+ break;
57
+ }
58
+ case 'fix': {
59
+ const { query } = await inquirer.prompt([
60
+ { type: 'input', name: 'query', message: 'Describe the issue you need help fixing:' }
61
+ ]);
62
+ if (query)
63
+ await fix(query);
64
+ break;
65
+ }
66
+ case 'review': {
67
+ const { fileOrDir } = await inquirer.prompt([
68
+ { type: 'input', name: 'fileOrDir', message: 'Enter file or directory path to review:' }
69
+ ]);
70
+ if (fileOrDir)
71
+ await review(fileOrDir);
72
+ break;
73
+ }
74
+ case 'optimize': {
75
+ const { file } = await inquirer.prompt([
76
+ { type: 'input', name: 'file', message: 'Enter file path to optimize:' }
77
+ ]);
78
+ if (file)
79
+ await optimize(file);
80
+ break;
81
+ }
82
+ case 'security-check': {
83
+ const { fileOrDir } = await inquirer.prompt([
84
+ { type: 'input', name: 'fileOrDir', message: 'Enter file or directory path to check (or press enter for current directory):', default: '.' }
85
+ ]);
86
+ await securityCheck(fileOrDir);
87
+ break;
88
+ }
89
+ case 'generate': {
90
+ const answers = await inquirer.prompt([
91
+ {
92
+ type: 'list',
93
+ name: 'type',
94
+ message: 'What would you like to generate?',
95
+ choices: ['tests', 'documentation', 'docs', 'component']
96
+ },
97
+ { type: 'input', name: 'target', message: 'Enter target file path:' }
98
+ ]);
99
+ if (answers.target)
100
+ await generate(answers.type, answers.target);
101
+ break;
102
+ }
103
+ case 'init': {
104
+ await init();
105
+ break;
106
+ }
107
+ case 'project-type': {
108
+ const type = detectProjectType();
109
+ console.log(chalk.blue(`Detected project type: ${type}`));
110
+ break;
111
+ }
112
+ default:
113
+ console.log(themed(`You selected: ${cmd}`, 'accent'));
114
+ }
115
+ }
116
+ catch (error) {
117
+ console.error(chalk.red(`Error executing ${cmd}: ${error.message}`));
118
+ }
119
+ console.log(''); // Add spacing between commands
32
120
  }
33
121
  }
@@ -0,0 +1 @@
1
+ export declare function metrics(): Promise<void>;
@@ -0,0 +1,51 @@
1
+ import chalk from 'chalk';
2
+ import { printError, printInfo } from '../utils/ux.js';
3
+ import { metricsCollector } from '../core/metrics.js';
4
+ import { logger } from '../core/logger.js';
5
+ export async function metrics() {
6
+ console.log(chalk.blue.bold('📊 Dhruv CLI Metrics\n'));
7
+ try {
8
+ // Get metrics data
9
+ const metricsData = await metricsCollector.getMetricsJSON();
10
+ if (metricsData.length === 0) {
11
+ printInfo('No metrics data available yet. Metrics are collected during CLI usage.');
12
+ return;
13
+ }
14
+ // Display metrics by category
15
+ const categories = {
16
+ 'Command Metrics': ['dhruv_command', 'dhruv_session'],
17
+ 'AI Service Metrics': ['dhruv_ai_request', 'dhruv_ai_tokens', 'dhruv_cache'],
18
+ 'Performance Metrics': ['dhruv_memory', 'dhruv_performance'],
19
+ 'Error Metrics': ['dhruv_error'],
20
+ 'Plugin Metrics': ['dhruv_plugin']
21
+ };
22
+ Object.entries(categories).forEach(([category, prefixes]) => {
23
+ const categoryMetrics = metricsData.filter(metric => prefixes.some(prefix => metric.name.startsWith(prefix)));
24
+ if (categoryMetrics.length > 0) {
25
+ console.log(chalk.cyan(`\n📈 ${category}:`));
26
+ categoryMetrics.forEach(metric => {
27
+ const name = metric.name.replace('dhruv_', '').replace(/_/g, ' ');
28
+ const value = metric.values?.[0]?.value || 0;
29
+ const labels = metric.values?.[0]?.labels || {};
30
+ console.log(` ${chalk.yellow(name)}: ${chalk.green(value)}`);
31
+ // Display labels if available
32
+ const labelEntries = Object.entries(labels);
33
+ if (labelEntries.length > 0) {
34
+ console.log(` ${chalk.gray('Labels:')} ${labelEntries.map(([k, v]) => `${k}=${v}`).join(', ')}`);
35
+ }
36
+ });
37
+ }
38
+ });
39
+ // Display raw Prometheus metrics
40
+ console.log(chalk.cyan('\n📋 Raw Prometheus Metrics:'));
41
+ console.log(chalk.gray('─'.repeat(50)));
42
+ const rawMetrics = await metricsCollector.getMetrics();
43
+ console.log(rawMetrics);
44
+ logger.info('Metrics displayed successfully', { metricsCount: metricsData.length });
45
+ }
46
+ catch (error) {
47
+ printError('Failed to retrieve metrics');
48
+ console.error(chalk.red(error.message));
49
+ logger.error('Metrics command failed', error);
50
+ }
51
+ }
@@ -1,42 +1,50 @@
1
- import { askOllama } from '../core/ai.js';
2
- import chalk from 'chalk';
3
- import { loadConfig } from '../config/config.js';
4
1
  import fs from 'fs';
5
- import { highlightCode, printError } from '../utils/ux.js';
2
+ import path from 'path';
3
+ import { runCommand } from '../core/command-runner.js';
4
+ import { getSystemMessage } from '../core/prompts.js';
5
+ import { printError } from '../utils/ux.js';
6
+ function optimizationType(file) {
7
+ const ext = path.extname(file).toLowerCase();
8
+ const fileName = path.basename(file);
9
+ if (fileName === 'package.json')
10
+ return 'package.json configuration';
11
+ if (ext === '.js' || ext === '.ts')
12
+ return 'JavaScript/TypeScript code';
13
+ if (ext === '.json')
14
+ return 'JSON configuration';
15
+ if (ext === '.css')
16
+ return 'CSS styles';
17
+ if (ext === '.html')
18
+ return 'HTML markup';
19
+ return 'general code';
20
+ }
6
21
  export async function optimize(file) {
7
- const config = loadConfig();
8
- let content = '';
9
- if (fs.existsSync(file)) {
10
- content = fs.readFileSync(file, 'utf-8');
22
+ if (!file || file.trim().length === 0) {
23
+ printError('Please provide a file path to optimize.');
24
+ return;
11
25
  }
12
- let streamed = '';
26
+ if (!fs.existsSync(file)) {
27
+ printError(`File "${file}" does not exist.`);
28
+ return;
29
+ }
30
+ let content;
13
31
  try {
14
- process.stdout.write(chalk.green('Optimization suggestion: '));
15
- await askOllama({
16
- prompt: `Optimize this file:\n${content}`,
17
- model: config.model,
18
- onToken: (token) => {
19
- streamed += token;
20
- process.stdout.write(chalk.cyan(token));
21
- }
22
- });
23
- process.stdout.write('\n');
24
- if (typeof highlightCode === 'function' && streamed.match(/```[a-z]*[\s\S]*?```/)) {
25
- const codeBlocks = streamed.match(/```([a-z]*)\n([\s\S]*?)```/g) || [];
26
- for (const block of codeBlocks) {
27
- const [, lang, code] = block.match(/```([a-z]*)\n([\s\S]*?)```/) || [];
28
- if (code)
29
- try {
30
- console.log(highlightCode(code, lang || 'js'));
31
- }
32
- catch (err) {
33
- console.error('Highlight error:', err);
34
- }
35
- }
36
- }
32
+ content = fs.readFileSync(file, 'utf-8');
37
33
  }
38
34
  catch (err) {
39
- printError('Failed to optimize.');
40
- console.error(chalk.red(err.message));
35
+ printError(`Error reading file "${file}": ${err.message}`);
36
+ return;
41
37
  }
38
+ const type = optimizationType(file);
39
+ await runCommand({
40
+ name: 'optimize',
41
+ input: { file },
42
+ header: `⚡ Optimization suggestions for ${type}: `,
43
+ buildRequest: (input, model) => ({
44
+ prompt: `Please analyze and provide optimization suggestions for this ${type}:\n\n${content}\n\nPlease provide:\n1. Specific optimization recommendations\n2. Performance improvements\n3. Best practices to implement\n4. Code examples of improvements\n5. Potential issues to fix\n\nFocus on actionable, practical improvements.`,
45
+ systemMessage: getSystemMessage('optimize'),
46
+ model,
47
+ }),
48
+ footer: `🔍 Want a code review? Try: dhruv review ${file}`,
49
+ });
42
50
  }
@@ -1,54 +1,58 @@
1
- import { askOllama } from '../core/ai.js';
2
- import chalk from 'chalk';
3
- import { loadConfig } from '../config/config.js';
4
1
  import fs from 'fs';
5
- import { highlightCode, printError, createProgressBar } from '../utils/ux.js';
6
- export async function review(fileOrDir) {
7
- const config = loadConfig();
8
- let code = '';
9
- if (fs.existsSync(fileOrDir)) {
10
- const stat = fs.statSync(fileOrDir);
11
- if (stat.isDirectory()) {
12
- const files = fs.readdirSync(fileOrDir);
13
- const bar = createProgressBar(files.length);
14
- for (const f of files) {
15
- code += fs.readFileSync(`${fileOrDir}/${f}`, 'utf-8') + '\n';
16
- bar.increment();
17
- }
18
- bar.stop();
19
- }
20
- else {
21
- code = fs.readFileSync(fileOrDir, 'utf-8');
22
- }
23
- }
24
- let streamed = '';
2
+ import path from 'path';
3
+ import { runCommand } from '../core/command-runner.js';
4
+ import { getSystemMessage } from '../core/prompts.js';
5
+ import { printError } from '../utils/ux.js';
6
+ /** Reads a file or the code files of a directory (up to 10), concatenated. */
7
+ function readCode(fileOrDir) {
8
+ // Read first, branch on the error: no separate existence check to race against.
9
+ let content;
25
10
  try {
26
- process.stdout.write(chalk.green('Review: '));
27
- await askOllama({
28
- prompt: `Review this code:\n${code}`,
29
- model: config.model,
30
- onToken: (token) => {
31
- streamed += token;
32
- process.stdout.write(chalk.cyan(token));
33
- }
34
- });
35
- process.stdout.write('\n');
36
- if (typeof highlightCode === 'function' && streamed.match(/```[a-z]*[\s\S]*?```/)) {
37
- const codeBlocks = streamed.match(/```([a-z]*)\n([\s\S]*?)```/g) || [];
38
- for (const block of codeBlocks) {
39
- const [, lang, code] = block.match(/```([a-z]*)\n([\s\S]*?)```/) || [];
40
- if (code)
41
- try {
42
- console.log(highlightCode(code, lang || 'js'));
43
- }
44
- catch (err) {
45
- console.error('Highlight error:', err);
46
- }
47
- }
48
- }
11
+ content = fs.readFileSync(fileOrDir, 'utf-8');
49
12
  }
50
13
  catch (err) {
51
- printError('Failed to review code.');
52
- console.error(chalk.red(err.message));
14
+ const code = err.code;
15
+ if (code === 'EISDIR') {
16
+ return readDirectory(fileOrDir);
17
+ }
18
+ printError(`Path "${fileOrDir}" does not exist or could not be read.`);
19
+ return undefined;
53
20
  }
21
+ return content;
22
+ }
23
+ function readDirectory(dir) {
24
+ const files = fs
25
+ .readdirSync(dir)
26
+ .filter((f) => f.match(/\.(js|ts|jsx|tsx|py|java|cpp|c|go|rs|rb|php)$/))
27
+ .slice(0, 10);
28
+ if (files.length === 0) {
29
+ printError(`No code files found in directory "${dir}".`);
30
+ return undefined;
31
+ }
32
+ let code = '';
33
+ for (const f of files) {
34
+ try {
35
+ code += `\n// File: ${f}\n${fs.readFileSync(path.join(dir, f), 'utf-8')}\n`;
36
+ }
37
+ catch (err) {
38
+ console.error(`Error reading file ${f}:`, err);
39
+ }
40
+ }
41
+ return code;
42
+ }
43
+ export async function review(fileOrDir) {
44
+ const code = readCode(fileOrDir);
45
+ if (code === undefined)
46
+ return;
47
+ await runCommand({
48
+ name: 'review',
49
+ input: { fileOrDir },
50
+ header: '🔍 Code Review: ',
51
+ buildRequest: (input, model) => ({
52
+ prompt: `Please review this code and provide feedback on code quality, best practices, potential issues, and suggestions for improvement. Here is the code to review:\n\nCODE_START\n${code}\nCODE_END\n\nPlease provide your review in a structured format with clear categories and actionable feedback.`,
53
+ systemMessage: getSystemMessage('review'),
54
+ model,
55
+ }),
56
+ footer: `🛡️ Security check? Try: dhruv security-check ${fileOrDir}`,
57
+ });
54
58
  }
@@ -1,48 +1,58 @@
1
- import { askOllama } from '../core/ai.js';
2
- import chalk from 'chalk';
3
- import { loadConfig } from '../config/config.js';
4
1
  import fs from 'fs';
5
- import { highlightCode, printError } from '../utils/ux.js';
6
- export async function securityCheck(fileOrDir = '.') {
7
- const config = loadConfig();
8
- let code = '';
9
- if (fs.existsSync(fileOrDir)) {
10
- const stat = fs.statSync(fileOrDir);
11
- if (stat.isDirectory()) {
12
- code = fs.readdirSync(fileOrDir).map(f => fs.readFileSync(`${fileOrDir}/${f}`, 'utf-8')).join('\n');
13
- }
14
- else {
15
- code = fs.readFileSync(fileOrDir, 'utf-8');
16
- }
17
- }
18
- let streamed = '';
2
+ import path from 'path';
3
+ import { runCommand } from '../core/command-runner.js';
4
+ import { getSystemMessage } from '../core/prompts.js';
5
+ import { printError } from '../utils/ux.js';
6
+ /** Reads a file or the code files of a directory (up to 10), concatenated. */
7
+ function readCode(fileOrDir) {
8
+ // Read first, branch on the error: no separate existence check to race against.
9
+ let content;
19
10
  try {
20
- process.stdout.write(chalk.green('Security check result: '));
21
- await askOllama({
22
- prompt: `Security check for this code:\n${code}`,
23
- model: config.model,
24
- onToken: (token) => {
25
- streamed += token;
26
- process.stdout.write(chalk.cyan(token));
27
- }
28
- });
29
- process.stdout.write('\n');
30
- if (typeof highlightCode === 'function' && streamed.match(/```[a-z]*[\s\S]*?```/)) {
31
- const codeBlocks = streamed.match(/```([a-z]*)\n([\s\S]*?)```/g) || [];
32
- for (const block of codeBlocks) {
33
- const [, lang, code] = block.match(/```([a-z]*)\n([\s\S]*?)```/) || [];
34
- if (code)
35
- try {
36
- console.log(highlightCode(code, lang || 'js'));
37
- }
38
- catch (err) {
39
- console.error('Highlight error:', err);
40
- }
41
- }
42
- }
11
+ content = fs.readFileSync(fileOrDir, 'utf-8');
43
12
  }
44
13
  catch (err) {
45
- printError('Failed to run security check.');
46
- console.error(chalk.red(err.message));
14
+ const code = err.code;
15
+ if (code === 'EISDIR') {
16
+ return readDirectory(fileOrDir);
17
+ }
18
+ printError(`Path "${fileOrDir}" does not exist or could not be read.`);
19
+ return undefined;
47
20
  }
21
+ return content;
22
+ }
23
+ function readDirectory(dir) {
24
+ const files = fs
25
+ .readdirSync(dir)
26
+ .filter((f) => f.match(/\.(js|ts|jsx|tsx|py|java|cpp|c|go|rs|rb|php)$/))
27
+ .slice(0, 10);
28
+ if (files.length === 0) {
29
+ printError(`No code files found in directory "${dir}".`);
30
+ return undefined;
31
+ }
32
+ let code = '';
33
+ for (const f of files) {
34
+ try {
35
+ code += `\n// File: ${f}\n${fs.readFileSync(path.join(dir, f), 'utf-8')}\n`;
36
+ }
37
+ catch (err) {
38
+ console.error(`Error reading file ${f}:`, err);
39
+ }
40
+ }
41
+ return code;
42
+ }
43
+ export async function securityCheck(fileOrDir = '.') {
44
+ const code = readCode(fileOrDir);
45
+ if (code === undefined)
46
+ return;
47
+ await runCommand({
48
+ name: 'security-check',
49
+ input: { fileOrDir },
50
+ header: '🛡️ Security Analysis: ',
51
+ buildRequest: (input, model) => ({
52
+ prompt: `Perform a security analysis on this code. Look for common security vulnerabilities, unsafe practices, potential injection attacks, and provide recommendations for improvement:\n\n${code}`,
53
+ systemMessage: getSystemMessage('security'),
54
+ model,
55
+ }),
56
+ footer: `🔧 Need fixes? Try: dhruv fix <security issue>`,
57
+ });
48
58
  }
@@ -0,0 +1 @@
1
+ export declare function status(): Promise<void>;
@@ -0,0 +1,45 @@
1
+ import chalk from 'chalk';
2
+ import { loadConfig } from '../config/config.js';
3
+ import { printSuccess, printError, printInfo } from '../utils/ux.js';
4
+ import { listModels } from '../core/ai.js';
5
+ export async function status() {
6
+ console.log(chalk.blue('🔍 Dhruv CLI Status Check\n'));
7
+ const config = loadConfig();
8
+ printInfo(`Current configuration:`);
9
+ console.log(` Model: ${config.model}`);
10
+ console.log(` Response Format: ${config.responseFormat}`);
11
+ console.log(` Verbose: ${config.verbose}`);
12
+ console.log(` Theme: ${config.theme}\n`);
13
+ try {
14
+ printInfo('Testing Ollama connection...');
15
+ const models = await listModels();
16
+ printSuccess('✓ Ollama is running and accessible');
17
+ if (models.length > 0) {
18
+ printSuccess(`✓ Found ${models.length} available models:`);
19
+ models.forEach((name) => {
20
+ const isConfigured = name === config.model;
21
+ const status = isConfigured ? chalk.green('(configured)') : '';
22
+ console.log(` • ${name} ${status}`);
23
+ });
24
+ }
25
+ else {
26
+ printError('✗ No models found');
27
+ console.log(chalk.yellow('Install a model using: ollama pull llama2'));
28
+ }
29
+ if (models.includes(config.model)) {
30
+ printSuccess(`✓ Configured model '${config.model}' is available`);
31
+ }
32
+ else {
33
+ printError(`✗ Configured model '${config.model}' is not available`);
34
+ if (models.length > 0) {
35
+ console.log(chalk.yellow(`Available models: ${models.join(', ')}`));
36
+ }
37
+ }
38
+ }
39
+ catch (error) {
40
+ printError('✗ Ollama connection failed');
41
+ console.log(chalk.red(error.message));
42
+ console.log(chalk.yellow('\nTo start Ollama, run: ollama serve'));
43
+ console.log(chalk.yellow('To install a model, run: ollama pull llama2'));
44
+ }
45
+ }
@@ -1,41 +1,15 @@
1
- import { askOllama } from '../core/ai.js';
2
- import ora from 'ora';
3
- import chalk from 'chalk';
4
- import { loadConfig } from '../config/config.js';
5
- import { highlightCode, printError } from '../utils/ux.js';
1
+ import { runCommand } from '../core/command-runner.js';
2
+ import { getSystemMessage } from '../core/prompts.js';
6
3
  export async function suggest(query) {
7
- const config = loadConfig();
8
- const spinner = ora('Generating suggestions...').start();
9
- let streamed = '';
10
- try {
11
- spinner.stop();
12
- process.stdout.write(chalk.green('Suggestions: '));
13
- await askOllama({
14
- prompt: `Suggest: ${query}`,
15
- model: config.model,
16
- onToken: (token) => {
17
- streamed += token;
18
- process.stdout.write(chalk.cyan(token));
19
- }
20
- });
21
- process.stdout.write('\n');
22
- if (typeof highlightCode === 'function' && streamed.match(/```[a-z]*[\s\S]*?```/)) {
23
- // Highlight code blocks if present
24
- const codeBlocks = streamed.match(/```([a-z]*)\n([\s\S]*?)```/g) || [];
25
- for (const block of codeBlocks) {
26
- const [, lang, code] = block.match(/```([a-z]*)\n([\s\S]*?)```/) || [];
27
- if (code)
28
- try {
29
- console.log(highlightCode(code, lang || 'js'));
30
- }
31
- catch (err) {
32
- console.error('Highlight error:', err);
33
- }
34
- }
35
- }
36
- }
37
- catch (err) {
38
- printError('Failed to get suggestions.');
39
- console.error(chalk.red(err.message));
40
- }
4
+ await runCommand({
5
+ name: 'suggest',
6
+ input: { query },
7
+ header: '💡 Suggestions: ',
8
+ buildRequest: (input, model) => ({
9
+ prompt: input.query,
10
+ systemMessage: getSystemMessage('suggest'),
11
+ model,
12
+ }),
13
+ footer: `🔧 Need implementation help? Try: dhruv fix "${query}"`,
14
+ });
41
15
  }
@@ -2,17 +2,45 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
  const CONFIG_FILE = path.join(process.cwd(), '.dhruv-config.json');
4
4
  const defaultConfig = {
5
- model: 'codellama',
5
+ model: 'gemma3:270m',
6
6
  verbose: false,
7
7
  responseFormat: 'text',
8
8
  theme: 'default',
9
9
  };
10
10
  export function loadConfig() {
11
11
  if (fs.existsSync(CONFIG_FILE)) {
12
- return { ...defaultConfig, ...JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8')) };
12
+ try {
13
+ const fileContent = fs.readFileSync(CONFIG_FILE, 'utf-8');
14
+ const parsedConfig = JSON.parse(fileContent);
15
+ return validateAndMergeConfig(parsedConfig);
16
+ }
17
+ catch (error) {
18
+ console.warn(`Warning: Invalid config file. Using defaults. Error: ${error.message}`);
19
+ return defaultConfig;
20
+ }
13
21
  }
14
22
  return defaultConfig;
15
23
  }
24
+ function validateAndMergeConfig(config) {
25
+ const validatedConfig = { ...defaultConfig };
26
+ // Validate model
27
+ if (config.model && typeof config.model === 'string') {
28
+ validatedConfig.model = config.model;
29
+ }
30
+ // Validate verbose
31
+ if (typeof config.verbose === 'boolean') {
32
+ validatedConfig.verbose = config.verbose;
33
+ }
34
+ // Validate responseFormat
35
+ if (config.responseFormat && ['text', 'json', 'markdown'].includes(config.responseFormat)) {
36
+ validatedConfig.responseFormat = config.responseFormat;
37
+ }
38
+ // Validate theme
39
+ if (config.theme && ['default', 'dark', 'light', 'mono'].includes(config.theme)) {
40
+ validatedConfig.theme = config.theme;
41
+ }
42
+ return validatedConfig;
43
+ }
16
44
  export function saveConfig(config) {
17
45
  const current = loadConfig();
18
46
  fs.writeFileSync(CONFIG_FILE, JSON.stringify({ ...current, ...config }, null, 2));