@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,206 @@
1
+ import promClient from 'prom-client';
2
+ import { logger } from './logger.js';
3
+ // Create a Registry which registers the metrics
4
+ const register = new promClient.Registry();
5
+ // Add a default label which is added to all metrics
6
+ register.setDefaultLabels({
7
+ app: 'dhruv-cli'
8
+ });
9
+ // Enable the collection of default metrics
10
+ promClient.collectDefaultMetrics({ register });
11
+ // Custom metrics
12
+ export const metrics = {
13
+ // Command execution metrics
14
+ commandDuration: new promClient.Histogram({
15
+ name: 'dhruv_command_duration_seconds',
16
+ help: 'Duration of command execution in seconds',
17
+ labelNames: ['command', 'success'],
18
+ buckets: [0.1, 0.5, 1, 2, 5, 10, 30]
19
+ }),
20
+ commandCount: new promClient.Counter({
21
+ name: 'dhruv_command_total',
22
+ help: 'Total number of commands executed',
23
+ labelNames: ['command', 'success']
24
+ }),
25
+ // AI service metrics
26
+ aiRequestDuration: new promClient.Histogram({
27
+ name: 'dhruv_ai_request_duration_seconds',
28
+ help: 'Duration of AI requests in seconds',
29
+ labelNames: ['model', 'command_type'],
30
+ buckets: [1, 5, 10, 30, 60, 120]
31
+ }),
32
+ aiRequestCount: new promClient.Counter({
33
+ name: 'dhruv_ai_request_total',
34
+ help: 'Total number of AI requests',
35
+ labelNames: ['model', 'command_type', 'success']
36
+ }),
37
+ aiTokensUsed: new promClient.Counter({
38
+ name: 'dhruv_ai_tokens_total',
39
+ help: 'Total number of tokens used in AI requests',
40
+ labelNames: ['model', 'command_type']
41
+ }),
42
+ // Cache metrics
43
+ cacheHitCount: new promClient.Counter({
44
+ name: 'dhruv_cache_hit_total',
45
+ help: 'Total number of cache hits',
46
+ labelNames: ['cache_type']
47
+ }),
48
+ cacheMissCount: new promClient.Counter({
49
+ name: 'dhruv_cache_miss_total',
50
+ help: 'Total number of cache misses',
51
+ labelNames: ['cache_type']
52
+ }),
53
+ cacheSize: new promClient.Gauge({
54
+ name: 'dhruv_cache_size_bytes',
55
+ help: 'Current size of cache in bytes',
56
+ labelNames: ['cache_type']
57
+ }),
58
+ // Error metrics
59
+ errorCount: new promClient.Counter({
60
+ name: 'dhruv_error_total',
61
+ help: 'Total number of errors',
62
+ labelNames: ['error_type', 'command']
63
+ }),
64
+ // Performance metrics
65
+ memoryUsage: new promClient.Gauge({
66
+ name: 'dhruv_memory_usage_bytes',
67
+ help: 'Current memory usage in bytes',
68
+ labelNames: ['type']
69
+ }),
70
+ // User engagement metrics
71
+ sessionCount: new promClient.Counter({
72
+ name: 'dhruv_session_total',
73
+ help: 'Total number of user sessions'
74
+ }),
75
+ pluginLoadedCount: new promClient.Counter({
76
+ name: 'dhruv_plugin_loaded_total',
77
+ help: 'Total number of plugins loaded'
78
+ })
79
+ };
80
+ // Register all metrics
81
+ Object.values(metrics).forEach(metric => {
82
+ register.registerMetric(metric);
83
+ });
84
+ export class MetricsCollector {
85
+ constructor() {
86
+ this.metricsEnabled = process.env.DHRUV_METRICS_ENABLED !== 'false';
87
+ if (this.metricsEnabled) {
88
+ logger.info('Metrics collection enabled');
89
+ }
90
+ }
91
+ static getInstance() {
92
+ if (!MetricsCollector.instance) {
93
+ MetricsCollector.instance = new MetricsCollector();
94
+ }
95
+ return MetricsCollector.instance;
96
+ }
97
+ recordCommand(command, duration, success) {
98
+ if (!this.metricsEnabled)
99
+ return;
100
+ try {
101
+ metrics.commandDuration.observe({ command, success: success.toString() }, duration / 1000);
102
+ metrics.commandCount.inc({ command, success: success.toString() });
103
+ }
104
+ catch (error) {
105
+ logger.error('Failed to record command metrics', error);
106
+ }
107
+ }
108
+ recordAIRequest(model, commandType, duration, success, tokensUsed) {
109
+ if (!this.metricsEnabled)
110
+ return;
111
+ try {
112
+ metrics.aiRequestDuration.observe({ model, command_type: commandType }, duration / 1000);
113
+ metrics.aiRequestCount.inc({ model, command_type: commandType, success: success.toString() });
114
+ if (tokensUsed) {
115
+ metrics.aiTokensUsed.inc({ model, command_type: commandType }, tokensUsed);
116
+ }
117
+ }
118
+ catch (error) {
119
+ logger.error('Failed to record AI request metrics', error);
120
+ }
121
+ }
122
+ recordCacheHit(cacheType) {
123
+ if (!this.metricsEnabled)
124
+ return;
125
+ try {
126
+ metrics.cacheHitCount.inc({ cache_type: cacheType });
127
+ }
128
+ catch (error) {
129
+ logger.error('Failed to record cache hit metrics', error);
130
+ }
131
+ }
132
+ recordCacheMiss(cacheType) {
133
+ if (!this.metricsEnabled)
134
+ return;
135
+ try {
136
+ metrics.cacheMissCount.inc({ cache_type: cacheType });
137
+ }
138
+ catch (error) {
139
+ logger.error('Failed to record cache miss metrics', error);
140
+ }
141
+ }
142
+ updateCacheSize(cacheType, size) {
143
+ if (!this.metricsEnabled)
144
+ return;
145
+ try {
146
+ metrics.cacheSize.set({ cache_type: cacheType }, size);
147
+ }
148
+ catch (error) {
149
+ logger.error('Failed to update cache size metrics', error);
150
+ }
151
+ }
152
+ recordError(errorType, command) {
153
+ if (!this.metricsEnabled)
154
+ return;
155
+ try {
156
+ metrics.errorCount.inc({ error_type: errorType, command: command || 'unknown' });
157
+ }
158
+ catch (error) {
159
+ logger.error('Failed to record error metrics', error);
160
+ }
161
+ }
162
+ updateMemoryUsage() {
163
+ if (!this.metricsEnabled)
164
+ return;
165
+ try {
166
+ const memUsage = process.memoryUsage();
167
+ metrics.memoryUsage.set({ type: 'rss' }, memUsage.rss);
168
+ metrics.memoryUsage.set({ type: 'heap_used' }, memUsage.heapUsed);
169
+ metrics.memoryUsage.set({ type: 'heap_total' }, memUsage.heapTotal);
170
+ metrics.memoryUsage.set({ type: 'external' }, memUsage.external);
171
+ }
172
+ catch (error) {
173
+ logger.error('Failed to update memory usage metrics', error);
174
+ }
175
+ }
176
+ recordSession() {
177
+ if (!this.metricsEnabled)
178
+ return;
179
+ try {
180
+ metrics.sessionCount.inc();
181
+ }
182
+ catch (error) {
183
+ logger.error('Failed to record session metrics', error);
184
+ }
185
+ }
186
+ recordPluginLoaded() {
187
+ if (!this.metricsEnabled)
188
+ return;
189
+ try {
190
+ metrics.pluginLoadedCount.inc();
191
+ }
192
+ catch (error) {
193
+ logger.error('Failed to record plugin loaded metrics', error);
194
+ }
195
+ }
196
+ async getMetrics() {
197
+ return register.metrics();
198
+ }
199
+ async getMetricsJSON() {
200
+ return register.getMetricsAsJSON();
201
+ }
202
+ getRegistry() {
203
+ return register;
204
+ }
205
+ }
206
+ export const metricsCollector = MetricsCollector.getInstance();
@@ -0,0 +1 @@
1
+ export declare function getSystemMessage(type: string): string;
@@ -0,0 +1,121 @@
1
+ /**
2
+ * System-message templates per command type. Pure data: no AI plumbing.
3
+ */
4
+ const SYSTEM_MESSAGES = {
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
+ suggest: `You are an expert software architect and senior developer providing actionable recommendations and best practices.
23
+
24
+ Guidelines:
25
+ - Provide specific, actionable suggestions
26
+ - Prioritize recommendations by importance
27
+ - Include practical implementation steps
28
+ - Mention relevant tools, libraries, or patterns
29
+ - Consider performance, maintainability, and scalability
30
+ - Provide concrete examples where helpful
31
+ - Focus on industry best practices
32
+
33
+ Output Format:
34
+ - Use clear numbered or bulleted lists
35
+ - Separate different types of suggestions
36
+ - Provide brief explanations for each recommendation
37
+ - Use plain text formatting for better readability`,
38
+ fix: `You are a debugging expert and problem-solving specialist helping developers resolve technical issues.
39
+
40
+ Guidelines:
41
+ - Analyze the problem systematically
42
+ - Identify the root cause
43
+ - Provide step-by-step solutions
44
+ - Include preventive measures
45
+ - Show corrected code examples
46
+ - Explain why the fix works
47
+ - Suggest testing approaches
48
+
49
+ Output Format:
50
+ - Start with problem analysis
51
+ - Provide clear solution steps
52
+ - Show before/after code examples
53
+ - Use plain text with clear structure
54
+ - Avoid complex markdown formatting`,
55
+ review: `You are a senior code reviewer focused on code quality, best practices, and maintainability.
56
+
57
+ Guidelines:
58
+ - Analyze code structure and patterns
59
+ - Identify potential issues or improvements
60
+ - Comment on performance implications
61
+ - Suggest refactoring opportunities
62
+ - Check for security considerations
63
+ - Evaluate readability and maintainability
64
+ - Provide constructive feedback
65
+
66
+ Output Format:
67
+ - Organize feedback by categories (Structure, Performance, Security, etc.)
68
+ - Use clear, actionable language
69
+ - Provide specific line-by-line suggestions where relevant
70
+ - Use plain text formatting for better CLI readability`,
71
+ optimize: `You are a performance optimization expert specializing in code efficiency and best practices.
72
+
73
+ Guidelines:
74
+ - Focus on measurable performance improvements
75
+ - Consider different types of optimization (runtime, memory, bundle size, etc.)
76
+ - Provide specific, implementable suggestions
77
+ - Explain the impact of each optimization
78
+ - Consider trade-offs between performance and maintainability
79
+ - Suggest profiling and measurement approaches
80
+
81
+ Output Format:
82
+ - Categorize optimizations by type
83
+ - Provide clear before/after examples
84
+ - Include estimated impact where possible
85
+ - Use structured plain text formatting`,
86
+ security: `You are a cybersecurity expert specializing in application security and secure coding practices.
87
+
88
+ Guidelines:
89
+ - Identify potential security vulnerabilities
90
+ - Provide remediation steps
91
+ - Suggest secure coding patterns
92
+ - Consider common attack vectors
93
+ - Recommend security tools and practices
94
+ - Focus on practical security measures
95
+ - Explain security implications
96
+
97
+ Output Format:
98
+ - Categorize findings by severity
99
+ - Provide clear remediation steps
100
+ - Use plain text formatting for better readability
101
+ - Include references to security standards where relevant`,
102
+ generate: `You are a code generation specialist creating high-quality, well-structured code.
103
+
104
+ Guidelines:
105
+ - Generate clean, readable code
106
+ - Follow established conventions
107
+ - Include appropriate comments
108
+ - Consider edge cases
109
+ - Use proper error handling
110
+ - Follow best practices for the target language
111
+ - Generate comprehensive test cases when requested
112
+
113
+ Output Format:
114
+ - Provide clean code without excessive markdown
115
+ - Use minimal formatting for better CLI display
116
+ - Include brief explanations only when necessary
117
+ - Focus on practical, working code`,
118
+ };
119
+ export function getSystemMessage(type) {
120
+ return SYSTEM_MESSAGES[type] ?? SYSTEM_MESSAGES.explain;
121
+ }
@@ -0,0 +1,34 @@
1
+ export interface SecurityConfig {
2
+ enableInputValidation: boolean;
3
+ enableRateLimiting: boolean;
4
+ maxInputLength: number;
5
+ allowedCommands: string[];
6
+ blockedPatterns: RegExp[];
7
+ maxRequestsPerMinute: number;
8
+ }
9
+ export declare class SecurityManager {
10
+ private static instance;
11
+ private config;
12
+ private requestCounts;
13
+ private schemas;
14
+ private constructor();
15
+ private initializeSchemas;
16
+ static getInstance(): SecurityManager;
17
+ validateInput(command: string, input: any): {
18
+ valid: boolean;
19
+ error?: string;
20
+ };
21
+ checkRateLimit(identifier: string): {
22
+ allowed: boolean;
23
+ remainingRequests: number;
24
+ };
25
+ sanitizeInput(input: any): any;
26
+ auditLog(action: string, details: Record<string, any>): void;
27
+ getSecurityStatus(): {
28
+ config: SecurityConfig;
29
+ activeRateLimits: number;
30
+ blockedPatternsCount: number;
31
+ };
32
+ resetRateLimits(): void;
33
+ }
34
+ export declare const securityManager: SecurityManager;
@@ -0,0 +1,197 @@
1
+ import Joi from 'joi';
2
+ import validator from 'validator';
3
+ import { logger, logSecurity } from './logger.js';
4
+ import { metricsCollector } from './metrics.js';
5
+ export class SecurityManager {
6
+ constructor() {
7
+ this.requestCounts = new Map();
8
+ this.schemas = {};
9
+ this.config = {
10
+ enableInputValidation: process.env.DHRUV_SECURITY_VALIDATION !== 'false',
11
+ enableRateLimiting: process.env.DHRUV_SECURITY_RATE_LIMIT !== 'false',
12
+ maxInputLength: parseInt(process.env.DHRUV_MAX_INPUT_LENGTH || '10000'),
13
+ allowedCommands: [
14
+ 'explain', 'suggest', 'fix', 'review', 'optimize',
15
+ 'security-check', 'generate', 'init', 'status',
16
+ 'project-type', 'menu', 'completion', 'help'
17
+ ],
18
+ blockedPatterns: [
19
+ /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, // XSS patterns
20
+ /\b(eval|exec|system|shell_exec|passthru|proc_open|popen)\b/i, // Code execution
21
+ /\b(rm\s+(-rf|--force)\s+|del\s+)/i, // File deletion
22
+ /\b(format|fdisk|mkfs)\b/i, // Disk operations
23
+ /password\s*=\s*['"][^'"]*['"]/i, // Password exposure
24
+ /\b(ssh|scp|rsync)\s+.*@\w+/i, // SSH operations
25
+ ],
26
+ maxRequestsPerMinute: parseInt(process.env.DHRUV_RATE_LIMIT || '60')
27
+ };
28
+ this.initializeSchemas();
29
+ logger.info('Security manager initialized', {
30
+ config: {
31
+ ...this.config,
32
+ blockedPatterns: this.config.blockedPatterns.map(p => p.toString())
33
+ }
34
+ });
35
+ }
36
+ initializeSchemas() {
37
+ this.schemas = {
38
+ explain: Joi.object({
39
+ query: Joi.string()
40
+ .min(1)
41
+ .max(this.config.maxInputLength)
42
+ .required()
43
+ .messages({
44
+ 'string.empty': 'Query cannot be empty',
45
+ 'string.max': `Query too long (max ${this.config.maxInputLength} characters)`
46
+ })
47
+ }),
48
+ suggest: Joi.object({
49
+ query: Joi.string()
50
+ .min(1)
51
+ .max(this.config.maxInputLength)
52
+ .required()
53
+ }),
54
+ fix: Joi.object({
55
+ query: Joi.string()
56
+ .min(1)
57
+ .max(this.config.maxInputLength)
58
+ .required()
59
+ }),
60
+ review: Joi.object({
61
+ fileOrDir: Joi.string()
62
+ .min(1)
63
+ .max(500)
64
+ .pattern(/^[^<>&|;$`]*$/) // Prevent shell injection
65
+ .required()
66
+ }),
67
+ optimize: Joi.object({
68
+ file: Joi.string()
69
+ .min(1)
70
+ .max(500)
71
+ .pattern(/^[^<>&|;$`]*$/)
72
+ .required()
73
+ }),
74
+ generate: Joi.object({
75
+ type: Joi.string()
76
+ .valid('tests', 'documentation', 'docs', 'component')
77
+ .required(),
78
+ target: Joi.string()
79
+ .min(1)
80
+ .max(500)
81
+ .pattern(/^[^<>&|;$`]*$/)
82
+ .required()
83
+ })
84
+ };
85
+ }
86
+ static getInstance() {
87
+ if (!SecurityManager.instance) {
88
+ SecurityManager.instance = new SecurityManager();
89
+ }
90
+ return SecurityManager.instance;
91
+ }
92
+ validateInput(command, input) {
93
+ if (!this.config.enableInputValidation) {
94
+ return { valid: true };
95
+ }
96
+ try {
97
+ // Check command is allowed
98
+ if (!this.config.allowedCommands.includes(command)) {
99
+ const error = `Command '${command}' is not allowed`;
100
+ logSecurity('blocked_command', { command, input });
101
+ metricsCollector.recordError('blocked_command', command);
102
+ return { valid: false, error };
103
+ }
104
+ // Get schema for command
105
+ const schema = this.schemas[command];
106
+ if (!schema) {
107
+ return { valid: true }; // No specific validation for this command
108
+ }
109
+ // Validate input
110
+ const { error } = schema.validate(input, { abortEarly: false });
111
+ if (error) {
112
+ const errorMessage = error.details.map((d) => d.message).join(', ');
113
+ logSecurity('invalid_input', { command, input, error: errorMessage });
114
+ metricsCollector.recordError('invalid_input', command);
115
+ return { valid: false, error: errorMessage };
116
+ }
117
+ // Check for blocked patterns
118
+ const inputString = JSON.stringify(input);
119
+ for (const pattern of this.config.blockedPatterns) {
120
+ if (pattern.test(inputString)) {
121
+ const error = 'Input contains blocked patterns';
122
+ logSecurity('blocked_pattern', { command, input, pattern: pattern.toString() });
123
+ metricsCollector.recordError('blocked_pattern', command);
124
+ return { valid: false, error };
125
+ }
126
+ }
127
+ // Sanitize input
128
+ const sanitized = this.sanitizeInput(input);
129
+ if (JSON.stringify(sanitized) !== JSON.stringify(input)) {
130
+ logger.warn('Input was sanitized', { command, original: input, sanitized });
131
+ }
132
+ return { valid: true };
133
+ }
134
+ catch (err) {
135
+ const error = `Validation error: ${err.message}`;
136
+ logSecurity('validation_error', { command, input, error });
137
+ metricsCollector.recordError('validation_error', command);
138
+ return { valid: false, error };
139
+ }
140
+ }
141
+ checkRateLimit(identifier) {
142
+ if (!this.config.enableRateLimiting) {
143
+ return { allowed: true, remainingRequests: this.config.maxRequestsPerMinute };
144
+ }
145
+ const now = Date.now();
146
+ const windowStart = Math.floor(now / 60000) * 60000; // 1-minute windows
147
+ const windowKey = `${identifier}:${windowStart}`;
148
+ const current = this.requestCounts.get(windowKey) || { count: 0, resetTime: windowStart + 60000 };
149
+ if (now > current.resetTime) {
150
+ // Reset window
151
+ this.requestCounts.set(windowKey, { count: 1, resetTime: windowStart + 60000 });
152
+ return { allowed: true, remainingRequests: this.config.maxRequestsPerMinute - 1 };
153
+ }
154
+ if (current.count >= this.config.maxRequestsPerMinute) {
155
+ logSecurity('rate_limit_exceeded', { identifier, count: current.count });
156
+ metricsCollector.recordError('rate_limit_exceeded', 'unknown');
157
+ return { allowed: false, remainingRequests: 0 };
158
+ }
159
+ current.count++;
160
+ const remaining = Math.max(0, this.config.maxRequestsPerMinute - current.count);
161
+ return { allowed: true, remainingRequests: remaining };
162
+ }
163
+ sanitizeInput(input) {
164
+ if (typeof input === 'string') {
165
+ return validator.escape(input);
166
+ }
167
+ if (Array.isArray(input)) {
168
+ return input.map(item => this.sanitizeInput(item));
169
+ }
170
+ if (typeof input === 'object' && input !== null) {
171
+ const sanitized = {};
172
+ for (const [key, value] of Object.entries(input)) {
173
+ sanitized[key] = this.sanitizeInput(value);
174
+ }
175
+ return sanitized;
176
+ }
177
+ return input;
178
+ }
179
+ auditLog(action, details) {
180
+ logSecurity(action, {
181
+ timestamp: new Date().toISOString(),
182
+ ...details
183
+ });
184
+ }
185
+ getSecurityStatus() {
186
+ return {
187
+ config: this.config,
188
+ activeRateLimits: this.requestCounts.size,
189
+ blockedPatternsCount: this.config.blockedPatterns.length
190
+ };
191
+ }
192
+ resetRateLimits() {
193
+ this.requestCounts.clear();
194
+ logger.info('Rate limits reset');
195
+ }
196
+ }
197
+ export const securityManager = SecurityManager.getInstance();
package/dist/index.js CHANGED
@@ -11,9 +11,14 @@ 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, logInfo, logError } from './core/logger.js';
21
+ import { metricsCollector } from './core/metrics.js';
17
22
  const require = createRequire(import.meta.url);
18
23
  const pkg = require('../package.json');
19
24
  const program = new Command();
@@ -24,14 +29,17 @@ program
24
29
  program
25
30
  .command('explain <query>')
26
31
  .description('Explain a concept or command')
32
+ .addHelpText('after', '\nExamples:\n $ dhruv explain "What is async/await?"\n $ dhruv explain "Docker containers vs VMs"')
27
33
  .action(explain);
28
34
  program
29
35
  .command('suggest <query>')
30
36
  .description('Get AI-powered suggestions')
37
+ .addHelpText('after', '\nExamples:\n $ dhruv suggest "React performance optimization"\n $ dhruv suggest "Node.js project structure"')
31
38
  .action(suggest);
32
39
  program
33
40
  .command('fix <query>')
34
41
  .description('Get a fix for a coding issue or error')
42
+ .addHelpText('after', '\nExamples:\n $ dhruv fix "TypeError: Cannot read property of undefined"\n $ dhruv fix "CORS error in Express.js"')
35
43
  .action(fix);
36
44
  program
37
45
  .command('review <fileOrDir>')
@@ -53,6 +61,18 @@ program
53
61
  .command('init')
54
62
  .description('Interactive setup/configuration wizard')
55
63
  .action(init);
64
+ program
65
+ .command('status')
66
+ .description('Check Ollama connection and available models')
67
+ .action(status);
68
+ program
69
+ .command('health')
70
+ .description('Run comprehensive health check')
71
+ .action(health);
72
+ program
73
+ .command('metrics')
74
+ .description('Display CLI usage metrics')
75
+ .action(metrics);
56
76
  program
57
77
  .command('project-type')
58
78
  .description('Detect and print the current project type')
@@ -104,8 +124,26 @@ async function loadPlugins(program) {
104
124
  }
105
125
  }
106
126
  (async () => {
107
- await loadPlugins(program);
108
- program.parse(process.argv);
127
+ // Initialize enterprise features
128
+ try {
129
+ logInfo('Dhruv CLI starting', {
130
+ version: pkg.version,
131
+ nodeVersion: process.version,
132
+ platform: process.platform
133
+ });
134
+ // Record session start
135
+ metricsCollector.recordSession();
136
+ await loadPlugins(program);
137
+ program.parse(process.argv);
138
+ // Record successful session
139
+ const sessionId = logger.getSessionId();
140
+ logInfo('CLI session completed', { sessionId });
141
+ }
142
+ catch (error) {
143
+ logError('CLI startup failed', error);
144
+ console.error(chalk.red('Failed to start Dhruv CLI:'), error.message);
145
+ process.exit(1);
146
+ }
109
147
  })();
110
148
  // Autocomplete: Generate shell completion scripts
111
149
  program
@@ -1,5 +1,8 @@
1
1
  export declare function highlightCode(code: string, lang?: string): string;
2
2
  export declare function printError(message: string): void;
3
3
  export declare function printSuccess(message: string): void;
4
+ export declare function printWarning(message: string): void;
5
+ export declare function printInfo(message: string): void;
6
+ export declare function createSpinner(text: string): import("ora").Ora;
4
7
  export declare function createProgressBar(total: number): any;
5
8
  export declare function themed(text: string, type?: 'primary' | 'accent'): string;
package/dist/utils/ux.js 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
  function getTheme() {
7
8
  const config = loadConfig();
@@ -27,6 +28,20 @@ export function printSuccess(message) {
27
28
  const { success } = getTheme();
28
29
  console.log(success(' SUCCESS '), success(message));
29
30
  }
31
+ export function printWarning(message) {
32
+ const { accent } = getTheme();
33
+ console.warn(chalk.bgYellow.black(' WARNING '), accent(message));
34
+ }
35
+ export function printInfo(message) {
36
+ const { primary } = getTheme();
37
+ console.log(chalk.bgBlue.white(' INFO '), primary(message));
38
+ }
39
+ export function createSpinner(text) {
40
+ return ora({
41
+ text: themed(text, 'primary'),
42
+ color: 'cyan',
43
+ });
44
+ }
30
45
  export function createProgressBar(total) {
31
46
  const { primary } = getTheme();
32
47
  const bar = new cliProgress.SingleBar({