@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.
- package/.github/workflows/ci.yml +18 -238
- package/.github/workflows/contribution.yml +6 -141
- package/.github/workflows/dependabot-auto-merge.yml +1 -0
- package/.github/workflows/labeler.yml +1 -0
- package/.github/workflows/security.yml +3 -0
- package/CHANGELOG.md +16 -4
- package/README.md +145 -40
- package/__tests__/cli-contract.test.ts +170 -0
- package/__tests__/core.test.ts +193 -1
- package/__tests__/diagnostics.test.ts +179 -0
- package/__tests__/file-workflows.test.ts +234 -0
- package/__tests__/interactive.test.ts +134 -0
- package/__tests__/setup.ts +1 -0
- package/__tests__/workflows.test.ts +27 -4
- package/dist/commands/generate.d.ts +6 -1
- package/dist/commands/generate.js +44 -11
- package/dist/commands/health.d.ts +4 -1
- package/dist/commands/health.js +59 -16
- package/dist/commands/init.js +18 -7
- package/dist/commands/menu.js +125 -100
- package/dist/commands/metrics.d.ts +5 -1
- package/dist/commands/metrics.js +49 -10
- package/dist/commands/optimize.js +1 -1
- package/dist/commands/review.d.ts +4 -1
- package/dist/commands/review.js +66 -9
- package/dist/commands/security-check.d.ts +4 -1
- package/dist/commands/security-check.js +100 -6
- package/dist/commands/status.js +44 -4
- package/dist/config/config.d.ts +5 -1
- package/dist/config/config.js +24 -7
- package/dist/core/ai.d.ts +11 -0
- package/dist/core/ai.js +33 -9
- package/dist/core/command-catalog.d.ts +10 -0
- package/dist/core/command-catalog.js +27 -0
- package/dist/core/command-runner.js +106 -21
- package/dist/core/logger.js +1 -0
- package/dist/core/metrics.d.ts +28 -0
- package/dist/core/metrics.js +79 -0
- package/dist/index.js +98 -24
- package/dist/utils/projectType.d.ts +7 -1
- package/dist/utils/projectType.js +91 -13
- package/docs/api/assets/highlight.css +4 -4
- package/docs/api/index.html +161 -39
- package/docs/api/media/CONTRIBUTING.md +60 -0
- package/docs/api/media/SECURITY.md +8 -0
- package/docs/api/media/dhruv-cli-preview.svg +42 -0
- package/docs/api/media/publishing-fix.md +34 -0
- package/docs/dhruv-cli-preview.svg +42 -0
- package/docs/index.html +631 -533
- package/docs/publishing-fix.md +34 -0
- package/package.json +1 -1
- package/src/commands/generate.ts +50 -11
- package/src/commands/health.ts +62 -17
- package/src/commands/init.ts +18 -7
- package/src/commands/menu.ts +54 -30
- package/src/commands/metrics.ts +53 -9
- package/src/commands/optimize.ts +1 -1
- package/src/commands/review.ts +72 -9
- package/src/commands/security-check.ts +111 -6
- package/src/commands/status.ts +43 -5
- package/src/config/config.ts +26 -7
- package/src/core/ai.ts +36 -8
- package/src/core/command-catalog.ts +37 -0
- package/src/core/command-runner.ts +108 -22
- package/src/core/logger.ts +1 -0
- package/src/core/metrics.ts +105 -0
- package/src/index.ts +97 -24
- package/src/utils/projectType.ts +85 -9
- package/tsconfig.json +1 -1
- package/.github/workflows/auto-assign.yml +0 -14
- package/.github/workflows/build-publish.yml +0 -154
- package/.github/workflows/deploy.yml +0 -336
- package/.github/workflows/monitoring.yml +0 -270
- package/PUBLISHING_FIX.md +0 -92
- package/logs/.8a99b6cf655346317fdbf29f4fffcf91131432f3-audit.json +0 -15
- 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
|
-
|
|
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
|
|
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
|
|
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?/
|
|
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
|
|
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
|
-
|
|
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
|
});
|
package/dist/commands/health.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
console.log(
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
43
|
-
|
|
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
|
+
}
|
package/dist/commands/init.js
CHANGED
|
@@ -12,18 +12,26 @@ export async function init() {
|
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
catch {
|
|
15
|
-
console.log(chalk.yellow('Warning: Could not
|
|
16
|
-
console.log(chalk.yellow('
|
|
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:
|
|
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
|
-
|
|
49
|
-
|
|
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
|
-
|
|
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
|
|
67
|
+
console.log(chalk.red('Configuration cancelled.'));
|
|
57
68
|
}
|
|
58
69
|
}
|
|
59
70
|
}
|
package/dist/commands/menu.js
CHANGED
|
@@ -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:
|
|
15
|
-
{ name: '
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
}
|
package/dist/commands/metrics.js
CHANGED
|
@@ -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
|
-
|
|
6
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
48
|
-
|
|
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}
|
|
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
|
}),
|