@rahul05ranjan/dhruv-cli 1.3.0 → 1.5.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/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/ci.yml +51 -38
- package/.github/workflows/contribution.yml +41 -34
- package/.github/workflows/dependabot-auto-merge.yml +62 -2
- package/.github/workflows/labeler.yml +1 -0
- package/.github/workflows/release.yml +229 -0
- package/.github/workflows/security.yml +201 -0
- package/.releaserc.json +50 -0
- package/AGENTS.md +13 -0
- package/CHANGELOG.md +13 -0
- package/README.md +145 -40
- package/__tests__/cli-contract.test.ts +104 -0
- package/__tests__/core.test.ts +439 -0
- package/__tests__/diagnostics.test.ts +155 -0
- package/__tests__/file-workflows.test.ts +195 -0
- package/__tests__/interactive.test.ts +119 -0
- package/__tests__/setup.ts +62 -0
- package/__tests__/workflows.test.ts +118 -0
- package/dist/commands/explain.js +13 -44
- package/dist/commands/fix.js +13 -38
- package/dist/commands/generate.d.ts +6 -1
- package/dist/commands/generate.js +60 -55
- package/dist/commands/health.d.ts +4 -0
- package/dist/commands/health.js +419 -0
- package/dist/commands/init.js +56 -42
- package/dist/commands/menu.js +137 -24
- package/dist/commands/metrics.d.ts +5 -0
- package/dist/commands/metrics.js +80 -0
- package/dist/commands/optimize.js +42 -34
- package/dist/commands/review.d.ts +4 -1
- package/dist/commands/review.js +87 -43
- package/dist/commands/security-check.d.ts +4 -1
- package/dist/commands/security-check.js +109 -38
- package/dist/commands/status.d.ts +1 -0
- package/dist/commands/status.js +83 -0
- package/dist/commands/suggest.js +13 -39
- package/dist/config/config.d.ts +5 -1
- package/dist/config/config.js +53 -8
- package/dist/core/ai.d.ts +88 -2
- package/dist/core/ai.js +231 -30
- package/dist/core/command-catalog.d.ts +10 -0
- package/dist/core/command-catalog.js +27 -0
- package/dist/core/command-runner.d.ts +17 -0
- package/dist/core/command-runner.js +157 -0
- package/dist/core/logger.d.ts +40 -0
- package/dist/core/logger.js +139 -0
- package/dist/core/metrics.d.ts +62 -0
- package/dist/core/metrics.js +284 -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 +84 -22
- package/dist/utils/projectType.d.ts +1 -1
- package/dist/utils/projectType.js +26 -7
- 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 +161 -0
- 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/api/modules.html +1 -0
- package/docs/dhruv-cli-preview.svg +42 -0
- package/docs/index.html +631 -533
- package/docs/publishing-fix.md +34 -0
- package/eslint.config.js +170 -0
- package/jest.config.json +37 -0
- package/lighthouserc.json +22 -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 +68 -48
- package/src/commands/health.ts +485 -0
- package/src/commands/init.ts +57 -42
- package/src/commands/menu.ts +132 -24
- package/src/commands/metrics.ts +100 -0
- package/src/commands/optimize.ts +40 -28
- package/src/commands/review.ts +96 -37
- package/src/commands/security-check.ts +127 -33
- package/src/commands/status.ts +83 -0
- package/src/commands/suggest.ts +13 -32
- package/src/config/config.ts +60 -8
- package/src/core/ai.ts +265 -26
- package/src/core/command-catalog.ts +37 -0
- package/src/core/command-runner.ts +185 -0
- package/src/core/logger.ts +195 -0
- package/src/core/metrics.ts +335 -0
- package/src/core/prompts.ts +128 -0
- package/src/core/security.ts +243 -0
- package/src/index.ts +90 -22
- package/src/utils/projectType.ts +22 -7
- package/src/utils/ux.ts +18 -0
- package/test-suite.sh +147 -0
- package/tsconfig.json +4 -3
- 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/.github/workflows/auto-assign.yml +0 -14
- package/src/core/ai.test.js +0 -40
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* System-message templates per command type. Pure data: no AI plumbing.
|
|
3
|
+
*/
|
|
4
|
+
const SYSTEM_MESSAGES: Record<string, string> = {
|
|
5
|
+
explain: `You are a knowledgeable programming instructor and technical expert. Your role is to provide clear, accurate, and practical explanations of technical concepts.
|
|
6
|
+
|
|
7
|
+
Guidelines:
|
|
8
|
+
- Provide structured explanations with clear sections
|
|
9
|
+
- Use practical examples where helpful
|
|
10
|
+
- Focus on concepts that developers need to understand
|
|
11
|
+
- Avoid unnecessary jargon, but use precise technical terminology
|
|
12
|
+
- Keep responses concise but comprehensive
|
|
13
|
+
- Format code examples clearly
|
|
14
|
+
- Provide context for why something matters
|
|
15
|
+
|
|
16
|
+
Output Format:
|
|
17
|
+
- Use plain text with clear structure
|
|
18
|
+
- Use bullet points for lists
|
|
19
|
+
- Use numbered steps for procedures
|
|
20
|
+
- Separate code examples with clear labels
|
|
21
|
+
- Avoid markdown formatting except for code blocks when absolutely necessary`,
|
|
22
|
+
|
|
23
|
+
suggest: `You are an expert software architect and senior developer providing actionable recommendations and best practices.
|
|
24
|
+
|
|
25
|
+
Guidelines:
|
|
26
|
+
- Provide specific, actionable suggestions
|
|
27
|
+
- Prioritize recommendations by importance
|
|
28
|
+
- Include practical implementation steps
|
|
29
|
+
- Mention relevant tools, libraries, or patterns
|
|
30
|
+
- Consider performance, maintainability, and scalability
|
|
31
|
+
- Provide concrete examples where helpful
|
|
32
|
+
- Focus on industry best practices
|
|
33
|
+
|
|
34
|
+
Output Format:
|
|
35
|
+
- Use clear numbered or bulleted lists
|
|
36
|
+
- Separate different types of suggestions
|
|
37
|
+
- Provide brief explanations for each recommendation
|
|
38
|
+
- Use plain text formatting for better readability`,
|
|
39
|
+
|
|
40
|
+
fix: `You are a debugging expert and problem-solving specialist helping developers resolve technical issues.
|
|
41
|
+
|
|
42
|
+
Guidelines:
|
|
43
|
+
- Analyze the problem systematically
|
|
44
|
+
- Identify the root cause
|
|
45
|
+
- Provide step-by-step solutions
|
|
46
|
+
- Include preventive measures
|
|
47
|
+
- Show corrected code examples
|
|
48
|
+
- Explain why the fix works
|
|
49
|
+
- Suggest testing approaches
|
|
50
|
+
|
|
51
|
+
Output Format:
|
|
52
|
+
- Start with problem analysis
|
|
53
|
+
- Provide clear solution steps
|
|
54
|
+
- Show before/after code examples
|
|
55
|
+
- Use plain text with clear structure
|
|
56
|
+
- Avoid complex markdown formatting`,
|
|
57
|
+
|
|
58
|
+
review: `You are a senior code reviewer focused on code quality, best practices, and maintainability.
|
|
59
|
+
|
|
60
|
+
Guidelines:
|
|
61
|
+
- Analyze code structure and patterns
|
|
62
|
+
- Identify potential issues or improvements
|
|
63
|
+
- Comment on performance implications
|
|
64
|
+
- Suggest refactoring opportunities
|
|
65
|
+
- Check for security considerations
|
|
66
|
+
- Evaluate readability and maintainability
|
|
67
|
+
- Provide constructive feedback
|
|
68
|
+
|
|
69
|
+
Output Format:
|
|
70
|
+
- Organize feedback by categories (Structure, Performance, Security, etc.)
|
|
71
|
+
- Use clear, actionable language
|
|
72
|
+
- Provide specific line-by-line suggestions where relevant
|
|
73
|
+
- Use plain text formatting for better CLI readability`,
|
|
74
|
+
|
|
75
|
+
optimize: `You are a performance optimization expert specializing in code efficiency and best practices.
|
|
76
|
+
|
|
77
|
+
Guidelines:
|
|
78
|
+
- Focus on measurable performance improvements
|
|
79
|
+
- Consider different types of optimization (runtime, memory, bundle size, etc.)
|
|
80
|
+
- Provide specific, implementable suggestions
|
|
81
|
+
- Explain the impact of each optimization
|
|
82
|
+
- Consider trade-offs between performance and maintainability
|
|
83
|
+
- Suggest profiling and measurement approaches
|
|
84
|
+
|
|
85
|
+
Output Format:
|
|
86
|
+
- Categorize optimizations by type
|
|
87
|
+
- Provide clear before/after examples
|
|
88
|
+
- Include estimated impact where possible
|
|
89
|
+
- Use structured plain text formatting`,
|
|
90
|
+
|
|
91
|
+
security: `You are a cybersecurity expert specializing in application security and secure coding practices.
|
|
92
|
+
|
|
93
|
+
Guidelines:
|
|
94
|
+
- Identify potential security vulnerabilities
|
|
95
|
+
- Provide remediation steps
|
|
96
|
+
- Suggest secure coding patterns
|
|
97
|
+
- Consider common attack vectors
|
|
98
|
+
- Recommend security tools and practices
|
|
99
|
+
- Focus on practical security measures
|
|
100
|
+
- Explain security implications
|
|
101
|
+
|
|
102
|
+
Output Format:
|
|
103
|
+
- Categorize findings by severity
|
|
104
|
+
- Provide clear remediation steps
|
|
105
|
+
- Use plain text formatting for better readability
|
|
106
|
+
- Include references to security standards where relevant`,
|
|
107
|
+
|
|
108
|
+
generate: `You are a code generation specialist creating high-quality, well-structured code.
|
|
109
|
+
|
|
110
|
+
Guidelines:
|
|
111
|
+
- Generate clean, readable code
|
|
112
|
+
- Follow established conventions
|
|
113
|
+
- Include appropriate comments
|
|
114
|
+
- Consider edge cases
|
|
115
|
+
- Use proper error handling
|
|
116
|
+
- Follow best practices for the target language
|
|
117
|
+
- Generate comprehensive test cases when requested
|
|
118
|
+
|
|
119
|
+
Output Format:
|
|
120
|
+
- Provide clean code without excessive markdown
|
|
121
|
+
- Use minimal formatting for better CLI display
|
|
122
|
+
- Include brief explanations only when necessary
|
|
123
|
+
- Focus on practical, working code`,
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export function getSystemMessage(type: string): string {
|
|
127
|
+
return SYSTEM_MESSAGES[type] ?? SYSTEM_MESSAGES.explain;
|
|
128
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import Joi from 'joi';
|
|
2
|
+
import validator from 'validator';
|
|
3
|
+
import { logger, logSecurity } from './logger.js';
|
|
4
|
+
import { metricsCollector } from './metrics.js';
|
|
5
|
+
|
|
6
|
+
export interface SecurityConfig {
|
|
7
|
+
enableInputValidation: boolean;
|
|
8
|
+
enableRateLimiting: boolean;
|
|
9
|
+
maxInputLength: number;
|
|
10
|
+
allowedCommands: string[];
|
|
11
|
+
blockedPatterns: RegExp[];
|
|
12
|
+
maxRequestsPerMinute: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class SecurityManager {
|
|
16
|
+
private static instance: SecurityManager;
|
|
17
|
+
private config: SecurityConfig;
|
|
18
|
+
private requestCounts: Map<string, { count: number; resetTime: number }> = new Map();
|
|
19
|
+
private schemas: { [key: string]: Joi.ObjectSchema } = {};
|
|
20
|
+
|
|
21
|
+
private constructor() {
|
|
22
|
+
this.config = {
|
|
23
|
+
enableInputValidation: process.env.DHRUV_SECURITY_VALIDATION !== 'false',
|
|
24
|
+
enableRateLimiting: process.env.DHRUV_SECURITY_RATE_LIMIT !== 'false',
|
|
25
|
+
maxInputLength: parseInt(process.env.DHRUV_MAX_INPUT_LENGTH || '10000'),
|
|
26
|
+
allowedCommands: [
|
|
27
|
+
'explain', 'suggest', 'fix', 'review', 'optimize',
|
|
28
|
+
'security-check', 'generate', 'init', 'status',
|
|
29
|
+
'project-type', 'menu', 'completion', 'help'
|
|
30
|
+
],
|
|
31
|
+
blockedPatterns: [
|
|
32
|
+
/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, // XSS patterns
|
|
33
|
+
/\b(eval|exec|system|shell_exec|passthru|proc_open|popen)\b/i, // Code execution
|
|
34
|
+
/\b(rm\s+(-rf|--force)\s+|del\s+)/i, // File deletion
|
|
35
|
+
/\b(format|fdisk|mkfs)\b/i, // Disk operations
|
|
36
|
+
/password\s*=\s*['"][^'"]*['"]/i, // Password exposure
|
|
37
|
+
/\b(ssh|scp|rsync)\s+.*@\w+/i, // SSH operations
|
|
38
|
+
],
|
|
39
|
+
maxRequestsPerMinute: parseInt(process.env.DHRUV_RATE_LIMIT || '60')
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
this.initializeSchemas();
|
|
43
|
+
|
|
44
|
+
logger.info('Security manager initialized', {
|
|
45
|
+
config: {
|
|
46
|
+
...this.config,
|
|
47
|
+
blockedPatterns: this.config.blockedPatterns.map(p => p.toString())
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private initializeSchemas(): void {
|
|
53
|
+
this.schemas = {
|
|
54
|
+
explain: Joi.object({
|
|
55
|
+
query: Joi.string()
|
|
56
|
+
.min(1)
|
|
57
|
+
.max(this.config.maxInputLength)
|
|
58
|
+
.required()
|
|
59
|
+
.messages({
|
|
60
|
+
'string.empty': 'Query cannot be empty',
|
|
61
|
+
'string.max': `Query too long (max ${this.config.maxInputLength} characters)`
|
|
62
|
+
})
|
|
63
|
+
}),
|
|
64
|
+
|
|
65
|
+
suggest: Joi.object({
|
|
66
|
+
query: Joi.string()
|
|
67
|
+
.min(1)
|
|
68
|
+
.max(this.config.maxInputLength)
|
|
69
|
+
.required()
|
|
70
|
+
}),
|
|
71
|
+
|
|
72
|
+
fix: Joi.object({
|
|
73
|
+
query: Joi.string()
|
|
74
|
+
.min(1)
|
|
75
|
+
.max(this.config.maxInputLength)
|
|
76
|
+
.required()
|
|
77
|
+
}),
|
|
78
|
+
|
|
79
|
+
review: Joi.object({
|
|
80
|
+
fileOrDir: Joi.string()
|
|
81
|
+
.min(1)
|
|
82
|
+
.max(500)
|
|
83
|
+
.pattern(/^[^<>&|;$`]*$/) // Prevent shell injection
|
|
84
|
+
.required()
|
|
85
|
+
}),
|
|
86
|
+
|
|
87
|
+
optimize: Joi.object({
|
|
88
|
+
file: Joi.string()
|
|
89
|
+
.min(1)
|
|
90
|
+
.max(500)
|
|
91
|
+
.pattern(/^[^<>&|;$`]*$/)
|
|
92
|
+
.required()
|
|
93
|
+
}),
|
|
94
|
+
|
|
95
|
+
generate: Joi.object({
|
|
96
|
+
type: Joi.string()
|
|
97
|
+
.valid('tests', 'documentation', 'docs', 'component')
|
|
98
|
+
.required(),
|
|
99
|
+
target: Joi.string()
|
|
100
|
+
.min(1)
|
|
101
|
+
.max(500)
|
|
102
|
+
.pattern(/^[^<>&|;$`]*$/)
|
|
103
|
+
.required()
|
|
104
|
+
})
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
public static getInstance(): SecurityManager {
|
|
109
|
+
if (!SecurityManager.instance) {
|
|
110
|
+
SecurityManager.instance = new SecurityManager();
|
|
111
|
+
}
|
|
112
|
+
return SecurityManager.instance;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
public validateInput(command: string, input: any): { valid: boolean; error?: string } {
|
|
116
|
+
if (!this.config.enableInputValidation) {
|
|
117
|
+
return { valid: true };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
// Check command is allowed
|
|
122
|
+
if (!this.config.allowedCommands.includes(command)) {
|
|
123
|
+
const error = `Command '${command}' is not allowed`;
|
|
124
|
+
logSecurity('blocked_command', { command, input });
|
|
125
|
+
metricsCollector.recordError('blocked_command', command);
|
|
126
|
+
return { valid: false, error };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Get schema for command
|
|
130
|
+
const schema = this.schemas[command];
|
|
131
|
+
if (!schema) {
|
|
132
|
+
return { valid: true }; // No specific validation for this command
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Validate input
|
|
136
|
+
const { error } = schema.validate(input, { abortEarly: false });
|
|
137
|
+
if (error) {
|
|
138
|
+
const errorMessage = error.details.map((d: any) => d.message).join(', ');
|
|
139
|
+
logSecurity('invalid_input', { command, input, error: errorMessage });
|
|
140
|
+
metricsCollector.recordError('invalid_input', command);
|
|
141
|
+
return { valid: false, error: errorMessage };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Check for blocked patterns
|
|
145
|
+
const inputString = JSON.stringify(input);
|
|
146
|
+
for (const pattern of this.config.blockedPatterns) {
|
|
147
|
+
if (pattern.test(inputString)) {
|
|
148
|
+
const error = 'Input contains blocked patterns';
|
|
149
|
+
logSecurity('blocked_pattern', { command, input, pattern: pattern.toString() });
|
|
150
|
+
metricsCollector.recordError('blocked_pattern', command);
|
|
151
|
+
return { valid: false, error };
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Sanitize input
|
|
156
|
+
const sanitized = this.sanitizeInput(input);
|
|
157
|
+
if (JSON.stringify(sanitized) !== JSON.stringify(input)) {
|
|
158
|
+
logger.warn('Input was sanitized', { command, original: input, sanitized });
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return { valid: true };
|
|
162
|
+
} catch (err) {
|
|
163
|
+
const error = `Validation error: ${(err as Error).message}`;
|
|
164
|
+
logSecurity('validation_error', { command, input, error });
|
|
165
|
+
metricsCollector.recordError('validation_error', command);
|
|
166
|
+
return { valid: false, error };
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
public checkRateLimit(identifier: string): { allowed: boolean; remainingRequests: number } {
|
|
171
|
+
if (!this.config.enableRateLimiting) {
|
|
172
|
+
return { allowed: true, remainingRequests: this.config.maxRequestsPerMinute };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const now = Date.now();
|
|
176
|
+
const windowStart = Math.floor(now / 60000) * 60000; // 1-minute windows
|
|
177
|
+
const windowKey = `${identifier}:${windowStart}`;
|
|
178
|
+
|
|
179
|
+
const current = this.requestCounts.get(windowKey) || { count: 0, resetTime: windowStart + 60000 };
|
|
180
|
+
|
|
181
|
+
if (now > current.resetTime) {
|
|
182
|
+
// Reset window
|
|
183
|
+
this.requestCounts.set(windowKey, { count: 1, resetTime: windowStart + 60000 });
|
|
184
|
+
return { allowed: true, remainingRequests: this.config.maxRequestsPerMinute - 1 };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (current.count >= this.config.maxRequestsPerMinute) {
|
|
188
|
+
logSecurity('rate_limit_exceeded', { identifier, count: current.count });
|
|
189
|
+
metricsCollector.recordError('rate_limit_exceeded', 'unknown');
|
|
190
|
+
return { allowed: false, remainingRequests: 0 };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
current.count++;
|
|
194
|
+
const remaining = Math.max(0, this.config.maxRequestsPerMinute - current.count);
|
|
195
|
+
return { allowed: true, remainingRequests: remaining };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
public sanitizeInput(input: any): any {
|
|
199
|
+
if (typeof input === 'string') {
|
|
200
|
+
return validator.escape(input);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (Array.isArray(input)) {
|
|
204
|
+
return input.map(item => this.sanitizeInput(item));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (typeof input === 'object' && input !== null) {
|
|
208
|
+
const sanitized: any = {};
|
|
209
|
+
for (const [key, value] of Object.entries(input)) {
|
|
210
|
+
sanitized[key] = this.sanitizeInput(value);
|
|
211
|
+
}
|
|
212
|
+
return sanitized;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return input;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
public auditLog(action: string, details: Record<string, any>): void {
|
|
219
|
+
logSecurity(action, {
|
|
220
|
+
timestamp: new Date().toISOString(),
|
|
221
|
+
...details
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
public getSecurityStatus(): {
|
|
226
|
+
config: SecurityConfig;
|
|
227
|
+
activeRateLimits: number;
|
|
228
|
+
blockedPatternsCount: number;
|
|
229
|
+
} {
|
|
230
|
+
return {
|
|
231
|
+
config: this.config,
|
|
232
|
+
activeRateLimits: this.requestCounts.size,
|
|
233
|
+
blockedPatternsCount: this.config.blockedPatterns.length
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
public resetRateLimits(): void {
|
|
238
|
+
this.requestCounts.clear();
|
|
239
|
+
logger.info('Rate limits reset');
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export const securityManager = SecurityManager.getInstance();
|
package/src/index.ts
CHANGED
|
@@ -11,9 +11,18 @@ import { optimize } from './commands/optimize.js';
|
|
|
11
11
|
import { securityCheck } from './commands/security-check.js';
|
|
12
12
|
import { generate } from './commands/generate.js';
|
|
13
13
|
import { init } from './commands/init.js';
|
|
14
|
+
import { status } from './commands/status.js';
|
|
15
|
+
import { health } from './commands/health.js';
|
|
16
|
+
import { metrics } from './commands/metrics.js';
|
|
14
17
|
import { detectProjectType } from './utils/projectType.js';
|
|
15
18
|
import { menu } from './commands/menu.js';
|
|
16
|
-
import
|
|
19
|
+
import { createRequire } from 'module';
|
|
20
|
+
import { logger, logCommand, logInfo, logError } from './core/logger.js';
|
|
21
|
+
import { metricsCollector } from './core/metrics.js';
|
|
22
|
+
import { securityManager } from './core/security.js';
|
|
23
|
+
import { commandDescription, completionCommands, completionOptions } from './core/command-catalog.js';
|
|
24
|
+
const require = createRequire(import.meta.url);
|
|
25
|
+
const pkg = require('../package.json');
|
|
17
26
|
|
|
18
27
|
const program = new Command();
|
|
19
28
|
|
|
@@ -24,47 +33,73 @@ program
|
|
|
24
33
|
|
|
25
34
|
program
|
|
26
35
|
.command('explain <query>')
|
|
27
|
-
.description('
|
|
36
|
+
.description(commandDescription('explain'))
|
|
37
|
+
.addHelpText('after', '\nExamples:\n $ dhruv explain "What is async/await?"\n $ dhruv explain "Docker containers vs VMs"')
|
|
28
38
|
.action(explain);
|
|
29
39
|
|
|
30
40
|
program
|
|
31
41
|
.command('suggest <query>')
|
|
32
|
-
.description('
|
|
42
|
+
.description(commandDescription('suggest'))
|
|
43
|
+
.addHelpText('after', '\nExamples:\n $ dhruv suggest "React performance optimization"\n $ dhruv suggest "Node.js project structure"')
|
|
33
44
|
.action(suggest);
|
|
34
45
|
|
|
35
46
|
program
|
|
36
47
|
.command('fix <query>')
|
|
37
|
-
.description('
|
|
48
|
+
.description(commandDescription('fix'))
|
|
49
|
+
.addHelpText('after', '\nExamples:\n $ dhruv fix "TypeError: Cannot read property of undefined"\n $ dhruv fix "CORS error in Express.js"')
|
|
38
50
|
.action(fix);
|
|
39
51
|
|
|
40
52
|
program
|
|
41
53
|
.command('review <fileOrDir>')
|
|
42
|
-
.description('
|
|
43
|
-
.
|
|
54
|
+
.description(commandDescription('review'))
|
|
55
|
+
.option('--diff', 'Review the current uncommitted git diff')
|
|
56
|
+
.action((fileOrDir: string, command: Command) => review(fileOrDir, command.opts()));
|
|
44
57
|
|
|
45
58
|
program
|
|
46
59
|
.command('optimize <file>')
|
|
47
|
-
.description(
|
|
60
|
+
.description(commandDescription('optimize'))
|
|
48
61
|
.action(optimize);
|
|
49
62
|
|
|
50
63
|
program
|
|
51
64
|
.command('security-check [fileOrDir]')
|
|
52
|
-
.description('
|
|
53
|
-
.
|
|
65
|
+
.description(commandDescription('security-check'))
|
|
66
|
+
.option('--strict', 'Exit with failure when high-confidence findings are detected')
|
|
67
|
+
.action((fileOrDir: string | undefined, command: Command) => securityCheck(fileOrDir, command.opts()));
|
|
54
68
|
|
|
55
69
|
program
|
|
56
70
|
.command('generate <type> <target>')
|
|
57
|
-
.description('
|
|
58
|
-
.
|
|
71
|
+
.description(commandDescription('generate'))
|
|
72
|
+
.option('--apply', 'Write generated tests to disk (preview is the default)')
|
|
73
|
+
.option('--output <path>', 'Write generated tests to this path')
|
|
74
|
+
.option('--overwrite', 'Allow replacing an existing output file')
|
|
75
|
+
.action((type: string, target: string, command: Command) => generate(type, target, command.opts()));
|
|
59
76
|
|
|
60
77
|
program
|
|
61
78
|
.command('init')
|
|
62
|
-
.description('
|
|
79
|
+
.description(commandDescription('init'))
|
|
63
80
|
.action(init);
|
|
64
81
|
|
|
82
|
+
program
|
|
83
|
+
.command('status')
|
|
84
|
+
.description(commandDescription('status'))
|
|
85
|
+
.action(status);
|
|
86
|
+
|
|
87
|
+
program
|
|
88
|
+
.command('health')
|
|
89
|
+
.description(commandDescription('health'))
|
|
90
|
+
.option('--details', 'Show every health check and diagnostic detail')
|
|
91
|
+
.action((command: Command) => health(command.opts()));
|
|
92
|
+
|
|
93
|
+
program
|
|
94
|
+
.command('metrics')
|
|
95
|
+
.description(commandDescription('metrics'))
|
|
96
|
+
.option('--raw', 'Export raw Prometheus metrics')
|
|
97
|
+
.option('--reset', 'Clear persisted local metrics')
|
|
98
|
+
.action((command: Command) => metrics(command.opts()));
|
|
99
|
+
|
|
65
100
|
program
|
|
66
101
|
.command('project-type')
|
|
67
|
-
.description('
|
|
102
|
+
.description(commandDescription('project-type'))
|
|
68
103
|
.action(() => {
|
|
69
104
|
const type = detectProjectType();
|
|
70
105
|
console.log(chalk.blue(`Detected project type: ${type}`));
|
|
@@ -72,20 +107,22 @@ program
|
|
|
72
107
|
|
|
73
108
|
program
|
|
74
109
|
.command('menu')
|
|
75
|
-
.description('
|
|
110
|
+
.description(commandDescription('menu'))
|
|
76
111
|
.action(menu);
|
|
77
112
|
|
|
78
113
|
program
|
|
79
114
|
.option('--model <model>', 'Set Ollama model')
|
|
80
115
|
.option('--verbose', 'Enable verbose output')
|
|
81
116
|
.option('--json', 'Output in JSON format')
|
|
117
|
+
.option('--timeout <milliseconds>', 'Set the AI request timeout')
|
|
82
118
|
.hook('preAction', async (thisCommand) => {
|
|
83
119
|
const opts = thisCommand.opts();
|
|
84
|
-
if (opts.model || opts.verbose || opts.json) {
|
|
120
|
+
if (opts.model || opts.verbose || opts.json || opts.timeout) {
|
|
85
121
|
const config: Record<string, unknown> = {};
|
|
86
122
|
if (opts.model) config.model = opts.model;
|
|
87
123
|
if (opts.verbose) config.verbose = true;
|
|
88
124
|
if (opts.json) config.responseFormat = 'json';
|
|
125
|
+
if (opts.timeout) config.timeoutMs = Number(opts.timeout);
|
|
89
126
|
// Save config for session
|
|
90
127
|
const configModule = await import('./config/config.js');
|
|
91
128
|
configModule.saveConfig(config);
|
|
@@ -111,30 +148,61 @@ async function loadPlugins(program: unknown) {
|
|
|
111
148
|
}
|
|
112
149
|
|
|
113
150
|
(async () => {
|
|
114
|
-
|
|
115
|
-
|
|
151
|
+
// Initialize enterprise features
|
|
152
|
+
try {
|
|
153
|
+
logInfo('Dhruv CLI starting', {
|
|
154
|
+
version: pkg.version,
|
|
155
|
+
nodeVersion: process.version,
|
|
156
|
+
platform: process.platform
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Record session start
|
|
160
|
+
metricsCollector.recordSession();
|
|
161
|
+
|
|
162
|
+
await loadPlugins(program);
|
|
163
|
+
program.parse(process.argv);
|
|
164
|
+
|
|
165
|
+
// Record successful session
|
|
166
|
+
const sessionId = logger.getSessionId();
|
|
167
|
+
logInfo('CLI session completed', { sessionId });
|
|
168
|
+
|
|
169
|
+
} catch (error) {
|
|
170
|
+
logError('CLI startup failed', error as Error);
|
|
171
|
+
console.error(chalk.red('Failed to start Dhruv CLI:'), (error as Error).message);
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
116
174
|
})();
|
|
117
175
|
|
|
118
176
|
// Autocomplete: Generate shell completion scripts
|
|
119
177
|
program
|
|
120
178
|
.command('completion')
|
|
121
|
-
.description('
|
|
179
|
+
.description(commandDescription('completion'))
|
|
122
180
|
.argument('[shell]', 'shell type (bash|zsh|fish)', 'bash')
|
|
123
181
|
.action((shell: string) => {
|
|
182
|
+
const commands = completionCommands();
|
|
183
|
+
const options = completionOptions();
|
|
124
184
|
let script = '';
|
|
125
185
|
switch (shell) {
|
|
126
186
|
case 'zsh':
|
|
127
|
-
script = `#compdef dhruv\n_dhruv_completion() {\n
|
|
187
|
+
script = `#compdef dhruv\n_dhruv_completion() {\n _arguments '1:command:(${commands})' '*:option:(${options})'\n}\ncompdef _dhruv_completion dhruv`;
|
|
128
188
|
break;
|
|
129
189
|
case 'fish':
|
|
130
|
-
script = `
|
|
190
|
+
script = `complete -c dhruv -f -n '__fish_use_subcommand' -a '${commands}'\ncomplete -c dhruv -f -n 'not __fish_use_subcommand' -a '${options}'`;
|
|
131
191
|
break;
|
|
132
|
-
|
|
192
|
+
case 'bash':
|
|
133
193
|
script = String.raw`#!/bin/bash
|
|
134
194
|
_dhruv_completion() {
|
|
135
|
-
|
|
195
|
+
local commands="${commands}"
|
|
196
|
+
local options="${options}"
|
|
197
|
+
local choices="$commands $options"
|
|
198
|
+
COMPREPLY=( $(compgen -W "$choices" -- "\${COMP_WORDS[COMP_CWORD]}") )
|
|
136
199
|
}
|
|
137
200
|
complete -F _dhruv_completion dhruv`;
|
|
201
|
+
break;
|
|
202
|
+
default:
|
|
203
|
+
console.error(chalk.red(`Unsupported shell "${shell}". Choose bash, zsh, or fish.`));
|
|
204
|
+
process.exitCode = 2;
|
|
205
|
+
return;
|
|
138
206
|
}
|
|
139
207
|
console.log(script);
|
|
140
208
|
console.log(`\n# To enable tab completion, add the above to your shell profile or source it directly.`);
|
package/src/utils/projectType.ts
CHANGED
|
@@ -1,14 +1,29 @@
|
|
|
1
1
|
// Use .js extension for ESM compatibility
|
|
2
2
|
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
3
4
|
|
|
4
|
-
export function detectProjectType(): string {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
export function detectProjectType(directory: string = process.cwd()): string {
|
|
6
|
+
const file = (name: string) => path.join(directory, name);
|
|
7
|
+
|
|
8
|
+
if (fs.existsSync(file('package.json'))) {
|
|
9
|
+
let pkg: { dependencies?: Record<string, string>; devDependencies?: Record<string, string> };
|
|
10
|
+
try {
|
|
11
|
+
pkg = JSON.parse(fs.readFileSync(file('package.json'), 'utf-8'));
|
|
12
|
+
} catch {
|
|
13
|
+
return 'unknown';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const dependencies = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
17
|
+
if (dependencies.react) return 'react';
|
|
18
|
+
if (dependencies.next) return 'nextjs';
|
|
19
|
+
if (dependencies.express) return 'node-express';
|
|
20
|
+
if (dependencies.typescript || fs.existsSync(file('tsconfig.json'))) return 'node-typescript';
|
|
9
21
|
return 'node';
|
|
10
22
|
}
|
|
11
|
-
if (fs.existsSync('requirements.txt')) return 'python';
|
|
12
|
-
if (fs.existsSync('pyproject.toml')) return 'python';
|
|
23
|
+
if (fs.existsSync(file('requirements.txt'))) return 'python';
|
|
24
|
+
if (fs.existsSync(file('pyproject.toml'))) return 'python';
|
|
25
|
+
if (fs.existsSync(file('go.mod'))) return 'go';
|
|
26
|
+
if (fs.existsSync(file('Cargo.toml'))) return 'rust';
|
|
27
|
+
if (fs.existsSync(file('pom.xml')) || fs.existsSync(file('build.gradle'))) return 'java';
|
|
13
28
|
return 'unknown';
|
|
14
29
|
}
|
package/src/utils/ux.ts
CHANGED
|
@@ -2,6 +2,7 @@ import chalk from 'chalk';
|
|
|
2
2
|
// @ts-expect-error: cli-progress has no type definitions
|
|
3
3
|
import cliProgress from 'cli-progress';
|
|
4
4
|
import { highlight } from 'cli-highlight';
|
|
5
|
+
import ora from 'ora';
|
|
5
6
|
import { loadConfig } from '../config/config.js';
|
|
6
7
|
|
|
7
8
|
function getTheme() {
|
|
@@ -32,6 +33,23 @@ export function printSuccess(message: string) {
|
|
|
32
33
|
console.log(success(' SUCCESS '), success(message));
|
|
33
34
|
}
|
|
34
35
|
|
|
36
|
+
export function printWarning(message: string) {
|
|
37
|
+
const { accent } = getTheme();
|
|
38
|
+
console.warn(chalk.bgYellow.black(' WARNING '), accent(message));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function printInfo(message: string) {
|
|
42
|
+
const { primary } = getTheme();
|
|
43
|
+
console.log(chalk.bgBlue.white(' INFO '), primary(message));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function createSpinner(text: string) {
|
|
47
|
+
return ora({
|
|
48
|
+
text: themed(text, 'primary'),
|
|
49
|
+
color: 'cyan',
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
35
53
|
export function createProgressBar(total: number) {
|
|
36
54
|
const { primary } = getTheme();
|
|
37
55
|
const bar = new cliProgress.SingleBar({
|