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