@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.
Files changed (103) hide show
  1. package/.github/ENTERPRISE.md +275 -0
  2. package/.github/ISSUE_TEMPLATE/bug_report.md +45 -17
  3. package/.github/ISSUE_TEMPLATE/documentation_issue.md +61 -0
  4. package/.github/ISSUE_TEMPLATE/feature_request.md +61 -9
  5. package/.github/ISSUE_TEMPLATE/security_vulnerability.md +74 -0
  6. package/.github/dependabot.yml +42 -2
  7. package/.github/pull_request_template.md +13 -0
  8. package/.github/workflows/build-publish.yml +154 -0
  9. package/.github/workflows/ci.yml +251 -18
  10. package/.github/workflows/contribution.yml +169 -27
  11. package/.github/workflows/dependabot-auto-merge.yml +61 -2
  12. package/.github/workflows/deploy.yml +336 -0
  13. package/.github/workflows/monitoring.yml +270 -0
  14. package/.github/workflows/release.yml +229 -0
  15. package/.github/workflows/security.yml +198 -0
  16. package/.releaserc.json +50 -0
  17. package/AGENTS.md +13 -0
  18. package/CHANGELOG.md +8 -0
  19. package/PUBLISHING_FIX.md +92 -0
  20. package/__tests__/core.test.ts +318 -0
  21. package/__tests__/setup.ts +61 -0
  22. package/__tests__/workflows.test.ts +95 -0
  23. package/dist/commands/explain.js +13 -44
  24. package/dist/commands/fix.js +13 -38
  25. package/dist/commands/generate.js +45 -54
  26. package/dist/commands/health.d.ts +1 -0
  27. package/dist/commands/health.js +376 -0
  28. package/dist/commands/init.js +47 -42
  29. package/dist/commands/menu.js +90 -2
  30. package/dist/commands/metrics.d.ts +1 -0
  31. package/dist/commands/metrics.js +51 -0
  32. package/dist/commands/optimize.js +42 -34
  33. package/dist/commands/review.js +52 -48
  34. package/dist/commands/security-check.js +52 -42
  35. package/dist/commands/status.d.ts +1 -0
  36. package/dist/commands/status.js +45 -0
  37. package/dist/commands/suggest.js +13 -39
  38. package/dist/config/config.js +30 -2
  39. package/dist/core/ai.d.ts +77 -2
  40. package/dist/core/ai.js +207 -30
  41. package/dist/core/command-runner.d.ts +17 -0
  42. package/dist/core/command-runner.js +78 -0
  43. package/dist/core/logger.d.ts +40 -0
  44. package/dist/core/logger.js +138 -0
  45. package/dist/core/metrics.d.ts +34 -0
  46. package/dist/core/metrics.js +206 -0
  47. package/dist/core/prompts.d.ts +1 -0
  48. package/dist/core/prompts.js +121 -0
  49. package/dist/core/security.d.ts +34 -0
  50. package/dist/core/security.js +197 -0
  51. package/dist/index.js +40 -2
  52. package/dist/utils/ux.d.ts +3 -0
  53. package/dist/utils/ux.js +15 -0
  54. package/docs/agents/domain.md +51 -0
  55. package/docs/agents/issue-tracker.md +45 -0
  56. package/docs/agents/triage-labels.md +15 -0
  57. package/docs/api/.nojekyll +1 -0
  58. package/docs/api/assets/hierarchy.js +1 -0
  59. package/docs/api/assets/highlight.css +71 -0
  60. package/docs/api/assets/icons.js +18 -0
  61. package/docs/api/assets/icons.svg +1 -0
  62. package/docs/api/assets/main.js +60 -0
  63. package/docs/api/assets/navigation.js +1 -0
  64. package/docs/api/assets/search.js +1 -0
  65. package/docs/api/assets/style.css +1633 -0
  66. package/docs/api/hierarchy.html +1 -0
  67. package/docs/api/index.html +39 -0
  68. package/docs/api/modules.html +1 -0
  69. package/eslint.config.js +170 -0
  70. package/jest.config.json +37 -0
  71. package/lighthouserc.json +22 -0
  72. package/logs/.8a99b6cf655346317fdbf29f4fffcf91131432f3-audit.json +15 -0
  73. package/logs/.eee104bf8fff5ecd38a6a2842df260de6470a7c3-audit.json +15 -0
  74. package/package.json +62 -8
  75. package/src/commands/explain.ts +13 -42
  76. package/src/commands/fix.ts +13 -31
  77. package/src/commands/generate.ts +47 -46
  78. package/src/commands/health.ts +440 -0
  79. package/src/commands/init.ts +48 -42
  80. package/src/commands/menu.ts +86 -2
  81. package/src/commands/metrics.ts +65 -0
  82. package/src/commands/optimize.ts +40 -28
  83. package/src/commands/review.ts +54 -40
  84. package/src/commands/security-check.ts +54 -34
  85. package/src/commands/status.ts +47 -0
  86. package/src/commands/suggest.ts +13 -32
  87. package/src/config/config.ts +35 -2
  88. package/src/core/ai.ts +237 -26
  89. package/src/core/command-runner.ts +105 -0
  90. package/src/core/logger.ts +194 -0
  91. package/src/core/metrics.ts +232 -0
  92. package/src/core/prompts.ts +128 -0
  93. package/src/core/security.ts +243 -0
  94. package/src/index.ts +47 -2
  95. package/src/utils/ux.ts +18 -0
  96. package/test-suite.sh +147 -0
  97. package/tsconfig.json +3 -2
  98. package/typedoc.json +44 -0
  99. package/types/global.d.ts +13 -0
  100. package/validate-workflows.sh +270 -0
  101. package/.eslintignore +0 -1
  102. package/.eslintrc.cjs +0 -43
  103. package/src/core/ai.test.js +0 -40
@@ -0,0 +1,232 @@
1
+ import promClient from 'prom-client';
2
+ import { logger } from './logger.js';
3
+
4
+ // Create a Registry which registers the metrics
5
+ const register = new promClient.Registry();
6
+
7
+ // Add a default label which is added to all metrics
8
+ register.setDefaultLabels({
9
+ app: 'dhruv-cli'
10
+ });
11
+
12
+ // Enable the collection of default metrics
13
+ promClient.collectDefaultMetrics({ register });
14
+
15
+ // Custom metrics
16
+ export const metrics = {
17
+ // Command execution metrics
18
+ commandDuration: new promClient.Histogram({
19
+ name: 'dhruv_command_duration_seconds',
20
+ help: 'Duration of command execution in seconds',
21
+ labelNames: ['command', 'success'],
22
+ buckets: [0.1, 0.5, 1, 2, 5, 10, 30]
23
+ }),
24
+
25
+ commandCount: new promClient.Counter({
26
+ name: 'dhruv_command_total',
27
+ help: 'Total number of commands executed',
28
+ labelNames: ['command', 'success']
29
+ }),
30
+
31
+ // AI service metrics
32
+ aiRequestDuration: new promClient.Histogram({
33
+ name: 'dhruv_ai_request_duration_seconds',
34
+ help: 'Duration of AI requests in seconds',
35
+ labelNames: ['model', 'command_type'],
36
+ buckets: [1, 5, 10, 30, 60, 120]
37
+ }),
38
+
39
+ aiRequestCount: new promClient.Counter({
40
+ name: 'dhruv_ai_request_total',
41
+ help: 'Total number of AI requests',
42
+ labelNames: ['model', 'command_type', 'success']
43
+ }),
44
+
45
+ aiTokensUsed: new promClient.Counter({
46
+ name: 'dhruv_ai_tokens_total',
47
+ help: 'Total number of tokens used in AI requests',
48
+ labelNames: ['model', 'command_type']
49
+ }),
50
+
51
+ // Cache metrics
52
+ cacheHitCount: new promClient.Counter({
53
+ name: 'dhruv_cache_hit_total',
54
+ help: 'Total number of cache hits',
55
+ labelNames: ['cache_type']
56
+ }),
57
+
58
+ cacheMissCount: new promClient.Counter({
59
+ name: 'dhruv_cache_miss_total',
60
+ help: 'Total number of cache misses',
61
+ labelNames: ['cache_type']
62
+ }),
63
+
64
+ cacheSize: new promClient.Gauge({
65
+ name: 'dhruv_cache_size_bytes',
66
+ help: 'Current size of cache in bytes',
67
+ labelNames: ['cache_type']
68
+ }),
69
+
70
+ // Error metrics
71
+ errorCount: new promClient.Counter({
72
+ name: 'dhruv_error_total',
73
+ help: 'Total number of errors',
74
+ labelNames: ['error_type', 'command']
75
+ }),
76
+
77
+ // Performance metrics
78
+ memoryUsage: new promClient.Gauge({
79
+ name: 'dhruv_memory_usage_bytes',
80
+ help: 'Current memory usage in bytes',
81
+ labelNames: ['type']
82
+ }),
83
+
84
+ // User engagement metrics
85
+ sessionCount: new promClient.Counter({
86
+ name: 'dhruv_session_total',
87
+ help: 'Total number of user sessions'
88
+ }),
89
+
90
+ pluginLoadedCount: new promClient.Counter({
91
+ name: 'dhruv_plugin_loaded_total',
92
+ help: 'Total number of plugins loaded'
93
+ })
94
+ };
95
+
96
+ // Register all metrics
97
+ Object.values(metrics).forEach(metric => {
98
+ register.registerMetric(metric);
99
+ });
100
+
101
+ export class MetricsCollector {
102
+ private static instance: MetricsCollector;
103
+ private metricsEnabled: boolean;
104
+
105
+ private constructor() {
106
+ this.metricsEnabled = process.env.DHRUV_METRICS_ENABLED !== 'false';
107
+ if (this.metricsEnabled) {
108
+ logger.info('Metrics collection enabled');
109
+ }
110
+ }
111
+
112
+ public static getInstance(): MetricsCollector {
113
+ if (!MetricsCollector.instance) {
114
+ MetricsCollector.instance = new MetricsCollector();
115
+ }
116
+ return MetricsCollector.instance;
117
+ }
118
+
119
+ public recordCommand(command: string, duration: number, success: boolean): void {
120
+ if (!this.metricsEnabled) return;
121
+
122
+ try {
123
+ metrics.commandDuration.observe({ command, success: success.toString() }, duration / 1000);
124
+ metrics.commandCount.inc({ command, success: success.toString() });
125
+ } catch (error) {
126
+ logger.error('Failed to record command metrics', error as Error);
127
+ }
128
+ }
129
+
130
+ public recordAIRequest(model: string, commandType: string, duration: number, success: boolean, tokensUsed?: number): void {
131
+ if (!this.metricsEnabled) return;
132
+
133
+ try {
134
+ metrics.aiRequestDuration.observe({ model, command_type: commandType }, duration / 1000);
135
+ metrics.aiRequestCount.inc({ model, command_type: commandType, success: success.toString() });
136
+
137
+ if (tokensUsed) {
138
+ metrics.aiTokensUsed.inc({ model, command_type: commandType }, tokensUsed);
139
+ }
140
+ } catch (error) {
141
+ logger.error('Failed to record AI request metrics', error as Error);
142
+ }
143
+ }
144
+
145
+ public recordCacheHit(cacheType: string): void {
146
+ if (!this.metricsEnabled) return;
147
+
148
+ try {
149
+ metrics.cacheHitCount.inc({ cache_type: cacheType });
150
+ } catch (error) {
151
+ logger.error('Failed to record cache hit metrics', error as Error);
152
+ }
153
+ }
154
+
155
+ public recordCacheMiss(cacheType: string): void {
156
+ if (!this.metricsEnabled) return;
157
+
158
+ try {
159
+ metrics.cacheMissCount.inc({ cache_type: cacheType });
160
+ } catch (error) {
161
+ logger.error('Failed to record cache miss metrics', error as Error);
162
+ }
163
+ }
164
+
165
+ public updateCacheSize(cacheType: string, size: number): void {
166
+ if (!this.metricsEnabled) return;
167
+
168
+ try {
169
+ metrics.cacheSize.set({ cache_type: cacheType }, size);
170
+ } catch (error) {
171
+ logger.error('Failed to update cache size metrics', error as Error);
172
+ }
173
+ }
174
+
175
+ public recordError(errorType: string, command?: string): void {
176
+ if (!this.metricsEnabled) return;
177
+
178
+ try {
179
+ metrics.errorCount.inc({ error_type: errorType, command: command || 'unknown' });
180
+ } catch (error) {
181
+ logger.error('Failed to record error metrics', error as Error);
182
+ }
183
+ }
184
+
185
+ public updateMemoryUsage(): void {
186
+ if (!this.metricsEnabled) return;
187
+
188
+ try {
189
+ const memUsage = process.memoryUsage();
190
+ metrics.memoryUsage.set({ type: 'rss' }, memUsage.rss);
191
+ metrics.memoryUsage.set({ type: 'heap_used' }, memUsage.heapUsed);
192
+ metrics.memoryUsage.set({ type: 'heap_total' }, memUsage.heapTotal);
193
+ metrics.memoryUsage.set({ type: 'external' }, memUsage.external);
194
+ } catch (error) {
195
+ logger.error('Failed to update memory usage metrics', error as Error);
196
+ }
197
+ }
198
+
199
+ public recordSession(): void {
200
+ if (!this.metricsEnabled) return;
201
+
202
+ try {
203
+ metrics.sessionCount.inc();
204
+ } catch (error) {
205
+ logger.error('Failed to record session metrics', error as Error);
206
+ }
207
+ }
208
+
209
+ public recordPluginLoaded(): void {
210
+ if (!this.metricsEnabled) return;
211
+
212
+ try {
213
+ metrics.pluginLoadedCount.inc();
214
+ } catch (error) {
215
+ logger.error('Failed to record plugin loaded metrics', error as Error);
216
+ }
217
+ }
218
+
219
+ public async getMetrics(): Promise<string> {
220
+ return register.metrics();
221
+ }
222
+
223
+ public async getMetricsJSON(): Promise<promClient.MetricObjectWithValues<promClient.MetricValue<string>>[]> {
224
+ return register.getMetricsAsJSON();
225
+ }
226
+
227
+ public getRegistry(): promClient.Registry {
228
+ return register;
229
+ }
230
+ }
231
+
232
+ export const metricsCollector = MetricsCollector.getInstance();
@@ -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,15 @@ 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
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';
17
23
  const require = createRequire(import.meta.url);
18
24
  const pkg = require('../package.json');
19
25
 
@@ -27,16 +33,19 @@ program
27
33
  program
28
34
  .command('explain <query>')
29
35
  .description('Explain a concept or command')
36
+ .addHelpText('after', '\nExamples:\n $ dhruv explain "What is async/await?"\n $ dhruv explain "Docker containers vs VMs"')
30
37
  .action(explain);
31
38
 
32
39
  program
33
40
  .command('suggest <query>')
34
41
  .description('Get AI-powered suggestions')
42
+ .addHelpText('after', '\nExamples:\n $ dhruv suggest "React performance optimization"\n $ dhruv suggest "Node.js project structure"')
35
43
  .action(suggest);
36
44
 
37
45
  program
38
46
  .command('fix <query>')
39
47
  .description('Get a fix for a coding issue or error')
48
+ .addHelpText('after', '\nExamples:\n $ dhruv fix "TypeError: Cannot read property of undefined"\n $ dhruv fix "CORS error in Express.js"')
40
49
  .action(fix);
41
50
 
42
51
  program
@@ -64,6 +73,21 @@ program
64
73
  .description('Interactive setup/configuration wizard')
65
74
  .action(init);
66
75
 
76
+ program
77
+ .command('status')
78
+ .description('Check Ollama connection and available models')
79
+ .action(status);
80
+
81
+ program
82
+ .command('health')
83
+ .description('Run comprehensive health check')
84
+ .action(health);
85
+
86
+ program
87
+ .command('metrics')
88
+ .description('Display CLI usage metrics')
89
+ .action(metrics);
90
+
67
91
  program
68
92
  .command('project-type')
69
93
  .description('Detect and print the current project type')
@@ -113,8 +137,29 @@ async function loadPlugins(program: unknown) {
113
137
  }
114
138
 
115
139
  (async () => {
116
- await loadPlugins(program);
117
- program.parse(process.argv);
140
+ // Initialize enterprise features
141
+ try {
142
+ logInfo('Dhruv CLI starting', {
143
+ version: pkg.version,
144
+ nodeVersion: process.version,
145
+ platform: process.platform
146
+ });
147
+
148
+ // Record session start
149
+ metricsCollector.recordSession();
150
+
151
+ await loadPlugins(program);
152
+ program.parse(process.argv);
153
+
154
+ // Record successful session
155
+ const sessionId = logger.getSessionId();
156
+ logInfo('CLI session completed', { sessionId });
157
+
158
+ } catch (error) {
159
+ logError('CLI startup failed', error as Error);
160
+ console.error(chalk.red('Failed to start Dhruv CLI:'), (error as Error).message);
161
+ process.exit(1);
162
+ }
118
163
  })();
119
164
 
120
165
  // Autocomplete: Generate shell completion scripts