@rahul05ranjan/dhruv-cli 1.4.6 → 1.6.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 (76) hide show
  1. package/.github/workflows/ci.yml +18 -238
  2. package/.github/workflows/contribution.yml +6 -141
  3. package/.github/workflows/dependabot-auto-merge.yml +1 -0
  4. package/.github/workflows/labeler.yml +1 -0
  5. package/.github/workflows/security.yml +3 -0
  6. package/CHANGELOG.md +16 -4
  7. package/README.md +145 -40
  8. package/__tests__/cli-contract.test.ts +170 -0
  9. package/__tests__/core.test.ts +193 -1
  10. package/__tests__/diagnostics.test.ts +179 -0
  11. package/__tests__/file-workflows.test.ts +234 -0
  12. package/__tests__/interactive.test.ts +134 -0
  13. package/__tests__/setup.ts +1 -0
  14. package/__tests__/workflows.test.ts +27 -4
  15. package/dist/commands/generate.d.ts +6 -1
  16. package/dist/commands/generate.js +44 -11
  17. package/dist/commands/health.d.ts +4 -1
  18. package/dist/commands/health.js +59 -16
  19. package/dist/commands/init.js +18 -7
  20. package/dist/commands/menu.js +125 -100
  21. package/dist/commands/metrics.d.ts +5 -1
  22. package/dist/commands/metrics.js +49 -10
  23. package/dist/commands/optimize.js +1 -1
  24. package/dist/commands/review.d.ts +4 -1
  25. package/dist/commands/review.js +66 -9
  26. package/dist/commands/security-check.d.ts +4 -1
  27. package/dist/commands/security-check.js +100 -6
  28. package/dist/commands/status.js +44 -4
  29. package/dist/config/config.d.ts +5 -1
  30. package/dist/config/config.js +24 -7
  31. package/dist/core/ai.d.ts +11 -0
  32. package/dist/core/ai.js +33 -9
  33. package/dist/core/command-catalog.d.ts +10 -0
  34. package/dist/core/command-catalog.js +27 -0
  35. package/dist/core/command-runner.js +106 -21
  36. package/dist/core/logger.js +1 -0
  37. package/dist/core/metrics.d.ts +28 -0
  38. package/dist/core/metrics.js +79 -0
  39. package/dist/index.js +98 -24
  40. package/dist/utils/projectType.d.ts +7 -1
  41. package/dist/utils/projectType.js +91 -13
  42. package/docs/api/assets/highlight.css +4 -4
  43. package/docs/api/index.html +161 -39
  44. package/docs/api/media/CONTRIBUTING.md +60 -0
  45. package/docs/api/media/SECURITY.md +8 -0
  46. package/docs/api/media/dhruv-cli-preview.svg +42 -0
  47. package/docs/api/media/publishing-fix.md +34 -0
  48. package/docs/dhruv-cli-preview.svg +42 -0
  49. package/docs/index.html +631 -533
  50. package/docs/publishing-fix.md +34 -0
  51. package/package.json +1 -1
  52. package/src/commands/generate.ts +50 -11
  53. package/src/commands/health.ts +62 -17
  54. package/src/commands/init.ts +18 -7
  55. package/src/commands/menu.ts +54 -30
  56. package/src/commands/metrics.ts +53 -9
  57. package/src/commands/optimize.ts +1 -1
  58. package/src/commands/review.ts +72 -9
  59. package/src/commands/security-check.ts +111 -6
  60. package/src/commands/status.ts +43 -5
  61. package/src/config/config.ts +26 -7
  62. package/src/core/ai.ts +36 -8
  63. package/src/core/command-catalog.ts +37 -0
  64. package/src/core/command-runner.ts +108 -22
  65. package/src/core/logger.ts +1 -0
  66. package/src/core/metrics.ts +105 -0
  67. package/src/index.ts +97 -24
  68. package/src/utils/projectType.ts +85 -9
  69. package/tsconfig.json +1 -1
  70. package/.github/workflows/auto-assign.yml +0 -14
  71. package/.github/workflows/build-publish.yml +0 -154
  72. package/.github/workflows/deploy.yml +0 -336
  73. package/.github/workflows/monitoring.yml +0 -270
  74. package/PUBLISHING_FIX.md +0 -92
  75. package/logs/.8a99b6cf655346317fdbf29f4fffcf91131432f3-audit.json +0 -15
  76. package/logs/.eee104bf8fff5ecd38a6a2842df260de6470a7c3-audit.json +0 -15
@@ -1,27 +1,48 @@
1
1
  import fs from 'fs';
2
+ import path from 'path';
2
3
  import { runCommand } from '../core/command-runner.js';
3
4
  import { getSystemMessage } from '../core/prompts.js';
4
- import { printError, printSuccess } from '../utils/ux.js';
5
- function buildPrompt(type, content) {
5
+ import { printError, printSuccess, printInfo } from '../utils/ux.js';
6
+ import { loadConfig } from '../config/config.js';
7
+ function getLanguageForFile(target) {
8
+ const ext = path.extname(target).toLowerCase();
9
+ switch (ext) {
10
+ case '.py':
11
+ return { name: 'Python', testFramework: 'pytest or unittest' };
12
+ case '.go':
13
+ return { name: 'Go', testFramework: 'standard testing package' };
14
+ case '.rs':
15
+ return { name: 'Rust', testFramework: 'standard Rust test framework' };
16
+ case '.ts':
17
+ case '.tsx':
18
+ return { name: 'TypeScript', testFramework: 'Jest or Vitest' };
19
+ case '.java':
20
+ return { name: 'Java', testFramework: 'JUnit 5' };
21
+ default:
22
+ return { name: 'JavaScript', testFramework: 'Jest or Mocha' };
23
+ }
24
+ }
25
+ function buildPrompt(type, content, target) {
26
+ const lang = getLanguageForFile(target);
6
27
  if (type === 'tests' || type === 'test') {
7
- return `Generate comprehensive unit tests for the following JavaScript code. Use Jest or Mocha syntax. Only return the test code without explanations:\n\n${content}`;
28
+ return `Generate comprehensive unit tests for the following ${lang.name} code. Use ${lang.testFramework} syntax. Only return the test code without explanations:\n\n${content}`;
8
29
  }
9
30
  if (type === 'documentation' || type === 'docs') {
10
- return `Generate JSDoc documentation for the following code:\n\n${content}`;
31
+ return `Generate ${lang.name === 'Python' ? 'docstrings' : 'JSDoc/documentation'} for the following code:\n\n${content}`;
11
32
  }
12
33
  return `Generate ${type} for this code:\n\n${content}`;
13
34
  }
14
35
  /** Extracts test code from the response: a fenced block if present, else the raw response. */
15
36
  function extractTestCode(response) {
16
- const fenced = response.match(/```(?:javascript|js)?\s*\n([\s\S]*?)```/);
37
+ const fenced = response.match(/```(?:javascript|js|typescript|ts|python|py|go|rust|rs|java)?\s*\n([\s\S]*?)```/i);
17
38
  if (fenced?.[1])
18
39
  return fenced[1].trim();
19
40
  return response
20
- .replace(/^.*?(?=const|describe|test|it\s*\()/s, '')
21
- .replace(/```[a-z]*\n?/g, '')
41
+ .replace(/^.*?(?=const|describe|test|it\s*\(|def test_|func Test|#\[test\])/s, '')
42
+ .replace(/```[a-z]*\n?/gi, '')
22
43
  .trim();
23
44
  }
24
- export async function generate(type, target) {
45
+ export async function generate(type, target, options = {}) {
25
46
  if (!fs.existsSync(target)) {
26
47
  printError(`Target file "${target}" does not exist.`);
27
48
  return;
@@ -32,7 +53,7 @@ export async function generate(type, target) {
32
53
  input: { type, target },
33
54
  header: `šŸ”Ø Generating ${type}: `,
34
55
  buildRequest: (input, model) => ({
35
- prompt: buildPrompt(input.type, content),
56
+ prompt: buildPrompt(input.type, content, input.target),
36
57
  systemMessage: getSystemMessage('generate'),
37
58
  model,
38
59
  }),
@@ -44,9 +65,21 @@ export async function generate(type, target) {
44
65
  printError('No valid test code generated.');
45
66
  return;
46
67
  }
47
- const testFile = target.replace(/\.[^.]+$/, '.test.js');
68
+ const extension = path.extname(target) || '.js';
69
+ const testFile = options.output ?? target.replace(/\.[^.]+$/, `.test${extension}`);
70
+ if (!options.apply && !options.output) {
71
+ if (loadConfig().responseFormat !== 'json') {
72
+ printInfo(`Preview only. Use --apply to write ${testFile}, or --output <path> to choose a destination.`);
73
+ }
74
+ return;
75
+ }
76
+ if (fs.existsSync(testFile) && !options.overwrite) {
77
+ printError(`Test file "${testFile}" already exists. Use --overwrite to replace it.`);
78
+ return;
79
+ }
48
80
  fs.writeFileSync(testFile, codeToSave);
49
- printSuccess(`Test file saved: ${testFile}`);
81
+ if (loadConfig().responseFormat !== 'json')
82
+ printSuccess(`Test file saved: ${testFile}`);
50
83
  },
51
84
  footer: `šŸ” Want a review? Try: dhruv review ${target}`,
52
85
  });
@@ -1 +1,4 @@
1
- export declare function health(): Promise<void>;
1
+ export interface HealthOptions {
2
+ details?: boolean;
3
+ }
4
+ export declare function health(options?: HealthOptions): Promise<void>;
@@ -7,18 +7,21 @@ import { securityManager } from '../core/security.js';
7
7
  import fs from 'fs';
8
8
  import path from 'path';
9
9
  import os from 'os';
10
- export async function health() {
11
- console.log(chalk.blue.bold('šŸ” Dhruv CLI Health Check\n'));
10
+ export async function health(options = {}) {
11
+ const jsonOutput = loadConfig().responseFormat === 'json';
12
12
  const results = [];
13
13
  const startTime = Date.now();
14
14
  try {
15
15
  // System Information
16
16
  const systemInfo = getSystemInfo();
17
- console.log(chalk.cyan('šŸ“Š System Information:'));
18
- Object.entries(systemInfo).forEach(([key, value]) => {
19
- console.log(` ${key}: ${chalk.yellow(value)}`);
20
- });
21
- console.log();
17
+ if (!jsonOutput) {
18
+ console.log(chalk.blue.bold('šŸ” Dhruv CLI Health Check\n'));
19
+ console.log(chalk.cyan('šŸ“Š System Information:'));
20
+ Object.entries(systemInfo).forEach(([key, value]) => {
21
+ console.log(` ${key}: ${chalk.yellow(value)}`);
22
+ });
23
+ console.log();
24
+ }
22
25
  // Configuration Check
23
26
  results.push(...await checkConfiguration());
24
27
  // Dependencies Check
@@ -33,14 +36,40 @@ export async function health() {
33
36
  results.push(...await checkFileSystem());
34
37
  // Plugin System Check
35
38
  results.push(...await checkPlugins());
36
- // Display Results
37
- displayResults(results);
38
39
  const duration = Date.now() - startTime;
40
+ const summary = summarizeResults(results);
41
+ if (jsonOutput) {
42
+ process.exitCode = summary.fail > 0 ? 1 : 0;
43
+ process.stdout.write(`${JSON.stringify({
44
+ ok: summary.fail === 0,
45
+ command: 'health',
46
+ system: systemInfo,
47
+ results,
48
+ summary,
49
+ durationMs: duration,
50
+ })}\n`);
51
+ }
52
+ else {
53
+ if (options.details)
54
+ displayResults(results);
55
+ else
56
+ displayConciseResults(results);
57
+ }
39
58
  logger.info('Health check completed', { duration, results: results.length });
40
59
  }
41
60
  catch (error) {
42
- printError('Health check failed');
43
- console.error(chalk.red(error.message));
61
+ process.exitCode = 1;
62
+ if (jsonOutput) {
63
+ process.stdout.write(`${JSON.stringify({
64
+ ok: false,
65
+ command: 'health',
66
+ error: error.message,
67
+ })}\n`);
68
+ }
69
+ else {
70
+ printError('Health check failed');
71
+ console.error(chalk.red(error.message));
72
+ }
44
73
  logger.error('Health check failed', error);
45
74
  }
46
75
  }
@@ -360,11 +389,7 @@ function displayResults(results) {
360
389
  }
361
390
  });
362
391
  // Summary
363
- const summary = {
364
- pass: results.filter(r => r.status === 'pass').length,
365
- warn: results.filter(r => r.status === 'warn').length,
366
- fail: results.filter(r => r.status === 'fail').length
367
- };
392
+ const summary = summarizeResults(results);
368
393
  console.log(chalk.blue.bold('\nšŸ“Š Summary:'));
369
394
  console.log(` āœ… Passed: ${chalk.green(summary.pass)}`);
370
395
  console.log(` āš ļø Warnings: ${chalk.yellow(summary.warn)}`);
@@ -374,3 +399,21 @@ function displayResults(results) {
374
399
  const statusColor = overallStatus === 'pass' ? chalk.green : overallStatus === 'warn' ? chalk.yellow : chalk.red;
375
400
  console.log(`\n${statusIcon} ${statusColor('Overall Status: ' + overallStatus.toUpperCase())}`);
376
401
  }
402
+ function displayConciseResults(results) {
403
+ const summary = summarizeResults(results);
404
+ const overallStatus = summary.fail > 0 ? 'fail' : summary.warn > 0 ? 'warn' : 'pass';
405
+ const icon = overallStatus === 'pass' ? 'āœ…' : overallStatus === 'warn' ? 'āš ļø' : 'āŒ';
406
+ console.log(chalk.blue.bold(`\n${icon} Health: ${overallStatus.toUpperCase()}`));
407
+ console.log(` ${chalk.green(`${summary.pass} passed`)}, ${chalk.yellow(`${summary.warn} warnings`)}, ${chalk.red(`${summary.fail} failed`)}`);
408
+ results
409
+ .filter((result) => result.status !== 'pass')
410
+ .forEach((result) => console.log(` ${result.category}: ${result.message}${result.recommendation ? ` — ${result.recommendation}` : ''}`));
411
+ console.log(chalk.dim('Run `dhruv health --details` for full diagnostics.'));
412
+ }
413
+ function summarizeResults(results) {
414
+ return {
415
+ pass: results.filter(r => r.status === 'pass').length,
416
+ warn: results.filter(r => r.status === 'warn').length,
417
+ fail: results.filter(r => r.status === 'fail').length,
418
+ };
419
+ }
@@ -12,18 +12,26 @@ export async function init() {
12
12
  }
13
13
  }
14
14
  catch {
15
- console.log(chalk.yellow('Warning: Could not fetch available models from Ollama.'));
16
- console.log(chalk.yellow('Using default model choices.'));
15
+ console.log(chalk.yellow('Warning: Could not connect to Ollama.'));
16
+ console.log(chalk.yellow('šŸ’” Start Ollama with: ollama serve'));
17
+ console.log(chalk.yellow(`šŸ’” Install default model with: ollama pull ${current.model}\n`));
17
18
  }
18
19
  try {
19
20
  const answers = await inquirer.prompt([
20
21
  {
21
22
  type: 'list',
22
23
  name: 'model',
23
- message: 'Which Ollama model do you want to use?',
24
+ message: `Which Ollama model do you want to use? (default: ${current.model})`,
24
25
  choices: modelChoices,
25
26
  default: current.model,
26
27
  },
28
+ {
29
+ type: 'list',
30
+ name: 'scope',
31
+ message: 'Where should Dhruv save these settings?',
32
+ choices: ['local', 'global'],
33
+ default: 'local',
34
+ },
27
35
  {
28
36
  type: 'list',
29
37
  name: 'responseFormat',
@@ -45,15 +53,18 @@ export async function init() {
45
53
  default: current.theme || 'default',
46
54
  },
47
55
  ]);
48
- saveConfig(answers);
49
- console.log(chalk.green('Configuration saved!'));
56
+ const { scope, ...settings } = answers;
57
+ saveConfig(settings, { scope });
58
+ console.log(chalk.green(`Configuration saved ${scope === 'global' ? 'for your user account' : 'in this project'}!`));
50
59
  }
51
60
  catch (error) {
52
- if (error?.isTtyError) {
61
+ const isTty = Boolean(error?.isTtyError);
62
+ process.exitCode = isTty ? 1 : 130;
63
+ if (isTty) {
53
64
  console.log(chalk.red('This command requires an interactive terminal.'));
54
65
  }
55
66
  else {
56
- console.log(chalk.red('Configuration cancelled or failed.'));
67
+ console.log(chalk.red('Configuration cancelled.'));
57
68
  }
58
69
  }
59
70
  }
@@ -8,114 +8,139 @@ import { optimize } from './optimize.js';
8
8
  import { securityCheck } from './security-check.js';
9
9
  import { generate } from './generate.js';
10
10
  import { init } from './init.js';
11
+ import { status } from './status.js';
12
+ import { health } from './health.js';
13
+ import { metrics } from './metrics.js';
11
14
  import { detectProjectType } from '../utils/projectType.js';
12
15
  import chalk from 'chalk';
16
+ import { commandCatalog } from '../core/command-catalog.js';
13
17
  const commands = [
14
- { name: 'Explain', value: 'explain' },
15
- { name: 'Suggest', value: 'suggest' },
16
- { name: 'Fix', value: 'fix' },
17
- { name: 'Review', value: 'review' },
18
- { name: 'Optimize', value: 'optimize' },
19
- { name: 'Security Check', value: 'security-check' },
20
- { name: 'Generate', value: 'generate' },
21
- { name: 'Init (Setup)', value: 'init' },
22
- { name: 'Project Type', value: 'project-type' },
23
- { name: 'Exit', value: 'exit' }
18
+ ...commandCatalog.map(({ menuLabel, name }) => ({ name: menuLabel, value: name })),
19
+ { name: 'Exit', value: 'exit' },
24
20
  ];
25
21
  export async function menu() {
26
- let running = true;
27
- while (running) {
28
- const { cmd } = await inquirer.prompt([
29
- {
30
- type: 'list',
31
- name: 'cmd',
32
- message: themed('What do you want to do?', 'primary'),
33
- choices: commands
34
- }
35
- ]);
36
- if (cmd === 'exit') {
37
- running = false;
38
- break;
39
- }
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;
22
+ try {
23
+ while (true) {
24
+ const { filter = '' } = await inquirer.prompt([
25
+ {
26
+ type: 'input',
27
+ name: 'filter',
28
+ message: 'Filter commands (press enter to show all):',
29
+ },
30
+ ]);
31
+ const normalizedFilter = String(filter).trim().toLowerCase();
32
+ const filteredCommands = normalizedFilter
33
+ ? commands.filter((command) => command.name.toLowerCase().includes(normalizedFilter) || command.value.includes(normalizedFilter))
34
+ : commands;
35
+ const { cmd } = await inquirer.prompt([
36
+ {
37
+ type: 'list',
38
+ name: 'cmd',
39
+ message: themed('What do you want to do?', 'primary'),
40
+ choices: filteredCommands.length > 0 ? filteredCommands : [{ name: 'No matching commands — Exit', value: 'exit' }],
88
41
  }
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;
42
+ ]);
43
+ if (cmd === 'exit') {
44
+ break;
45
+ }
46
+ try {
47
+ switch (cmd) {
48
+ case 'explain': {
49
+ const { query } = await inquirer.prompt([
50
+ { type: 'input', name: 'query', message: 'What would you like me to explain?' }
51
+ ]);
52
+ if (query)
53
+ await explain(query);
54
+ break;
55
+ }
56
+ case 'suggest': {
57
+ const { query } = await inquirer.prompt([
58
+ { type: 'input', name: 'query', message: 'What would you like suggestions for?' }
59
+ ]);
60
+ if (query)
61
+ await suggest(query);
62
+ break;
63
+ }
64
+ case 'fix': {
65
+ const { query } = await inquirer.prompt([
66
+ { type: 'input', name: 'query', message: 'Describe the issue you need help fixing:' }
67
+ ]);
68
+ if (query)
69
+ await fix(query);
70
+ break;
71
+ }
72
+ case 'review': {
73
+ const { fileOrDir } = await inquirer.prompt([
74
+ { type: 'input', name: 'fileOrDir', message: 'Enter file or directory path to review:' }
75
+ ]);
76
+ if (fileOrDir)
77
+ await review(fileOrDir);
78
+ break;
79
+ }
80
+ case 'optimize': {
81
+ const { file } = await inquirer.prompt([
82
+ { type: 'input', name: 'file', message: 'Enter file path to optimize:' }
83
+ ]);
84
+ if (file)
85
+ await optimize(file);
86
+ break;
87
+ }
88
+ case 'security-check': {
89
+ const { fileOrDir } = await inquirer.prompt([
90
+ { type: 'input', name: 'fileOrDir', message: 'Enter file or directory path to check (or press enter for current directory):', default: '.' }
91
+ ]);
92
+ await securityCheck(fileOrDir);
93
+ break;
94
+ }
95
+ case 'generate': {
96
+ const answers = await inquirer.prompt([
97
+ {
98
+ type: 'list',
99
+ name: 'type',
100
+ message: 'What would you like to generate?',
101
+ choices: ['tests', 'documentation', 'docs', 'component']
102
+ },
103
+ { type: 'input', name: 'target', message: 'Enter target file path:' }
104
+ ]);
105
+ if (answers.target)
106
+ await generate(answers.type, answers.target);
107
+ break;
108
+ }
109
+ case 'init': {
110
+ await init();
111
+ break;
112
+ }
113
+ case 'project-type': {
114
+ const type = detectProjectType();
115
+ console.log(chalk.blue(`Detected project type: ${type}`));
116
+ break;
117
+ }
118
+ case 'status':
119
+ await status();
120
+ break;
121
+ case 'health':
122
+ await health();
123
+ break;
124
+ case 'metrics':
125
+ await metrics();
126
+ break;
127
+ case 'completion':
128
+ console.log(themed('Run `dhruv completion <bash|zsh|fish>` to install shell completion.', 'accent'));
129
+ break;
130
+ default:
131
+ console.log(themed(`You selected: ${cmd}`, 'accent'));
111
132
  }
112
- default:
113
- console.log(themed(`You selected: ${cmd}`, 'accent'));
114
133
  }
134
+ catch (error) {
135
+ console.error(chalk.red(`Error executing ${cmd}: ${error.message}`));
136
+ }
137
+ console.log(''); // Add spacing between commands
115
138
  }
116
- catch (error) {
117
- console.error(chalk.red(`Error executing ${cmd}: ${error.message}`));
118
- }
119
- console.log(''); // Add spacing between commands
139
+ }
140
+ catch (error) {
141
+ const message = error instanceof Error ? error.message : String(error);
142
+ const cancelled = /cancel|force closed|exitprompt/i.test(message);
143
+ process.exitCode = cancelled ? 130 : 1;
144
+ console.error(chalk.red(cancelled ? 'Interactive menu cancelled.' : `Interactive menu failed: ${message}`));
120
145
  }
121
146
  }
@@ -1 +1,5 @@
1
- export declare function metrics(): Promise<void>;
1
+ export interface MetricsOptions {
2
+ raw?: boolean;
3
+ reset?: boolean;
4
+ }
5
+ export declare function metrics(options?: MetricsOptions): Promise<void>;
@@ -1,12 +1,42 @@
1
1
  import chalk from 'chalk';
2
- import { printError, printInfo } from '../utils/ux.js';
2
+ import { printSuccess, printError, printInfo } from '../utils/ux.js';
3
3
  import { metricsCollector } from '../core/metrics.js';
4
4
  import { logger } from '../core/logger.js';
5
- export async function metrics() {
6
- console.log(chalk.blue.bold('šŸ“Š Dhruv CLI Metrics\n'));
5
+ import { loadConfig } from '../config/config.js';
6
+ export async function metrics(options = {}) {
7
7
  try {
8
+ if (options.reset) {
9
+ metricsCollector.resetPersistent();
10
+ if (loadConfig().responseFormat === 'json') {
11
+ process.stdout.write(`${JSON.stringify({ ok: true, command: 'metrics', reset: true, summary: metricsCollector.getSummary() })}\n`);
12
+ }
13
+ else {
14
+ printSuccess('Local metrics reset.');
15
+ }
16
+ return;
17
+ }
8
18
  // Get metrics data
9
19
  const metricsData = await metricsCollector.getMetricsJSON();
20
+ const summary = metricsCollector.getSummary();
21
+ if (loadConfig().responseFormat === 'json') {
22
+ process.stdout.write(`${JSON.stringify({
23
+ ok: true,
24
+ command: 'metrics',
25
+ summary,
26
+ metrics: metricsData,
27
+ })}\n`);
28
+ return;
29
+ }
30
+ console.log(chalk.blue.bold('šŸ“Š Dhruv CLI Metrics\n'));
31
+ console.log(chalk.cyan('šŸ“Œ Local summary:'));
32
+ console.log(` Sessions: ${chalk.green(summary.sessions)}`);
33
+ Object.entries(summary.commands).forEach(([command, data]) => {
34
+ console.log(` ${chalk.yellow(command)}: ${data.runs} runs, ${data.successes} succeeded, ${data.failures} failed, ${data.durationMs}ms`);
35
+ });
36
+ Object.entries(summary.models).forEach(([model, data]) => {
37
+ console.log(` ${chalk.yellow(model)}: ${data.requests} requests, ${data.successes} succeeded, ${data.failures} failed, ${data.durationMs}ms`);
38
+ });
39
+ console.log(` Cache: ${chalk.green(summary.cache.hits)} hits, ${chalk.yellow(summary.cache.misses)} misses`);
10
40
  if (metricsData.length === 0) {
11
41
  printInfo('No metrics data available yet. Metrics are collected during CLI usage.');
12
42
  return;
@@ -36,16 +66,25 @@ export async function metrics() {
36
66
  });
37
67
  }
38
68
  });
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);
69
+ if (options.raw) {
70
+ const rawMetrics = await metricsCollector.getMetrics();
71
+ process.stdout.write(rawMetrics);
72
+ }
44
73
  logger.info('Metrics displayed successfully', { metricsCount: metricsData.length });
45
74
  }
46
75
  catch (error) {
47
- printError('Failed to retrieve metrics');
48
- console.error(chalk.red(error.message));
76
+ process.exitCode = 1;
77
+ if (loadConfig().responseFormat === 'json') {
78
+ process.stdout.write(`${JSON.stringify({
79
+ ok: false,
80
+ command: 'metrics',
81
+ error: error.message,
82
+ })}\n`);
83
+ }
84
+ else {
85
+ printError('Failed to retrieve metrics');
86
+ console.error(chalk.red(error.message));
87
+ }
49
88
  logger.error('Metrics command failed', error);
50
89
  }
51
90
  }
@@ -41,7 +41,7 @@ export async function optimize(file) {
41
41
  input: { file },
42
42
  header: `⚔ Optimization suggestions for ${type}: `,
43
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.`,
44
+ prompt: `Please analyze and provide optimization suggestions for this ${type}. For every recommendation, explain the expected impact, how to measure it, and the trade-offs or risks before applying it.\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
45
  systemMessage: getSystemMessage('optimize'),
46
46
  model,
47
47
  }),
@@ -1 +1,4 @@
1
- export declare function review(fileOrDir: string): Promise<void>;
1
+ export interface ReviewOptions {
2
+ diff?: boolean;
3
+ }
4
+ export declare function review(fileOrDir: string, options?: ReviewOptions): Promise<void>;