@cpretzinger/boss-claude 1.0.0 → 1.0.2

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 (87) hide show
  1. package/README.md +304 -1
  2. package/bin/boss-claude.js +1138 -0
  3. package/bin/commands/mode.js +250 -0
  4. package/bin/onyx-guard.js +259 -0
  5. package/bin/onyx-guard.sh +251 -0
  6. package/bin/prompts.js +284 -0
  7. package/bin/rollback.js +85 -0
  8. package/bin/setup-wizard.js +492 -0
  9. package/config/.env.example +17 -0
  10. package/lib/README.md +83 -0
  11. package/lib/agent-logger.js +61 -0
  12. package/lib/agents/memory-engineers/github-memory-engineer.js +251 -0
  13. package/lib/agents/memory-engineers/postgres-memory-engineer.js +633 -0
  14. package/lib/agents/memory-engineers/qdrant-memory-engineer.js +358 -0
  15. package/lib/agents/memory-engineers/redis-memory-engineer.js +383 -0
  16. package/lib/agents/memory-supervisor.js +526 -0
  17. package/lib/agents/registry.js +135 -0
  18. package/lib/auto-monitor.js +131 -0
  19. package/lib/checkpoint-hook.js +112 -0
  20. package/lib/checkpoint.js +319 -0
  21. package/lib/commentator.js +213 -0
  22. package/lib/context-scribe.js +120 -0
  23. package/lib/delegation-strategies.js +326 -0
  24. package/lib/hierarchy-validator.js +643 -0
  25. package/lib/index.js +15 -0
  26. package/lib/init-with-mode.js +261 -0
  27. package/lib/init.js +44 -6
  28. package/lib/memory-result-aggregator.js +252 -0
  29. package/lib/memory.js +35 -7
  30. package/lib/mode-enforcer.js +473 -0
  31. package/lib/onyx-banner.js +169 -0
  32. package/lib/onyx-identity.js +214 -0
  33. package/lib/onyx-monitor.js +381 -0
  34. package/lib/onyx-reminder.js +188 -0
  35. package/lib/onyx-tool-interceptor.js +341 -0
  36. package/lib/onyx-wrapper.js +315 -0
  37. package/lib/orchestrator-gate.js +334 -0
  38. package/lib/output-formatter.js +296 -0
  39. package/lib/postgres.js +1 -1
  40. package/lib/prompt-injector.js +220 -0
  41. package/lib/prompts.js +532 -0
  42. package/lib/session.js +153 -6
  43. package/lib/setup/README.md +187 -0
  44. package/lib/setup/env-manager.js +785 -0
  45. package/lib/setup/error-recovery.js +630 -0
  46. package/lib/setup/explain-scopes.js +385 -0
  47. package/lib/setup/github-instructions.js +333 -0
  48. package/lib/setup/github-repo.js +254 -0
  49. package/lib/setup/import-credentials.js +498 -0
  50. package/lib/setup/index.js +62 -0
  51. package/lib/setup/init-postgres.js +785 -0
  52. package/lib/setup/init-redis.js +456 -0
  53. package/lib/setup/integration-test.js +652 -0
  54. package/lib/setup/progress.js +357 -0
  55. package/lib/setup/rollback.js +670 -0
  56. package/lib/setup/rollback.test.js +452 -0
  57. package/lib/setup/setup-with-rollback.example.js +351 -0
  58. package/lib/setup/summary.js +400 -0
  59. package/lib/setup/test-github-setup.js +10 -0
  60. package/lib/setup/test-postgres-init.js +98 -0
  61. package/lib/setup/verify-setup.js +102 -0
  62. package/lib/task-agent-worker.js +235 -0
  63. package/lib/token-monitor.js +466 -0
  64. package/lib/tool-wrapper-integration.js +369 -0
  65. package/lib/tool-wrapper.js +387 -0
  66. package/lib/validators/README.md +497 -0
  67. package/lib/validators/config.js +583 -0
  68. package/lib/validators/config.test.js +175 -0
  69. package/lib/validators/github.js +310 -0
  70. package/lib/validators/github.test.js +61 -0
  71. package/lib/validators/index.js +15 -0
  72. package/lib/validators/postgres.js +525 -0
  73. package/package.json +98 -13
  74. package/scripts/benchmark-memory.js +433 -0
  75. package/scripts/check-secrets.sh +12 -0
  76. package/scripts/fetch-todos.mjs +148 -0
  77. package/scripts/graceful-shutdown.sh +156 -0
  78. package/scripts/install-onyx-hooks.js +373 -0
  79. package/scripts/install.js +119 -18
  80. package/scripts/redis-monitor.js +284 -0
  81. package/scripts/redis-setup.js +412 -0
  82. package/scripts/test-memory-retrieval.js +201 -0
  83. package/scripts/validate-exports.js +68 -0
  84. package/scripts/validate-package.js +120 -0
  85. package/scripts/verify-onyx-deployment.js +309 -0
  86. package/scripts/verify-redis-deployment.js +354 -0
  87. package/scripts/verify-redis-init.js +219 -0
@@ -0,0 +1,583 @@
1
+ /**
2
+ * Boss Claude Configuration Validator
3
+ * Validates entire ~/.boss-claude/.env file for completeness and correctness
4
+ *
5
+ * Checks:
6
+ * - All required environment variables are present
7
+ * - URLs are properly formatted
8
+ * - Redis URLs are valid
9
+ * - PostgreSQL URLs are valid
10
+ * - GitHub tokens are present (format check only, no API validation)
11
+ * - OpenAI API keys are present and formatted correctly
12
+ * - No obviously broken values (empty strings, placeholder text)
13
+ */
14
+
15
+ import dotenv from 'dotenv';
16
+ import { existsSync, readFileSync } from 'fs';
17
+ import { join } from 'path';
18
+ import os from 'os';
19
+ import { validateUrl as validatePostgresUrl } from './postgres.js';
20
+
21
+ /**
22
+ * Required environment variables for Boss Claude
23
+ */
24
+ const REQUIRED_VARS = [
25
+ 'REDIS_URL',
26
+ 'POSTGRES_URL',
27
+ 'GITHUB_TOKEN',
28
+ 'OPENAI_API_KEY'
29
+ ];
30
+
31
+ /**
32
+ * Optional environment variables with defaults
33
+ */
34
+ const OPTIONAL_VARS = {
35
+ 'BOSS_LOG_LEVEL': 'info',
36
+ 'BOSS_MAX_SESSIONS': '100',
37
+ 'BOSS_TOKEN_BANK_LIMIT': '1000000',
38
+ 'BOSS_XP_MULTIPLIER': '1.0'
39
+ };
40
+
41
+ /**
42
+ * Validates Redis URL format
43
+ * @param {string} url - Redis URL
44
+ * @returns {Object} Validation result
45
+ */
46
+ export function validateRedisUrl(url) {
47
+ if (!url || typeof url !== 'string') {
48
+ return {
49
+ valid: false,
50
+ error: 'Redis URL is required and must be a string',
51
+ field: 'REDIS_URL'
52
+ };
53
+ }
54
+
55
+ // Redis URL formats:
56
+ // redis://[[username:]password@]host[:port][/database]
57
+ // rediss://[[username:]password@]host[:port][/database] (SSL)
58
+ const redisUrlPattern = /^rediss?:\/\/(?:([^:@]+)(?::([^@]+))?@)?([^:\/]+)(?::(\d+))?(?:\/(\d+))?$/;
59
+ const match = url.match(redisUrlPattern);
60
+
61
+ if (!match) {
62
+ return {
63
+ valid: false,
64
+ error: 'Invalid Redis URL format',
65
+ field: 'REDIS_URL',
66
+ expected: 'redis://[username:password@]host:port[/database]',
67
+ received: url.substring(0, 50) + (url.length > 50 ? '...' : '')
68
+ };
69
+ }
70
+
71
+ const [, username, password, host, port, database] = match;
72
+
73
+ // Validate host is not empty
74
+ if (!host || host.trim() === '') {
75
+ return {
76
+ valid: false,
77
+ error: 'Redis host cannot be empty',
78
+ field: 'REDIS_URL'
79
+ };
80
+ }
81
+
82
+ // Check for placeholder values
83
+ const placeholders = ['localhost', 'example.com', 'your-redis-url', 'REPLACE_ME'];
84
+ if (placeholders.some(p => url.toLowerCase().includes(p))) {
85
+ return {
86
+ valid: false,
87
+ error: 'Redis URL appears to be a placeholder',
88
+ field: 'REDIS_URL',
89
+ suggestion: 'Replace with actual Redis connection URL'
90
+ };
91
+ }
92
+
93
+ return {
94
+ valid: true,
95
+ field: 'REDIS_URL',
96
+ components: {
97
+ username: username || null,
98
+ hasPassword: !!password,
99
+ host,
100
+ port: port ? parseInt(port, 10) : 6379,
101
+ database: database ? parseInt(database, 10) : 0,
102
+ ssl: url.startsWith('rediss://'),
103
+ isRailway: host.includes('railway.app') || host.includes('rlwy.net')
104
+ }
105
+ };
106
+ }
107
+
108
+ /**
109
+ * Validates GitHub token format (basic check, no API validation)
110
+ * @param {string} token - GitHub token
111
+ * @returns {Object} Validation result
112
+ */
113
+ export function validateGitHubToken(token) {
114
+ if (!token || typeof token !== 'string') {
115
+ return {
116
+ valid: false,
117
+ error: 'GitHub token is required',
118
+ field: 'GITHUB_TOKEN',
119
+ suggestion: 'Create a token at https://github.com/settings/tokens'
120
+ };
121
+ }
122
+
123
+ // Trim whitespace
124
+ token = token.trim();
125
+
126
+ // Check for empty or placeholder values
127
+ const placeholders = ['YOUR_TOKEN', 'REPLACE_ME', 'your-github-token', 'ghp_placeholder'];
128
+ if (placeholders.some(p => token.toLowerCase().includes(p.toLowerCase()))) {
129
+ return {
130
+ valid: false,
131
+ error: 'GitHub token appears to be a placeholder',
132
+ field: 'GITHUB_TOKEN',
133
+ suggestion: 'Replace with actual GitHub personal access token'
134
+ };
135
+ }
136
+
137
+ // Check token length (GitHub tokens are typically 40+ characters)
138
+ if (token.length < 20) {
139
+ return {
140
+ valid: false,
141
+ error: 'GitHub token appears too short',
142
+ field: 'GITHUB_TOKEN',
143
+ details: `Token length: ${token.length} (expected: 40+)`
144
+ };
145
+ }
146
+
147
+ // Check for common token prefixes
148
+ // Classic tokens: ghp_ (personal), gho_ (OAuth), ghu_ (user), ghs_ (server)
149
+ // Fine-grained: github_pat_
150
+ const validPrefixes = ['ghp_', 'gho_', 'ghu_', 'ghs_', 'github_pat_'];
151
+ const hasValidPrefix = validPrefixes.some(prefix => token.startsWith(prefix));
152
+
153
+ if (!hasValidPrefix) {
154
+ return {
155
+ valid: false,
156
+ error: 'GitHub token has unexpected format',
157
+ field: 'GITHUB_TOKEN',
158
+ details: `Expected prefix: ${validPrefixes.join(', ')}`,
159
+ warning: 'Token may still be valid, but format is non-standard'
160
+ };
161
+ }
162
+
163
+ return {
164
+ valid: true,
165
+ field: 'GITHUB_TOKEN',
166
+ tokenType: token.startsWith('github_pat_') ? 'fine-grained' : 'classic',
167
+ length: token.length
168
+ };
169
+ }
170
+
171
+ /**
172
+ * Validates OpenAI API key format
173
+ * @param {string} apiKey - OpenAI API key
174
+ * @returns {Object} Validation result
175
+ */
176
+ export function validateOpenAiKey(apiKey) {
177
+ if (!apiKey || typeof apiKey !== 'string') {
178
+ return {
179
+ valid: false,
180
+ error: 'OpenAI API key is required',
181
+ field: 'OPENAI_API_KEY',
182
+ suggestion: 'Get your API key at https://platform.openai.com/api-keys'
183
+ };
184
+ }
185
+
186
+ // Trim whitespace
187
+ apiKey = apiKey.trim();
188
+
189
+ // Check for placeholder values
190
+ const placeholders = ['YOUR_KEY', 'REPLACE_ME', 'your-openai-key', 'sk-placeholder'];
191
+ if (placeholders.some(p => apiKey.toLowerCase().includes(p.toLowerCase()))) {
192
+ return {
193
+ valid: false,
194
+ error: 'OpenAI API key appears to be a placeholder',
195
+ field: 'OPENAI_API_KEY',
196
+ suggestion: 'Replace with actual OpenAI API key'
197
+ };
198
+ }
199
+
200
+ // OpenAI API keys start with 'sk-' and are typically 51 characters
201
+ // New format: sk-proj-... (longer)
202
+ if (!apiKey.startsWith('sk-')) {
203
+ return {
204
+ valid: false,
205
+ error: 'OpenAI API key has invalid format',
206
+ field: 'OPENAI_API_KEY',
207
+ details: 'API key must start with "sk-"'
208
+ };
209
+ }
210
+
211
+ // Check minimum length
212
+ if (apiKey.length < 40) {
213
+ return {
214
+ valid: false,
215
+ error: 'OpenAI API key appears too short',
216
+ field: 'OPENAI_API_KEY',
217
+ details: `Key length: ${apiKey.length} (expected: 50+)`
218
+ };
219
+ }
220
+
221
+ // Detect key type
222
+ let keyType = 'legacy';
223
+ if (apiKey.startsWith('sk-proj-')) {
224
+ keyType = 'project';
225
+ } else if (apiKey.startsWith('sk-org-')) {
226
+ keyType = 'organization';
227
+ }
228
+
229
+ return {
230
+ valid: true,
231
+ field: 'OPENAI_API_KEY',
232
+ keyType,
233
+ length: apiKey.length
234
+ };
235
+ }
236
+
237
+ /**
238
+ * Validates a single environment variable
239
+ * @param {string} key - Variable name
240
+ * @param {string} value - Variable value
241
+ * @returns {Object} Validation result
242
+ */
243
+ function validateVariable(key, value) {
244
+ // Check if value is empty or just whitespace
245
+ if (!value || (typeof value === 'string' && value.trim() === '')) {
246
+ return {
247
+ valid: false,
248
+ error: `${key} is empty`,
249
+ field: key,
250
+ suggestion: 'Provide a valid value'
251
+ };
252
+ }
253
+
254
+ // Route to specific validator based on key
255
+ switch (key) {
256
+ case 'REDIS_URL':
257
+ return validateRedisUrl(value);
258
+
259
+ case 'POSTGRES_URL':
260
+ return validatePostgresUrl(value);
261
+
262
+ case 'GITHUB_TOKEN':
263
+ return validateGitHubToken(value);
264
+
265
+ case 'OPENAI_API_KEY':
266
+ return validateOpenAiKey(value);
267
+
268
+ default:
269
+ // For other variables, just check they're not empty
270
+ return {
271
+ valid: true,
272
+ field: key,
273
+ value: value.substring(0, 50) + (value.length > 50 ? '...' : '')
274
+ };
275
+ }
276
+ }
277
+
278
+ /**
279
+ * Load and parse .env file from ~/.boss-claude/
280
+ * @returns {Object} Parsed environment variables
281
+ */
282
+ export function loadEnvFile() {
283
+ const envPath = join(os.homedir(), '.boss-claude', '.env');
284
+
285
+ if (!existsSync(envPath)) {
286
+ return {
287
+ exists: false,
288
+ path: envPath,
289
+ error: 'Configuration file not found',
290
+ suggestion: 'Run: boss-claude init'
291
+ };
292
+ }
293
+
294
+ try {
295
+ // Read file content
296
+ const content = readFileSync(envPath, 'utf-8');
297
+
298
+ // Parse with dotenv
299
+ const parsed = dotenv.parse(content);
300
+
301
+ return {
302
+ exists: true,
303
+ path: envPath,
304
+ variables: parsed,
305
+ lineCount: content.split('\n').length,
306
+ fileSize: content.length
307
+ };
308
+ } catch (error) {
309
+ return {
310
+ exists: true,
311
+ path: envPath,
312
+ error: `Failed to parse .env file: ${error.message}`,
313
+ suggestion: 'Check file format and permissions'
314
+ };
315
+ }
316
+ }
317
+
318
+ /**
319
+ * Comprehensive configuration validation
320
+ * @param {Object} options - Validation options
321
+ * @param {boolean} options.includeOptional - Validate optional variables
322
+ * @param {boolean} options.testConnections - Test actual connections (slow)
323
+ * @returns {Promise<Object>} Validation report
324
+ */
325
+ export async function validateConfig(options = {}) {
326
+ const {
327
+ includeOptional = true,
328
+ testConnections = false
329
+ } = options;
330
+
331
+ const startTime = Date.now();
332
+
333
+ // Load .env file
334
+ const envFile = loadEnvFile();
335
+ if (!envFile.exists) {
336
+ return {
337
+ valid: false,
338
+ envFile,
339
+ variables: {},
340
+ errors: [{
341
+ severity: 'critical',
342
+ message: envFile.error,
343
+ suggestion: envFile.suggestion
344
+ }],
345
+ warnings: [],
346
+ summary: {
347
+ total: 0,
348
+ valid: 0,
349
+ invalid: 0,
350
+ missing: REQUIRED_VARS.length
351
+ },
352
+ duration: Date.now() - startTime
353
+ };
354
+ }
355
+
356
+ if (envFile.error) {
357
+ return {
358
+ valid: false,
359
+ envFile,
360
+ variables: {},
361
+ errors: [{
362
+ severity: 'critical',
363
+ message: envFile.error,
364
+ suggestion: envFile.suggestion
365
+ }],
366
+ warnings: [],
367
+ summary: {
368
+ total: 0,
369
+ valid: 0,
370
+ invalid: 0,
371
+ missing: REQUIRED_VARS.length
372
+ },
373
+ duration: Date.now() - startTime
374
+ };
375
+ }
376
+
377
+ const variables = envFile.variables || {};
378
+ const validationResults = {};
379
+ const errors = [];
380
+ const warnings = [];
381
+
382
+ // Check for missing required variables
383
+ const missing = REQUIRED_VARS.filter(key => !(key in variables));
384
+ if (missing.length > 0) {
385
+ errors.push({
386
+ severity: 'critical',
387
+ field: 'configuration',
388
+ message: `Missing required variables: ${missing.join(', ')}`,
389
+ missingVars: missing,
390
+ suggestion: 'Add missing variables to ~/.boss-claude/.env'
391
+ });
392
+ }
393
+
394
+ // Validate each required variable
395
+ for (const key of REQUIRED_VARS) {
396
+ if (key in variables) {
397
+ const result = validateVariable(key, variables[key]);
398
+ validationResults[key] = result;
399
+
400
+ if (!result.valid) {
401
+ errors.push({
402
+ severity: 'error',
403
+ field: result.field,
404
+ message: result.error,
405
+ details: result.details,
406
+ suggestion: result.suggestion
407
+ });
408
+ } else if (result.warning) {
409
+ warnings.push({
410
+ severity: 'warning',
411
+ field: result.field,
412
+ message: result.warning,
413
+ details: result.details
414
+ });
415
+ }
416
+ }
417
+ }
418
+
419
+ // Validate optional variables if requested
420
+ if (includeOptional) {
421
+ for (const [key, defaultValue] of Object.entries(OPTIONAL_VARS)) {
422
+ if (key in variables) {
423
+ const result = validateVariable(key, variables[key]);
424
+ validationResults[key] = result;
425
+
426
+ if (!result.valid) {
427
+ warnings.push({
428
+ severity: 'warning',
429
+ field: result.field,
430
+ message: result.error,
431
+ details: `Will use default: ${defaultValue}`,
432
+ suggestion: result.suggestion
433
+ });
434
+ }
435
+ } else {
436
+ warnings.push({
437
+ severity: 'info',
438
+ field: key,
439
+ message: `Optional variable not set`,
440
+ details: `Using default: ${defaultValue}`
441
+ });
442
+ }
443
+ }
444
+ }
445
+
446
+ // Calculate summary
447
+ const totalVars = Object.keys(validationResults).length;
448
+ const validVars = Object.values(validationResults).filter(r => r.valid).length;
449
+ const invalidVars = totalVars - validVars;
450
+
451
+ const isValid = errors.filter(e => e.severity === 'critical' || e.severity === 'error').length === 0;
452
+
453
+ return {
454
+ valid: isValid,
455
+ envFile,
456
+ variables: validationResults,
457
+ errors,
458
+ warnings,
459
+ summary: {
460
+ total: totalVars,
461
+ valid: validVars,
462
+ invalid: invalidVars,
463
+ missing: missing.length,
464
+ configurationHealth: isValid ? 'healthy' : 'unhealthy'
465
+ },
466
+ duration: Date.now() - startTime
467
+ };
468
+ }
469
+
470
+ /**
471
+ * Pretty print configuration validation report
472
+ * @param {Object} report - Report from validateConfig()
473
+ */
474
+ export function printReport(report) {
475
+ console.log('\n╔════════════════════════════════════════════════════════╗');
476
+ console.log('║ Boss Claude Configuration Validation Report ║');
477
+ console.log('╚════════════════════════════════════════════════════════╝\n');
478
+
479
+ // Environment file status
480
+ console.log('📄 Environment File:');
481
+ if (report.envFile.exists) {
482
+ console.log(` ✓ Found: ${report.envFile.path}`);
483
+ console.log(` Size: ${report.envFile.fileSize} bytes (${report.envFile.lineCount} lines)`);
484
+ } else {
485
+ console.log(` ✗ Not found: ${report.envFile.path}`);
486
+ console.log(` → ${report.envFile.suggestion}\n`);
487
+ return;
488
+ }
489
+
490
+ // Variables validation
491
+ console.log('\n🔍 Variables Validation:\n');
492
+
493
+ // Required variables
494
+ console.log(' Required Variables:');
495
+ for (const key of REQUIRED_VARS) {
496
+ const result = report.variables[key];
497
+ if (!result) {
498
+ console.log(` ✗ ${key}: MISSING`);
499
+ } else if (result.valid) {
500
+ console.log(` ✓ ${key}: OK`);
501
+ // Show additional info for some variables
502
+ if (key === 'REDIS_URL' && result.components) {
503
+ console.log(` - Host: ${result.components.host}:${result.components.port}`);
504
+ console.log(` - SSL: ${result.components.ssl ? 'Yes' : 'No'}`);
505
+ } else if (key === 'POSTGRES_URL' && result.components) {
506
+ console.log(` - Host: ${result.components.host}:${result.components.port}`);
507
+ console.log(` - Database: ${result.components.database}`);
508
+ } else if (key === 'GITHUB_TOKEN' && result.tokenType) {
509
+ console.log(` - Type: ${result.tokenType}`);
510
+ } else if (key === 'OPENAI_API_KEY' && result.keyType) {
511
+ console.log(` - Type: ${result.keyType}`);
512
+ }
513
+ } else {
514
+ console.log(` ✗ ${key}: ${result.error}`);
515
+ if (result.suggestion) {
516
+ console.log(` → ${result.suggestion}`);
517
+ }
518
+ }
519
+ }
520
+
521
+ // Errors
522
+ if (report.errors.length > 0) {
523
+ console.log('\n❌ Errors:');
524
+ report.errors.forEach((error, i) => {
525
+ console.log(` ${i + 1}. [${error.severity.toUpperCase()}] ${error.message}`);
526
+ if (error.details) {
527
+ console.log(` Details: ${error.details}`);
528
+ }
529
+ if (error.suggestion) {
530
+ console.log(` → ${error.suggestion}`);
531
+ }
532
+ });
533
+ }
534
+
535
+ // Warnings
536
+ if (report.warnings.length > 0) {
537
+ console.log('\n⚠️ Warnings:');
538
+ report.warnings.forEach((warning, i) => {
539
+ console.log(` ${i + 1}. [${warning.severity.toUpperCase()}] ${warning.field}: ${warning.message}`);
540
+ if (warning.details) {
541
+ console.log(` Details: ${warning.details}`);
542
+ }
543
+ });
544
+ }
545
+
546
+ // Summary
547
+ console.log('\n📊 Summary:');
548
+ console.log(` Total Variables: ${report.summary.total}`);
549
+ console.log(` Valid: ${report.summary.valid} ✓`);
550
+ console.log(` Invalid: ${report.summary.invalid} ✗`);
551
+ console.log(` Missing: ${report.summary.missing}`);
552
+ console.log(` Configuration Health: ${report.summary.configurationHealth.toUpperCase()}`);
553
+ console.log(` Validation Duration: ${report.duration}ms`);
554
+
555
+ // Overall status
556
+ console.log('\n' + '─'.repeat(60));
557
+ if (report.valid) {
558
+ console.log(' ✅ Configuration is VALID - Ready to use Boss Claude!');
559
+ } else {
560
+ console.log(' ❌ Configuration is INVALID - Fix errors above');
561
+ console.log(' 💡 Run: boss-claude init (to reset configuration)');
562
+ }
563
+ console.log('─'.repeat(60) + '\n');
564
+ }
565
+
566
+ /**
567
+ * Quick validation check (returns boolean)
568
+ * @returns {Promise<boolean>} True if config is valid
569
+ */
570
+ export async function isConfigValid() {
571
+ const report = await validateConfig({ includeOptional: false });
572
+ return report.valid;
573
+ }
574
+
575
+ export default {
576
+ loadEnvFile,
577
+ validateConfig,
578
+ printReport,
579
+ isConfigValid,
580
+ validateRedisUrl,
581
+ validateGitHubToken,
582
+ validateOpenAiKey
583
+ };