@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.
- package/README.md +304 -1
- package/bin/boss-claude.js +1138 -0
- package/bin/commands/mode.js +250 -0
- package/bin/onyx-guard.js +259 -0
- package/bin/onyx-guard.sh +251 -0
- package/bin/prompts.js +284 -0
- package/bin/rollback.js +85 -0
- package/bin/setup-wizard.js +492 -0
- package/config/.env.example +17 -0
- package/lib/README.md +83 -0
- package/lib/agent-logger.js +61 -0
- package/lib/agents/memory-engineers/github-memory-engineer.js +251 -0
- package/lib/agents/memory-engineers/postgres-memory-engineer.js +633 -0
- package/lib/agents/memory-engineers/qdrant-memory-engineer.js +358 -0
- package/lib/agents/memory-engineers/redis-memory-engineer.js +383 -0
- package/lib/agents/memory-supervisor.js +526 -0
- package/lib/agents/registry.js +135 -0
- package/lib/auto-monitor.js +131 -0
- package/lib/checkpoint-hook.js +112 -0
- package/lib/checkpoint.js +319 -0
- package/lib/commentator.js +213 -0
- package/lib/context-scribe.js +120 -0
- package/lib/delegation-strategies.js +326 -0
- package/lib/hierarchy-validator.js +643 -0
- package/lib/index.js +15 -0
- package/lib/init-with-mode.js +261 -0
- package/lib/init.js +44 -6
- package/lib/memory-result-aggregator.js +252 -0
- package/lib/memory.js +35 -7
- package/lib/mode-enforcer.js +473 -0
- package/lib/onyx-banner.js +169 -0
- package/lib/onyx-identity.js +214 -0
- package/lib/onyx-monitor.js +381 -0
- package/lib/onyx-reminder.js +188 -0
- package/lib/onyx-tool-interceptor.js +341 -0
- package/lib/onyx-wrapper.js +315 -0
- package/lib/orchestrator-gate.js +334 -0
- package/lib/output-formatter.js +296 -0
- package/lib/postgres.js +1 -1
- package/lib/prompt-injector.js +220 -0
- package/lib/prompts.js +532 -0
- package/lib/session.js +153 -6
- package/lib/setup/README.md +187 -0
- package/lib/setup/env-manager.js +785 -0
- package/lib/setup/error-recovery.js +630 -0
- package/lib/setup/explain-scopes.js +385 -0
- package/lib/setup/github-instructions.js +333 -0
- package/lib/setup/github-repo.js +254 -0
- package/lib/setup/import-credentials.js +498 -0
- package/lib/setup/index.js +62 -0
- package/lib/setup/init-postgres.js +785 -0
- package/lib/setup/init-redis.js +456 -0
- package/lib/setup/integration-test.js +652 -0
- package/lib/setup/progress.js +357 -0
- package/lib/setup/rollback.js +670 -0
- package/lib/setup/rollback.test.js +452 -0
- package/lib/setup/setup-with-rollback.example.js +351 -0
- package/lib/setup/summary.js +400 -0
- package/lib/setup/test-github-setup.js +10 -0
- package/lib/setup/test-postgres-init.js +98 -0
- package/lib/setup/verify-setup.js +102 -0
- package/lib/task-agent-worker.js +235 -0
- package/lib/token-monitor.js +466 -0
- package/lib/tool-wrapper-integration.js +369 -0
- package/lib/tool-wrapper.js +387 -0
- package/lib/validators/README.md +497 -0
- package/lib/validators/config.js +583 -0
- package/lib/validators/config.test.js +175 -0
- package/lib/validators/github.js +310 -0
- package/lib/validators/github.test.js +61 -0
- package/lib/validators/index.js +15 -0
- package/lib/validators/postgres.js +525 -0
- package/package.json +98 -13
- package/scripts/benchmark-memory.js +433 -0
- package/scripts/check-secrets.sh +12 -0
- package/scripts/fetch-todos.mjs +148 -0
- package/scripts/graceful-shutdown.sh +156 -0
- package/scripts/install-onyx-hooks.js +373 -0
- package/scripts/install.js +119 -18
- package/scripts/redis-monitor.js +284 -0
- package/scripts/redis-setup.js +412 -0
- package/scripts/test-memory-retrieval.js +201 -0
- package/scripts/validate-exports.js +68 -0
- package/scripts/validate-package.js +120 -0
- package/scripts/verify-onyx-deployment.js +309 -0
- package/scripts/verify-redis-deployment.js +354 -0
- 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
|
+
};
|