@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,670 @@
1
+ /**
2
+ * BOSS CLAUDE - Setup Rollback Utility
3
+ *
4
+ * Safely undoes setup changes if user cancels or errors occur:
5
+ * - Deletes created GitHub repositories
6
+ * - Restores .env backups
7
+ * - Cleans up Redis keys
8
+ * - Removes temporary files
9
+ * - Provides detailed rollback logs
10
+ */
11
+
12
+ import { Octokit } from '@octokit/rest';
13
+ import { EnvManager } from './env-manager.js';
14
+ import chalk from 'chalk';
15
+ import fs from 'fs/promises';
16
+ import { existsSync } from 'fs';
17
+ import path from 'path';
18
+ import os from 'os';
19
+
20
+ const ENV_DIR = path.join(os.homedir(), '.boss-claude');
21
+ const ROLLBACK_LOG = path.join(ENV_DIR, 'rollback.log');
22
+ const STATE_FILE = path.join(ENV_DIR, 'setup-state.json');
23
+
24
+ /**
25
+ * Setup state tracker for rollback operations
26
+ */
27
+ export class SetupState {
28
+ constructor() {
29
+ this.state = {
30
+ timestamp: new Date().toISOString(),
31
+ actions: [],
32
+ completed: false
33
+ };
34
+ }
35
+
36
+ /**
37
+ * Load existing state from file
38
+ */
39
+ async load() {
40
+ try {
41
+ if (existsSync(STATE_FILE)) {
42
+ const content = await fs.readFile(STATE_FILE, 'utf8');
43
+ this.state = JSON.parse(content);
44
+ return this.state;
45
+ }
46
+ } catch (error) {
47
+ console.warn(chalk.yellow('Warning: Could not load setup state'));
48
+ }
49
+ return null;
50
+ }
51
+
52
+ /**
53
+ * Save current state to file
54
+ */
55
+ async save() {
56
+ try {
57
+ await fs.mkdir(ENV_DIR, { recursive: true, mode: 0o700 });
58
+ await fs.writeFile(STATE_FILE, JSON.stringify(this.state, null, 2), { mode: 0o600 });
59
+ } catch (error) {
60
+ console.warn(chalk.yellow('Warning: Could not save setup state'));
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Record an action for potential rollback
66
+ */
67
+ async recordAction(type, data) {
68
+ this.state.actions.push({
69
+ type,
70
+ data,
71
+ timestamp: new Date().toISOString()
72
+ });
73
+ await this.save();
74
+ }
75
+
76
+ /**
77
+ * Mark setup as completed (no rollback needed)
78
+ */
79
+ async markCompleted() {
80
+ this.state.completed = true;
81
+ await this.save();
82
+ }
83
+
84
+ /**
85
+ * Clear state after successful rollback
86
+ */
87
+ async clear() {
88
+ try {
89
+ if (existsSync(STATE_FILE)) {
90
+ await fs.unlink(STATE_FILE);
91
+ }
92
+ } catch (error) {
93
+ console.warn(chalk.yellow('Warning: Could not clear setup state'));
94
+ }
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Main rollback manager
100
+ */
101
+ export class RollbackManager {
102
+ constructor(options = {}) {
103
+ this.options = {
104
+ verbose: options.verbose || false,
105
+ dryRun: options.dryRun || false,
106
+ logFile: options.logFile || ROLLBACK_LOG
107
+ };
108
+ this.logs = [];
109
+ this.errors = [];
110
+ this.state = new SetupState();
111
+ }
112
+
113
+ /**
114
+ * Log message to console and internal log
115
+ */
116
+ log(message, type = 'info') {
117
+ const timestamp = new Date().toISOString();
118
+ const logEntry = `[${timestamp}] [${type.toUpperCase()}] ${message}`;
119
+
120
+ this.logs.push(logEntry);
121
+
122
+ if (this.options.verbose || type === 'error') {
123
+ const colorMap = {
124
+ info: chalk.blue,
125
+ success: chalk.green,
126
+ error: chalk.red,
127
+ warning: chalk.yellow,
128
+ debug: chalk.gray
129
+ };
130
+
131
+ const color = colorMap[type] || chalk.white;
132
+ console.log(color(message));
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Record error
138
+ */
139
+ logError(error, context = '') {
140
+ const errorMsg = `${context}: ${error.message}`;
141
+ this.errors.push(errorMsg);
142
+ this.log(errorMsg, 'error');
143
+ }
144
+
145
+ /**
146
+ * Save logs to file
147
+ */
148
+ async saveLogs() {
149
+ try {
150
+ await fs.mkdir(ENV_DIR, { recursive: true, mode: 0o700 });
151
+ const logContent = this.logs.join('\n') + '\n';
152
+ await fs.appendFile(this.options.logFile, logContent);
153
+ this.log(`Logs saved to: ${this.options.logFile}`, 'debug');
154
+ } catch (error) {
155
+ console.warn(chalk.yellow(`Warning: Could not save logs: ${error.message}`));
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Rollback GitHub repository creation
161
+ */
162
+ async rollbackGitHub(action) {
163
+ const { owner, repo, token, wasCreated } = action.data;
164
+
165
+ if (!wasCreated) {
166
+ this.log(`Skipping GitHub rollback: repository existed before setup`, 'debug');
167
+ return { success: true, skipped: true };
168
+ }
169
+
170
+ if (this.options.dryRun) {
171
+ this.log(`[DRY RUN] Would delete GitHub repository: ${owner}/${repo}`, 'info');
172
+ return { success: true, dryRun: true };
173
+ }
174
+
175
+ try {
176
+ this.log(`Deleting GitHub repository: ${owner}/${repo}`, 'info');
177
+
178
+ const octokit = new Octokit({ auth: token });
179
+
180
+ // Verify repository exists
181
+ try {
182
+ await octokit.repos.get({ owner, repo });
183
+ } catch (error) {
184
+ if (error.status === 404) {
185
+ this.log(`Repository ${owner}/${repo} already deleted`, 'debug');
186
+ return { success: true, alreadyDeleted: true };
187
+ }
188
+ throw error;
189
+ }
190
+
191
+ // Delete repository
192
+ await octokit.repos.delete({ owner, repo });
193
+
194
+ this.log(`Successfully deleted repository: ${owner}/${repo}`, 'success');
195
+
196
+ return {
197
+ success: true,
198
+ owner,
199
+ repo,
200
+ deleted: true
201
+ };
202
+
203
+ } catch (error) {
204
+ this.logError(error, 'GitHub rollback failed');
205
+ return {
206
+ success: false,
207
+ owner,
208
+ repo,
209
+ error: error.message
210
+ };
211
+ }
212
+ }
213
+
214
+ /**
215
+ * Rollback environment variable changes
216
+ */
217
+ async rollbackEnv(action) {
218
+ const { keys, backupName } = action.data;
219
+
220
+ if (this.options.dryRun) {
221
+ this.log(`[DRY RUN] Would restore .env backup: ${backupName || 'latest'}`, 'info');
222
+ return { success: true, dryRun: true };
223
+ }
224
+
225
+ try {
226
+ const envManager = new EnvManager();
227
+ await envManager.init();
228
+
229
+ if (backupName) {
230
+ // Restore from specific backup
231
+ this.log(`Restoring .env from backup: ${backupName}`, 'info');
232
+ const result = await envManager.restore(backupName);
233
+
234
+ if (result.success) {
235
+ this.log('Successfully restored .env from backup', 'success');
236
+ }
237
+
238
+ return result;
239
+ } else if (keys && keys.length > 0) {
240
+ // Remove specific keys
241
+ this.log(`Removing ${keys.length} environment variable(s)`, 'info');
242
+ const results = [];
243
+
244
+ for (const key of keys) {
245
+ try {
246
+ const result = await envManager.remove(key, { skipBackup: true });
247
+ results.push({ key, ...result });
248
+
249
+ if (result.success) {
250
+ this.log(`Removed ${key}`, 'debug');
251
+ }
252
+ } catch (error) {
253
+ this.logError(error, `Failed to remove ${key}`);
254
+ results.push({ key, success: false, error: error.message });
255
+ }
256
+ }
257
+
258
+ const successCount = results.filter(r => r.success).length;
259
+ this.log(`Removed ${successCount}/${keys.length} environment variables`, 'success');
260
+
261
+ return {
262
+ success: successCount > 0,
263
+ results,
264
+ removed: successCount,
265
+ total: keys.length
266
+ };
267
+ }
268
+
269
+ return { success: false, error: 'No rollback method specified' };
270
+
271
+ } catch (error) {
272
+ this.logError(error, 'Environment rollback failed');
273
+ return {
274
+ success: false,
275
+ error: error.message
276
+ };
277
+ }
278
+ }
279
+
280
+ /**
281
+ * Rollback Redis keys
282
+ */
283
+ async rollbackRedis(action) {
284
+ const { keys, pattern } = action.data;
285
+
286
+ if (this.options.dryRun) {
287
+ this.log(`[DRY RUN] Would delete Redis keys: ${keys?.join(', ') || pattern}`, 'info');
288
+ return { success: true, dryRun: true };
289
+ }
290
+
291
+ try {
292
+ // Try to import Redis - it's optional
293
+ let redis;
294
+ try {
295
+ redis = await import('redis');
296
+ } catch (error) {
297
+ this.log('Redis not available (optional dependency)', 'debug');
298
+ return { success: true, skipped: true, reason: 'Redis not installed' };
299
+ }
300
+
301
+ const redisUrl = process.env.REDIS_URL || 'redis://localhost:6379';
302
+ const client = redis.createClient({ url: redisUrl });
303
+
304
+ await client.connect();
305
+ this.log('Connected to Redis', 'debug');
306
+
307
+ let deleted = 0;
308
+
309
+ if (pattern) {
310
+ // Delete by pattern
311
+ this.log(`Scanning for Redis keys matching: ${pattern}`, 'info');
312
+ const matchingKeys = [];
313
+
314
+ for await (const key of client.scanIterator({ MATCH: pattern })) {
315
+ matchingKeys.push(key);
316
+ }
317
+
318
+ if (matchingKeys.length > 0) {
319
+ deleted = await client.del(matchingKeys);
320
+ this.log(`Deleted ${deleted} Redis keys matching pattern: ${pattern}`, 'success');
321
+ } else {
322
+ this.log(`No Redis keys found matching: ${pattern}`, 'debug');
323
+ }
324
+
325
+ } else if (keys && keys.length > 0) {
326
+ // Delete specific keys
327
+ this.log(`Deleting ${keys.length} Redis keys`, 'info');
328
+
329
+ for (const key of keys) {
330
+ try {
331
+ const result = await client.del(key);
332
+ if (result > 0) deleted++;
333
+ } catch (error) {
334
+ this.logError(error, `Failed to delete Redis key: ${key}`);
335
+ }
336
+ }
337
+
338
+ this.log(`Deleted ${deleted}/${keys.length} Redis keys`, 'success');
339
+ }
340
+
341
+ await client.quit();
342
+
343
+ return {
344
+ success: true,
345
+ deleted,
346
+ total: keys?.length || deleted
347
+ };
348
+
349
+ } catch (error) {
350
+ this.logError(error, 'Redis rollback failed');
351
+ return {
352
+ success: false,
353
+ error: error.message
354
+ };
355
+ }
356
+ }
357
+
358
+ /**
359
+ * Rollback file creation
360
+ */
361
+ async rollbackFiles(action) {
362
+ const { files } = action.data;
363
+
364
+ if (this.options.dryRun) {
365
+ this.log(`[DRY RUN] Would delete ${files.length} file(s)`, 'info');
366
+ return { success: true, dryRun: true };
367
+ }
368
+
369
+ try {
370
+ this.log(`Removing ${files.length} file(s)`, 'info');
371
+ const results = [];
372
+
373
+ for (const filePath of files) {
374
+ try {
375
+ if (existsSync(filePath)) {
376
+ await fs.unlink(filePath);
377
+ this.log(`Deleted: ${filePath}`, 'debug');
378
+ results.push({ file: filePath, success: true });
379
+ } else {
380
+ this.log(`File already removed: ${filePath}`, 'debug');
381
+ results.push({ file: filePath, success: true, alreadyDeleted: true });
382
+ }
383
+ } catch (error) {
384
+ this.logError(error, `Failed to delete file: ${filePath}`);
385
+ results.push({ file: filePath, success: false, error: error.message });
386
+ }
387
+ }
388
+
389
+ const successCount = results.filter(r => r.success).length;
390
+ this.log(`Removed ${successCount}/${files.length} files`, 'success');
391
+
392
+ return {
393
+ success: successCount > 0,
394
+ results,
395
+ removed: successCount,
396
+ total: files.length
397
+ };
398
+
399
+ } catch (error) {
400
+ this.logError(error, 'File rollback failed');
401
+ return {
402
+ success: false,
403
+ error: error.message
404
+ };
405
+ }
406
+ }
407
+
408
+ /**
409
+ * Rollback directory creation
410
+ */
411
+ async rollbackDirectories(action) {
412
+ const { directories } = action.data;
413
+
414
+ if (this.options.dryRun) {
415
+ this.log(`[DRY RUN] Would delete ${directories.length} directory(ies)`, 'info');
416
+ return { success: true, dryRun: true };
417
+ }
418
+
419
+ try {
420
+ this.log(`Removing ${directories.length} directory(ies)`, 'info');
421
+ const results = [];
422
+
423
+ for (const dirPath of directories) {
424
+ try {
425
+ if (existsSync(dirPath)) {
426
+ await fs.rm(dirPath, { recursive: true, force: true });
427
+ this.log(`Deleted directory: ${dirPath}`, 'debug');
428
+ results.push({ directory: dirPath, success: true });
429
+ } else {
430
+ this.log(`Directory already removed: ${dirPath}`, 'debug');
431
+ results.push({ directory: dirPath, success: true, alreadyDeleted: true });
432
+ }
433
+ } catch (error) {
434
+ this.logError(error, `Failed to delete directory: ${dirPath}`);
435
+ results.push({ directory: dirPath, success: false, error: error.message });
436
+ }
437
+ }
438
+
439
+ const successCount = results.filter(r => r.success).length;
440
+ this.log(`Removed ${successCount}/${directories.length} directories`, 'success');
441
+
442
+ return {
443
+ success: successCount > 0,
444
+ results,
445
+ removed: successCount,
446
+ total: directories.length
447
+ };
448
+
449
+ } catch (error) {
450
+ this.logError(error, 'Directory rollback failed');
451
+ return {
452
+ success: false,
453
+ error: error.message
454
+ };
455
+ }
456
+ }
457
+
458
+ /**
459
+ * Execute rollback for a specific action
460
+ */
461
+ async rollbackAction(action) {
462
+ this.log(`Rolling back: ${action.type} (${action.timestamp})`, 'info');
463
+
464
+ switch (action.type) {
465
+ case 'github_repo':
466
+ return await this.rollbackGitHub(action);
467
+
468
+ case 'env_vars':
469
+ return await this.rollbackEnv(action);
470
+
471
+ case 'redis_keys':
472
+ return await this.rollbackRedis(action);
473
+
474
+ case 'files':
475
+ return await this.rollbackFiles(action);
476
+
477
+ case 'directories':
478
+ return await this.rollbackDirectories(action);
479
+
480
+ default:
481
+ this.log(`Unknown action type: ${action.type}`, 'warning');
482
+ return { success: false, error: 'Unknown action type' };
483
+ }
484
+ }
485
+
486
+ /**
487
+ * Execute full rollback from state file
488
+ */
489
+ async rollback(stateOrFile = null) {
490
+ console.log(chalk.bold.yellow('\nšŸ”„ BOSS CLAUDE ROLLBACK\n'));
491
+
492
+ try {
493
+ // Load state
494
+ let state;
495
+ if (stateOrFile && typeof stateOrFile === 'string') {
496
+ // Load from custom file
497
+ const content = await fs.readFile(stateOrFile, 'utf8');
498
+ state = JSON.parse(content);
499
+ } else if (stateOrFile && typeof stateOrFile === 'object') {
500
+ // Use provided state
501
+ state = stateOrFile;
502
+ } else {
503
+ // Load from default state file
504
+ state = await this.state.load();
505
+ }
506
+
507
+ if (!state || state.actions.length === 0) {
508
+ console.log(chalk.yellow('No setup actions to rollback'));
509
+ return {
510
+ success: true,
511
+ rollbackCount: 0,
512
+ message: 'Nothing to rollback'
513
+ };
514
+ }
515
+
516
+ if (state.completed) {
517
+ console.log(chalk.yellow('Setup was marked as completed. Rollback may not be necessary.'));
518
+ console.log(chalk.gray('Use --force to rollback anyway\n'));
519
+
520
+ if (!this.options.force) {
521
+ return {
522
+ success: false,
523
+ error: 'Setup already completed. Use --force to rollback anyway.'
524
+ };
525
+ }
526
+ }
527
+
528
+ this.log(`Found ${state.actions.length} actions to rollback`, 'info');
529
+
530
+ if (this.options.dryRun) {
531
+ console.log(chalk.cyan('\n[DRY RUN MODE - No changes will be made]\n'));
532
+ }
533
+
534
+ const results = [];
535
+
536
+ // Rollback in reverse order (LIFO)
537
+ for (let i = state.actions.length - 1; i >= 0; i--) {
538
+ const action = state.actions[i];
539
+ const result = await this.rollbackAction(action);
540
+ results.push({ action, result });
541
+ }
542
+
543
+ // Summary
544
+ const successful = results.filter(r => r.result.success).length;
545
+ const failed = results.filter(r => !r.result.success && !r.result.skipped).length;
546
+ const skipped = results.filter(r => r.result.skipped).length;
547
+
548
+ console.log(chalk.bold('\nšŸ“Š Rollback Summary:\n'));
549
+ console.log(chalk.green(` āœ“ Successful: ${successful}`));
550
+ if (skipped > 0) {
551
+ console.log(chalk.blue(` ⊘ Skipped: ${skipped}`));
552
+ }
553
+ if (failed > 0) {
554
+ console.log(chalk.red(` āœ— Failed: ${failed}`));
555
+ }
556
+
557
+ // Save logs
558
+ await this.saveLogs();
559
+
560
+ // Clear state if successful
561
+ if (failed === 0 && !this.options.dryRun) {
562
+ await this.state.clear();
563
+ this.log('Setup state cleared', 'success');
564
+ }
565
+
566
+ if (this.errors.length > 0) {
567
+ console.log(chalk.red.bold('\nāš ļø Errors occurred during rollback:\n'));
568
+ this.errors.forEach(err => console.log(chalk.red(` • ${err}`)));
569
+ }
570
+
571
+ console.log(chalk.gray(`\nLogs saved to: ${this.options.logFile}\n`));
572
+
573
+ return {
574
+ success: failed === 0,
575
+ total: results.length,
576
+ successful,
577
+ failed,
578
+ skipped,
579
+ results,
580
+ errors: this.errors
581
+ };
582
+
583
+ } catch (error) {
584
+ this.logError(error, 'Rollback failed');
585
+ await this.saveLogs();
586
+
587
+ console.log(chalk.red.bold('\nāœ— Rollback failed\n'));
588
+ console.log(chalk.red(error.message));
589
+ console.log(chalk.gray(`\nLogs saved to: ${this.options.logFile}\n`));
590
+
591
+ return {
592
+ success: false,
593
+ error: error.message,
594
+ errors: this.errors
595
+ };
596
+ }
597
+ }
598
+
599
+ /**
600
+ * Interactive rollback with confirmation
601
+ */
602
+ async rollbackInteractive() {
603
+ const state = await this.state.load();
604
+
605
+ if (!state || state.actions.length === 0) {
606
+ console.log(chalk.yellow('\nNo setup actions found to rollback.\n'));
607
+ return { success: true, rollbackCount: 0 };
608
+ }
609
+
610
+ console.log(chalk.bold.yellow('\nšŸ”„ SETUP ROLLBACK\n'));
611
+ console.log(chalk.white(`Found ${state.actions.length} action(s) to rollback:\n`));
612
+
613
+ state.actions.forEach((action, index) => {
614
+ console.log(chalk.cyan(` ${index + 1}. ${action.type}`));
615
+ console.log(chalk.gray(` ${action.timestamp}`));
616
+ });
617
+
618
+ console.log(chalk.yellow('\nāš ļø This will undo the following:\n'));
619
+ console.log(chalk.white(' • Delete created GitHub repositories'));
620
+ console.log(chalk.white(' • Restore .env backups'));
621
+ console.log(chalk.white(' • Clean up Redis keys'));
622
+ console.log(chalk.white(' • Remove temporary files\n'));
623
+
624
+ // In a real implementation, you'd use a prompt library here
625
+ // For now, we'll assume user confirmed
626
+ console.log(chalk.gray('To rollback, run: boss-claude rollback --confirm\n'));
627
+
628
+ return {
629
+ success: false,
630
+ requiresConfirmation: true,
631
+ actionCount: state.actions.length
632
+ };
633
+ }
634
+ }
635
+
636
+ /**
637
+ * Quick rollback helper for common use cases
638
+ */
639
+ export async function quickRollback(options = {}) {
640
+ const manager = new RollbackManager(options);
641
+ return await manager.rollback();
642
+ }
643
+
644
+ /**
645
+ * Create a rollback state snapshot
646
+ */
647
+ export async function createSnapshot() {
648
+ const state = new SetupState();
649
+ await state.save();
650
+ return state;
651
+ }
652
+
653
+ /**
654
+ * Record an action for rollback
655
+ */
656
+ export async function recordAction(type, data) {
657
+ const state = new SetupState();
658
+ await state.load();
659
+ await state.recordAction(type, data);
660
+ return state;
661
+ }
662
+
663
+ // Export classes and functions
664
+ export default {
665
+ RollbackManager,
666
+ SetupState,
667
+ quickRollback,
668
+ createSnapshot,
669
+ recordAction
670
+ };