@jishankai/solid-cli 1.0.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.
@@ -0,0 +1,446 @@
1
+ import config from 'config';
2
+ import { readFileSync, writeFileSync, existsSync } from 'fs';
3
+ import { join } from 'path';
4
+ import Joi from 'joi';
5
+
6
+ /**
7
+ * Configuration Management System
8
+ */
9
+ export class ConfigManager {
10
+ constructor() {
11
+ this.schema = this.createSchema();
12
+ this.config = this.loadConfiguration();
13
+ this.watchers = [];
14
+ }
15
+
16
+ /**
17
+ * Create validation schema for configuration
18
+ */
19
+ createSchema() {
20
+ return Joi.object({
21
+ analysis: Joi.object({
22
+ defaultDepth: Joi.string().valid('fast', 'comprehensive', 'deep').default('comprehensive'),
23
+ adaptiveMode: Joi.boolean().default(true),
24
+ blockchainDetection: Joi.boolean().default(true),
25
+ deepForensicsThreshold: Joi.string().valid('low', 'medium', 'high').default('medium'),
26
+ parallelExecution: Joi.boolean().default(true),
27
+ maxParallelAgents: Joi.number().integer().min(1).max(10).default(3)
28
+ }),
29
+
30
+ reports: Joi.object({
31
+ outputDir: Joi.string().default('./reports'),
32
+ retentionDays: Joi.number().integer().min(1).max(365).default(90),
33
+ defaultTemplate: Joi.string().default('executive'),
34
+ defaultFormats: Joi.array().items(Joi.string().valid('markdown', 'pdf', 'html')).default(['markdown', 'pdf']),
35
+ pdfOptions: Joi.object({
36
+ format: Joi.string().default('A4'),
37
+ margin: Joi.string().default('2cm'),
38
+ displayHeaderFooter: Joi.boolean().default(true),
39
+ printBackground: Joi.boolean().default(true)
40
+ }),
41
+ includeScreenshots: Joi.boolean().default(false),
42
+ compressOldReports: Joi.boolean().default(true)
43
+ }),
44
+
45
+ logging: Joi.object({
46
+ level: Joi.string().valid('error', 'warn', 'info', 'debug').default('info'),
47
+ consoleLevel: Joi.string().valid('error', 'warn', 'info', 'debug').default('warn'),
48
+ enableConsole: Joi.boolean().default(true),
49
+ enableFiles: Joi.boolean().default(true),
50
+ logDir: Joi.string().default('./logs'),
51
+ maxFileSize: Joi.string().default('20m'),
52
+ maxFiles: Joi.string().default('14d'),
53
+ securityLogRetention: Joi.string().default('30d')
54
+ }),
55
+
56
+ llm: Joi.object({
57
+ autoDetectProvider: Joi.boolean().default(true),
58
+ enableLogging: Joi.boolean().default(true),
59
+ logDir: Joi.string().default('./logs/llm-requests'),
60
+ privacyLevel: Joi.string().valid('low', 'medium', 'high').default('high'),
61
+ maxTokens: Joi.number().integer().min(100).max(8000).default(4000),
62
+ temperature: Joi.number().min(0).max(2).default(0.1),
63
+ minHighRiskFindings: Joi.number().integer().min(0).max(50).default(1),
64
+ minTotalFindings: Joi.number().integer().min(0).max(200).default(5),
65
+ skipWhenBelowThreshold: Joi.boolean().default(true)
66
+ }),
67
+
68
+ privacy: Joi.object({
69
+ redactUserPaths: Joi.boolean().default(true),
70
+ redactUsernames: Joi.boolean().default(true),
71
+ redactIPs: Joi.boolean().default(false),
72
+ preserveDomains: Joi.boolean().default(true),
73
+ sanitizationLevel: Joi.string().valid('low', 'medium', 'high').default('high')
74
+ }),
75
+
76
+ performance: Joi.object({
77
+ enableMetrics: Joi.boolean().default(true),
78
+ slowQueryThreshold: Joi.number().integer().min(100).default(5000),
79
+ memoryThreshold: Joi.number().integer().min(128).default(1024),
80
+ enableProfiling: Joi.boolean().default(false)
81
+ }),
82
+
83
+ security: Joi.object({
84
+ enableGeoLookup: Joi.boolean().default(true),
85
+ geoLookupLimit: Joi.number().integer().min(1).max(50).default(10),
86
+ trustedPaths: Joi.array().items(Joi.string()).default([
87
+ '/System', '/usr/bin', '/usr/sbin', '/bin', '/sbin', '/Applications'
88
+ ]),
89
+ riskyPorts: Joi.array().items(Joi.number()).default([
90
+ 22, 23, 135, 139, 445, 1433, 3389, 5432, 6379, 27017, 8080, 8443
91
+ ])
92
+ }),
93
+
94
+ compliance: Joi.object({
95
+ frameworks: Joi.array().items(Joi.string()).default(['NIST CSF', 'ISO 27001', 'SOC 2']),
96
+ enableMapping: Joi.boolean().default(true),
97
+ reportCompliance: Joi.boolean().default(true)
98
+ })
99
+ });
100
+ }
101
+
102
+ /**
103
+ * Load and validate configuration
104
+ */
105
+ loadConfiguration() {
106
+ try {
107
+ // Use config library for environment-specific configs
108
+ let configData;
109
+
110
+ try {
111
+ configData = config.util.toObject();
112
+ } catch (configError) {
113
+ console.warn(`Config library failed, using defaults: ${configError.message}`);
114
+ return this.getDefaultConfiguration();
115
+ }
116
+
117
+ // Validate against schema
118
+ const { error, value } = this.schema.validate(configData, {
119
+ allowUnknown: true,
120
+ stripUnknown: false
121
+ });
122
+
123
+ if (error) {
124
+ console.warn(`Configuration validation warnings: ${error.message}`);
125
+ // Use defaults for invalid values
126
+ return this.getDefaultConfiguration();
127
+ }
128
+
129
+ return value;
130
+ } catch (error) {
131
+ console.error(`Failed to load configuration: ${error.message}`);
132
+ // Return hardcoded defaults as fallback
133
+ return this.getDefaultConfiguration();
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Get default configuration
139
+ */
140
+ getDefaultConfiguration() {
141
+ return {
142
+ analysis: {
143
+ defaultDepth: 'comprehensive',
144
+ adaptiveMode: true,
145
+ blockchainDetection: true,
146
+ deepForensicsThreshold: 'medium',
147
+ parallelExecution: true,
148
+ maxParallelAgents: 3
149
+ },
150
+ reports: {
151
+ outputDir: './reports',
152
+ retentionDays: 90,
153
+ defaultTemplate: 'executive',
154
+ defaultFormats: ['markdown', 'pdf'],
155
+ pdfOptions: {
156
+ format: 'A4',
157
+ margin: '2cm',
158
+ displayHeaderFooter: true,
159
+ printBackground: true
160
+ },
161
+ includeScreenshots: false,
162
+ compressOldReports: true
163
+ },
164
+ logging: {
165
+ level: 'info',
166
+ consoleLevel: 'warn',
167
+ enableConsole: true,
168
+ enableFiles: true,
169
+ logDir: './logs',
170
+ maxFileSize: '20m',
171
+ maxFiles: '14d',
172
+ securityLogRetention: '30d'
173
+ },
174
+ llm: {
175
+ autoDetectProvider: true,
176
+ enableLogging: true,
177
+ logDir: './logs/llm-requests',
178
+ privacyLevel: 'high',
179
+ maxTokens: 4000,
180
+ temperature: 0.1,
181
+ minHighRiskFindings: 1,
182
+ minTotalFindings: 5,
183
+ skipWhenBelowThreshold: true
184
+ },
185
+ privacy: {
186
+ redactUserPaths: true,
187
+ redactUsernames: true,
188
+ redactIPs: false,
189
+ preserveDomains: true,
190
+ sanitizationLevel: 'high'
191
+ },
192
+ performance: {
193
+ enableMetrics: true,
194
+ slowQueryThreshold: 5000,
195
+ memoryThreshold: 1024,
196
+ enableProfiling: false
197
+ },
198
+ security: {
199
+ enableGeoLookup: true,
200
+ geoLookupLimit: 10,
201
+ trustedPaths: ['/System', '/usr/bin', '/usr/sbin', '/bin', '/sbin', '/Applications'],
202
+ riskyPorts: [22, 23, 135, 139, 445, 1433, 3389, 5432, 6379, 27017, 8080, 8443]
203
+ },
204
+ compliance: {
205
+ frameworks: ['NIST CSF', 'ISO 27001', 'SOC 2'],
206
+ enableMapping: true,
207
+ reportCompliance: true
208
+ }
209
+ };
210
+ }
211
+
212
+ /**
213
+ * Get configuration value by path
214
+ */
215
+ get(path, defaultValue = undefined) {
216
+ if (!path || typeof path !== 'string') {
217
+ return defaultValue;
218
+ }
219
+
220
+ const keys = path.split('.');
221
+ let value = this.config;
222
+
223
+ for (const key of keys) {
224
+ if (value && typeof value === 'object' && key in value) {
225
+ value = value[key];
226
+ } else {
227
+ return defaultValue;
228
+ }
229
+ }
230
+
231
+ return value;
232
+ }
233
+
234
+ /**
235
+ * Set configuration value by path
236
+ */
237
+ set(path, value) {
238
+ const keys = path.split('.');
239
+ let current = this.config;
240
+
241
+ // Navigate to parent object
242
+ for (let i = 0; i < keys.length - 1; i++) {
243
+ const key = keys[i];
244
+ if (!(key in current) || typeof current[key] !== 'object') {
245
+ current[key] = {};
246
+ }
247
+ current = current[key];
248
+ }
249
+
250
+ // Set value
251
+ const finalKey = keys[keys.length - 1];
252
+ const oldValue = current[finalKey];
253
+ current[finalKey] = value;
254
+
255
+ // Validate the updated configuration
256
+ const { error } = this.schema.validate(this.config);
257
+ if (error) {
258
+ // Revert change if invalid
259
+ current[finalKey] = oldValue;
260
+ throw new Error(`Invalid configuration value for ${path}: ${error.message}`);
261
+ }
262
+
263
+ // Notify watchers
264
+ this.notifyWatchers(path, oldValue, value);
265
+
266
+ // Save to file if persistent
267
+ this.saveConfiguration();
268
+ }
269
+
270
+ /**
271
+ * Get entire configuration object
272
+ */
273
+ getAll() {
274
+ return { ...this.config };
275
+ }
276
+
277
+ /**
278
+ * Watch for configuration changes
279
+ */
280
+ watch(path, callback) {
281
+ this.watchers.push({ path, callback });
282
+ }
283
+
284
+ /**
285
+ * Remove configuration watcher
286
+ */
287
+ unwatch(path, callback) {
288
+ this.watchers = this.watchers.filter(w =>
289
+ !(w.path === path && w.callback === callback)
290
+ );
291
+ }
292
+
293
+ /**
294
+ * Notify watchers of changes
295
+ */
296
+ notifyWatchers(path, oldValue, newValue) {
297
+ this.watchers
298
+ .filter(w => w.path === path || w.path === '*')
299
+ .forEach(w => {
300
+ try {
301
+ w.callback(path, oldValue, newValue);
302
+ } catch (error) {
303
+ console.error(`Configuration watcher error: ${error.message}`);
304
+ }
305
+ });
306
+ }
307
+
308
+ /**
309
+ * Save configuration to file
310
+ */
311
+ async saveConfiguration() {
312
+ try {
313
+ const configPath = join(process.cwd(), 'config', 'default.json');
314
+ writeFileSync(configPath, JSON.stringify(this.config, null, 2));
315
+ } catch (error) {
316
+ console.error(`Failed to save configuration: ${error.message}`);
317
+ }
318
+ }
319
+
320
+ /**
321
+ * Load configuration from environment variables
322
+ */
323
+ loadFromEnvironment() {
324
+ const envMappings = {
325
+ 'ANALYSIS_DEPTH': 'analysis.defaultDepth',
326
+ 'ANALYSIS_ADAPTIVE': 'analysis.adaptiveMode',
327
+ 'ANALYSIS_BLOCKCHAIN': 'analysis.blockchainDetection',
328
+ 'REPORTS_OUTPUT_DIR': 'reports.outputDir',
329
+ 'REPORTS_RETENTION_DAYS': 'reports.retentionDays',
330
+ 'LOG_LEVEL': 'logging.level',
331
+ 'LOG_DIR': 'logging.logDir',
332
+ 'LLM_MAX_TOKENS': 'llm.maxTokens',
333
+ 'LLM_PRIVACY_LEVEL': 'llm.privacyLevel',
334
+ 'LLM_MIN_HIGH_RISK': 'llm.minHighRiskFindings',
335
+ 'LLM_MIN_TOTAL': 'llm.minTotalFindings',
336
+ 'LLM_SKIP_BELOW_THRESHOLD': 'llm.skipWhenBelowThreshold',
337
+ 'PRIVACY_REDACT_PATHS': 'privacy.redactUserPaths',
338
+ 'PRIVACY_REDACT_USERNAMES': 'privacy.redactUsernames',
339
+ 'SECURITY_GEO_LOOKUP': 'security.enableGeoLookup',
340
+ 'SECURITY_GEO_LIMIT': 'security.geoLookupLimit'
341
+ };
342
+
343
+ for (const [envVar, configPath] of Object.entries(envMappings)) {
344
+ const value = process.env[envVar];
345
+ if (value !== undefined) {
346
+ // Convert string values to appropriate types
347
+ let parsedValue = value;
348
+ if (value === 'true') parsedValue = true;
349
+ else if (value === 'false') parsedValue = false;
350
+ else if (!isNaN(value) && value.includes('.')) parsedValue = parseFloat(value);
351
+ else if (!isNaN(value)) parsedValue = parseInt(value);
352
+
353
+ try {
354
+ this.set(configPath, parsedValue);
355
+ } catch (error) {
356
+ console.warn(`Invalid environment variable ${envVar}: ${error.message}`);
357
+ }
358
+ }
359
+ }
360
+ }
361
+
362
+ /**
363
+ * Get configuration for a specific component
364
+ */
365
+ getComponentConfig(component) {
366
+ return this.get(component, {});
367
+ }
368
+
369
+ /**
370
+ * Merge user configuration with defaults
371
+ */
372
+ mergeWithUserConfig(userConfig) {
373
+ const merged = this.deepMerge(this.getDefaultConfiguration(), userConfig);
374
+
375
+ const { error, value } = this.schema.validate(merged);
376
+ if (error) {
377
+ console.warn(`Configuration merge warnings: ${error.message}`);
378
+ return value;
379
+ }
380
+
381
+ return value;
382
+ }
383
+
384
+ /**
385
+ * Deep merge objects
386
+ */
387
+ deepMerge(target, source) {
388
+ const result = { ...target };
389
+
390
+ for (const key in source) {
391
+ if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
392
+ result[key] = this.deepMerge(result[key] || {}, source[key]);
393
+ } else {
394
+ result[key] = source[key];
395
+ }
396
+ }
397
+
398
+ return result;
399
+ }
400
+
401
+ /**
402
+ * Reset configuration to defaults
403
+ */
404
+ reset() {
405
+ this.config = this.getDefaultConfiguration();
406
+ this.saveConfiguration();
407
+ this.notifyWatchers('*', null, this.config);
408
+ }
409
+
410
+ /**
411
+ * Validate current configuration
412
+ */
413
+ validate() {
414
+ const { error, value } = this.schema.validate(this.config);
415
+ return { valid: !error, errors: error?.details || [], value };
416
+ }
417
+ }
418
+
419
+ /**
420
+ * Global configuration manager instance
421
+ */
422
+ let globalConfig = null;
423
+
424
+ /**
425
+ * Get or create global configuration manager
426
+ */
427
+ export function getConfigManager() {
428
+ if (!globalConfig) {
429
+ globalConfig = new ConfigManager();
430
+ }
431
+ return globalConfig;
432
+ }
433
+
434
+ /**
435
+ * Convenience function to get configuration value
436
+ */
437
+ export function getConfig(path, defaultValue) {
438
+ return getConfigManager().get(path, defaultValue);
439
+ }
440
+
441
+ /**
442
+ * Convenience function to set configuration value
443
+ */
444
+ export function setConfig(path, value) {
445
+ return getConfigManager().set(path, value);
446
+ }