@paulduvall/claude-dev-toolkit 0.0.1-alpha.1 → 0.0.1-alpha.11
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 +74 -23
- package/bin/claude-commands +263 -64
- package/commands/active/xarchitecture.md +393 -0
- package/commands/active/xconfig.md +127 -0
- package/commands/active/xdebug.md +130 -0
- package/commands/active/xdocs.md +178 -0
- package/commands/active/xgit.md +149 -0
- package/commands/active/xpipeline.md +152 -0
- package/commands/active/xquality.md +96 -0
- package/commands/active/xrefactor.md +198 -0
- package/commands/active/xrelease.md +142 -0
- package/commands/active/xsecurity.md +92 -0
- package/commands/active/xspec.md +174 -0
- package/commands/active/xtdd.md +151 -0
- package/commands/active/xtest.md +89 -0
- package/commands/experiments/xact.md +742 -0
- package/commands/experiments/xanalytics.md +113 -0
- package/commands/experiments/xanalyze.md +70 -0
- package/commands/experiments/xapi.md +161 -0
- package/commands/experiments/xatomic.md +112 -0
- package/commands/experiments/xaws.md +85 -0
- package/commands/experiments/xcicd.md +337 -0
- package/commands/experiments/xcommit.md +122 -0
- package/commands/experiments/xcompliance.md +182 -0
- package/commands/experiments/xconstraints.md +89 -0
- package/commands/experiments/xcoverage.md +90 -0
- package/commands/experiments/xdb.md +102 -0
- package/commands/experiments/xdesign.md +121 -0
- package/commands/experiments/xevaluate.md +111 -0
- package/commands/experiments/xfootnote.md +12 -0
- package/commands/experiments/xgenerate.md +117 -0
- package/commands/experiments/xgovernance.md +149 -0
- package/commands/experiments/xgreen.md +66 -0
- package/commands/experiments/xiac.md +118 -0
- package/commands/experiments/xincident.md +137 -0
- package/commands/experiments/xinfra.md +115 -0
- package/commands/experiments/xknowledge.md +115 -0
- package/commands/experiments/xmaturity.md +120 -0
- package/commands/experiments/xmetrics.md +118 -0
- package/commands/experiments/xmonitoring.md +128 -0
- package/commands/experiments/xnew.md +898 -0
- package/commands/experiments/xobservable.md +114 -0
- package/commands/experiments/xoidc.md +165 -0
- package/commands/experiments/xoptimize.md +115 -0
- package/commands/experiments/xperformance.md +112 -0
- package/commands/experiments/xplanning.md +131 -0
- package/commands/experiments/xpolicy.md +115 -0
- package/commands/experiments/xproduct.md +98 -0
- package/commands/experiments/xreadiness.md +75 -0
- package/commands/experiments/xred.md +55 -0
- package/commands/experiments/xrisk.md +128 -0
- package/commands/experiments/xrules.md +124 -0
- package/commands/experiments/xsandbox.md +120 -0
- package/commands/experiments/xscan.md +102 -0
- package/commands/experiments/xsetup.md +123 -0
- package/commands/experiments/xtemplate.md +116 -0
- package/commands/experiments/xtrace.md +212 -0
- package/commands/experiments/xux.md +171 -0
- package/commands/experiments/xvalidate.md +104 -0
- package/commands/experiments/xworkflow.md +113 -0
- package/hooks/README.md +231 -0
- package/hooks/file-logger.sh +98 -0
- package/hooks/lib/argument-parser.sh +422 -0
- package/hooks/lib/config-constants.sh +230 -0
- package/hooks/lib/context-manager.sh +549 -0
- package/hooks/lib/error-handler.sh +412 -0
- package/hooks/lib/execution-engine.sh +627 -0
- package/hooks/lib/file-utils.sh +375 -0
- package/hooks/lib/subagent-discovery.sh +465 -0
- package/hooks/lib/subagent-validator.sh +597 -0
- package/hooks/on-error-debug.sh +221 -0
- package/hooks/pre-commit-quality.sh +204 -0
- package/hooks/pre-write-security.sh +107 -0
- package/hooks/prevent-credential-exposure.sh +265 -0
- package/hooks/subagent-trigger-simple.sh +193 -0
- package/hooks/subagent-trigger.sh +253 -0
- package/lib/backup-restore-command.js +140 -0
- package/lib/base/base-command.js +252 -0
- package/lib/base/command-result.js +184 -0
- package/lib/config/constants.js +255 -0
- package/lib/config.js +228 -3
- package/lib/configure-command.js +428 -0
- package/lib/dependency-validator.js +64 -5
- package/lib/hook-installer-core.js +2 -2
- package/lib/installation-instruction-generator-backup.js +579 -0
- package/lib/installation-instruction-generator.js +213 -495
- package/lib/installer.js +134 -56
- package/lib/oidc-command.js +363 -0
- package/lib/result.js +138 -0
- package/lib/services/backup-list-service.js +226 -0
- package/lib/services/backup-service.js +230 -0
- package/lib/services/command-installer-service.js +217 -0
- package/lib/services/logger-service.js +201 -0
- package/lib/services/package-manager-service.js +319 -0
- package/lib/services/platform-instruction-service.js +294 -0
- package/lib/services/recovery-instruction-service.js +348 -0
- package/lib/services/restore-service.js +221 -0
- package/lib/setup-command.js +309 -0
- package/lib/subagent-formatter.js +278 -0
- package/lib/subagents-core.js +237 -0
- package/lib/subagents.js +508 -0
- package/lib/types.d.ts +183 -0
- package/lib/utils/claude-path-config.js +184 -0
- package/lib/utils/file-system-utils.js +152 -0
- package/lib/utils.js +8 -4
- package/lib/verify-command.js +430 -0
- package/package.json +17 -4
- package/scripts/postinstall.js +28 -10
- package/subagents/api-guardian.md +29 -0
- package/subagents/audit-trail-verifier.md +24 -0
- package/subagents/change-scoper.md +23 -0
- package/subagents/ci-pipeline-curator.md +24 -0
- package/subagents/code-review-assistant.md +258 -0
- package/subagents/continuous-release-orchestrator.md +29 -0
- package/subagents/contract-tester.md +24 -0
- package/subagents/data-steward.md +29 -0
- package/subagents/debug-context.md +197 -0
- package/subagents/debug-specialist.md +138 -0
- package/subagents/dependency-steward.md +24 -0
- package/subagents/deployment-strategist.md +29 -0
- package/subagents/documentation-curator.md +29 -0
- package/subagents/environment-guardian.md +29 -0
- package/subagents/license-compliance-guardian.md +29 -0
- package/subagents/observability-engineer.md +25 -0
- package/subagents/performance-guardian.md +29 -0
- package/subagents/product-owner-proxy.md +28 -0
- package/subagents/requirements-reviewer.md +26 -0
- package/subagents/rollback-first-responder.md +24 -0
- package/subagents/sbom-provenance.md +25 -0
- package/subagents/security-auditor.md +29 -0
- package/subagents/style-enforcer.md +23 -0
- package/subagents/test-writer.md +24 -0
- package/subagents/trunk-guardian.md +29 -0
- package/subagents/workflow-coordinator.md +26 -0
- package/templates/README.md +100 -0
- package/templates/basic-settings.json +30 -0
- package/templates/comprehensive-settings.json +206 -0
- package/templates/hybrid-hook-config.yaml +133 -0
- package/templates/security-focused-settings.json +62 -0
- package/templates/subagent-hooks.yaml +188 -0
- package/tsconfig.json +37 -0
package/lib/result.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Result/Either Pattern Implementation
|
|
3
|
+
* Provides functional error handling without throwing exceptions
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Success result containing a value
|
|
8
|
+
*/
|
|
9
|
+
class Ok {
|
|
10
|
+
constructor(value) {
|
|
11
|
+
this.value = value;
|
|
12
|
+
this.isOk = true;
|
|
13
|
+
this.isError = false;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
map(fn) {
|
|
17
|
+
try {
|
|
18
|
+
return new Ok(fn(this.value));
|
|
19
|
+
} catch (error) {
|
|
20
|
+
return new Err(error);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
flatMap(fn) {
|
|
25
|
+
try {
|
|
26
|
+
return fn(this.value);
|
|
27
|
+
} catch (error) {
|
|
28
|
+
return new Err(error);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
mapError() {
|
|
33
|
+
return this;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
unwrap() {
|
|
37
|
+
return this.value;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
unwrapOr() {
|
|
41
|
+
return this.value;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
match(okFn, errFn) {
|
|
45
|
+
return okFn(this.value);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Error result containing an error
|
|
51
|
+
*/
|
|
52
|
+
class Err {
|
|
53
|
+
constructor(error) {
|
|
54
|
+
this.error = error;
|
|
55
|
+
this.isOk = false;
|
|
56
|
+
this.isError = true;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
map() {
|
|
60
|
+
return this;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
flatMap() {
|
|
64
|
+
return this;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
mapError(fn) {
|
|
68
|
+
try {
|
|
69
|
+
return new Err(fn(this.error));
|
|
70
|
+
} catch (error) {
|
|
71
|
+
return new Err(error);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
unwrap() {
|
|
76
|
+
throw new Error(`Called unwrap on an Err: ${this.error}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
unwrapOr(defaultValue) {
|
|
80
|
+
return defaultValue;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
match(okFn, errFn) {
|
|
84
|
+
return errFn(this.error);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Static factory methods for creating Results
|
|
90
|
+
*/
|
|
91
|
+
class Result {
|
|
92
|
+
static ok(value) {
|
|
93
|
+
return new Ok(value);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
static err(error) {
|
|
97
|
+
return new Err(error);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Wraps a function that might throw in a Result
|
|
102
|
+
*/
|
|
103
|
+
static try(fn) {
|
|
104
|
+
try {
|
|
105
|
+
return Result.ok(fn());
|
|
106
|
+
} catch (error) {
|
|
107
|
+
return Result.err(error);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Wraps an async function that might throw in a Result
|
|
113
|
+
*/
|
|
114
|
+
static async tryAsync(fn) {
|
|
115
|
+
try {
|
|
116
|
+
const result = await fn();
|
|
117
|
+
return Result.ok(result);
|
|
118
|
+
} catch (error) {
|
|
119
|
+
return Result.err(error);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Combines multiple Results - returns Ok if all are Ok, Err if any are Err
|
|
125
|
+
*/
|
|
126
|
+
static all(results) {
|
|
127
|
+
const values = [];
|
|
128
|
+
for (const result of results) {
|
|
129
|
+
if (result.isError) {
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
132
|
+
values.push(result.value);
|
|
133
|
+
}
|
|
134
|
+
return Result.ok(values);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
module.exports = { Result, Ok, Err };
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backup List Service
|
|
3
|
+
* Service for listing and managing backup inventory
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const FileSystemUtils = require('../utils/file-system-utils');
|
|
9
|
+
const ClaudePathConfig = require('../utils/claude-path-config');
|
|
10
|
+
|
|
11
|
+
class BackupListService {
|
|
12
|
+
constructor(config = null) {
|
|
13
|
+
this.config = config || new ClaudePathConfig();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* List all available backups
|
|
18
|
+
*/
|
|
19
|
+
async list() {
|
|
20
|
+
if (!fs.existsSync(this.config.backupsDir)) {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const backups = [];
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const entries = fs.readdirSync(this.config.backupsDir);
|
|
28
|
+
|
|
29
|
+
for (const entry of entries) {
|
|
30
|
+
const fullPath = path.join(this.config.backupsDir, entry);
|
|
31
|
+
const stats = FileSystemUtils.getStats(fullPath);
|
|
32
|
+
|
|
33
|
+
if (!stats) continue;
|
|
34
|
+
|
|
35
|
+
// Compressed backups
|
|
36
|
+
if (entry.endsWith('.tar.gz')) {
|
|
37
|
+
const name = entry.replace('.tar.gz', '');
|
|
38
|
+
backups.push({
|
|
39
|
+
name,
|
|
40
|
+
type: 'compressed',
|
|
41
|
+
size: stats.size,
|
|
42
|
+
modified: stats.mtime,
|
|
43
|
+
path: fullPath
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
// Directory backups
|
|
47
|
+
else if (stats.isDirectory() && !entry.startsWith('.')) {
|
|
48
|
+
const metadata = await this.readBackupMetadata(fullPath);
|
|
49
|
+
|
|
50
|
+
backups.push({
|
|
51
|
+
name: entry,
|
|
52
|
+
type: 'directory',
|
|
53
|
+
size: FileSystemUtils.getDirectorySize(fullPath),
|
|
54
|
+
modified: stats.mtime,
|
|
55
|
+
path: fullPath,
|
|
56
|
+
metadata
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Sort by modification time (newest first)
|
|
62
|
+
backups.sort((a, b) => b.modified - a.modified);
|
|
63
|
+
|
|
64
|
+
return backups;
|
|
65
|
+
} catch (error) {
|
|
66
|
+
throw new Error(`Failed to list backups: ${error.message}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Display backups in user-friendly format
|
|
72
|
+
*/
|
|
73
|
+
async display() {
|
|
74
|
+
console.log('📦 Available Backups:\n');
|
|
75
|
+
|
|
76
|
+
const backups = await this.list();
|
|
77
|
+
|
|
78
|
+
if (backups.length === 0) {
|
|
79
|
+
console.log('No backups found');
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
backups.forEach(backup => {
|
|
84
|
+
const date = backup.modified.toLocaleString();
|
|
85
|
+
const size = FileSystemUtils.formatSize(backup.size);
|
|
86
|
+
const type = backup.type === 'compressed' ? '📦' : '📁';
|
|
87
|
+
|
|
88
|
+
console.log(`${type} ${backup.name}`);
|
|
89
|
+
console.log(` Date: ${date}`);
|
|
90
|
+
console.log(` Size: ${size}`);
|
|
91
|
+
|
|
92
|
+
if (backup.metadata) {
|
|
93
|
+
console.log(` Files: ${backup.metadata.totalFiles}`);
|
|
94
|
+
if (backup.metadata.components) {
|
|
95
|
+
const components = Object.keys(backup.metadata.components).filter(k => backup.metadata.components[k]);
|
|
96
|
+
if (components.length > 0) {
|
|
97
|
+
console.log(` Contains: ${components.join(', ')}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
console.log('');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
console.log(`Total: ${backups.length} backup(s)`);
|
|
105
|
+
console.log('\n💡 To restore a backup, run:');
|
|
106
|
+
console.log(' claude-commands restore <backup-name>');
|
|
107
|
+
|
|
108
|
+
return backups;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Find a specific backup by name
|
|
113
|
+
*/
|
|
114
|
+
async findBackup(name) {
|
|
115
|
+
const backups = await this.list();
|
|
116
|
+
return backups.find(backup => backup.name === name);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Get backup details
|
|
121
|
+
*/
|
|
122
|
+
async getBackupDetails(name) {
|
|
123
|
+
const backup = await this.findBackup(name);
|
|
124
|
+
|
|
125
|
+
if (!backup) {
|
|
126
|
+
throw new Error(`Backup '${name}' not found`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const details = {
|
|
130
|
+
...backup,
|
|
131
|
+
exists: fs.existsSync(backup.path),
|
|
132
|
+
readable: FileSystemUtils.isReadable(backup.path)
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// Add component details if metadata available
|
|
136
|
+
if (backup.metadata && backup.metadata.components) {
|
|
137
|
+
details.components = backup.metadata.components;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return details;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Read backup metadata if available
|
|
145
|
+
*/
|
|
146
|
+
async readBackupMetadata(backupPath) {
|
|
147
|
+
const metadataPath = path.join(backupPath, 'backup-metadata.json');
|
|
148
|
+
|
|
149
|
+
if (!FileSystemUtils.isReadable(metadataPath)) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
const content = FileSystemUtils.readFile(metadataPath);
|
|
155
|
+
return JSON.parse(content);
|
|
156
|
+
} catch (error) {
|
|
157
|
+
// Return null for invalid metadata
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Clean up old backups (keep only N most recent)
|
|
164
|
+
*/
|
|
165
|
+
async cleanup(keepCount = 10) {
|
|
166
|
+
const backups = await this.list();
|
|
167
|
+
|
|
168
|
+
if (backups.length <= keepCount) {
|
|
169
|
+
return { cleaned: 0, kept: backups.length };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const toRemove = backups.slice(keepCount);
|
|
173
|
+
let cleanedCount = 0;
|
|
174
|
+
|
|
175
|
+
for (const backup of toRemove) {
|
|
176
|
+
try {
|
|
177
|
+
if (FileSystemUtils.remove(backup.path)) {
|
|
178
|
+
cleanedCount++;
|
|
179
|
+
}
|
|
180
|
+
} catch (error) {
|
|
181
|
+
console.warn(`⚠️ Warning: Could not remove backup ${backup.name} - ${error.message}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
cleaned: cleanedCount,
|
|
187
|
+
kept: backups.length - cleanedCount
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Get backup statistics
|
|
193
|
+
*/
|
|
194
|
+
async getStats() {
|
|
195
|
+
const backups = await this.list();
|
|
196
|
+
|
|
197
|
+
let totalSize = 0;
|
|
198
|
+
let oldestDate = null;
|
|
199
|
+
let newestDate = null;
|
|
200
|
+
const types = { compressed: 0, directory: 0 };
|
|
201
|
+
|
|
202
|
+
backups.forEach(backup => {
|
|
203
|
+
totalSize += backup.size;
|
|
204
|
+
types[backup.type]++;
|
|
205
|
+
|
|
206
|
+
if (!oldestDate || backup.modified < oldestDate) {
|
|
207
|
+
oldestDate = backup.modified;
|
|
208
|
+
}
|
|
209
|
+
if (!newestDate || backup.modified > newestDate) {
|
|
210
|
+
newestDate = backup.modified;
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
count: backups.length,
|
|
216
|
+
totalSize,
|
|
217
|
+
formattedSize: FileSystemUtils.formatSize(totalSize),
|
|
218
|
+
types,
|
|
219
|
+
oldestDate,
|
|
220
|
+
newestDate,
|
|
221
|
+
averageSize: backups.length > 0 ? totalSize / backups.length : 0
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
module.exports = BackupListService;
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backup Service
|
|
3
|
+
* Focused service for creating backups of Claude Code configuration
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const FileSystemUtils = require('../utils/file-system-utils');
|
|
9
|
+
const ClaudePathConfig = require('../utils/claude-path-config');
|
|
10
|
+
|
|
11
|
+
class BackupService {
|
|
12
|
+
constructor(config = null) {
|
|
13
|
+
this.config = config || new ClaudePathConfig();
|
|
14
|
+
this.backedUpCount = 0;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Create a complete backup
|
|
19
|
+
*/
|
|
20
|
+
async create(name = null) {
|
|
21
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').split('.')[0];
|
|
22
|
+
const backupName = name || `backup-${timestamp}`;
|
|
23
|
+
const backupPath = path.join(this.config.backupsDir, backupName);
|
|
24
|
+
|
|
25
|
+
// Ensure backup directory exists
|
|
26
|
+
FileSystemUtils.ensureDirectory(this.config.backupsDir);
|
|
27
|
+
|
|
28
|
+
// Check if backup already exists
|
|
29
|
+
if (fs.existsSync(backupPath)) {
|
|
30
|
+
throw new Error(`Backup '${backupName}' already exists`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Create backup directory
|
|
34
|
+
FileSystemUtils.ensureDirectory(backupPath);
|
|
35
|
+
|
|
36
|
+
// Reset counter
|
|
37
|
+
this.backedUpCount = 0;
|
|
38
|
+
|
|
39
|
+
// Backup components
|
|
40
|
+
const components = {};
|
|
41
|
+
let totalFiles = 0;
|
|
42
|
+
let totalSize = 0;
|
|
43
|
+
|
|
44
|
+
// Backup settings
|
|
45
|
+
if (FileSystemUtils.isReadable(this.config.settingsPath)) {
|
|
46
|
+
await this.backupSettings(backupPath);
|
|
47
|
+
components.settings = true;
|
|
48
|
+
totalFiles++;
|
|
49
|
+
const stats = FileSystemUtils.getStats(this.config.settingsPath);
|
|
50
|
+
if (stats) totalSize += stats.size;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Backup commands
|
|
54
|
+
const commandsResult = await this.backupCommands(backupPath);
|
|
55
|
+
if (commandsResult.count > 0) {
|
|
56
|
+
components.commands = true;
|
|
57
|
+
totalFiles += commandsResult.count;
|
|
58
|
+
totalSize += commandsResult.size;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Backup hooks
|
|
62
|
+
const hooksResult = await this.backupHooks(backupPath);
|
|
63
|
+
if (hooksResult.count > 0) {
|
|
64
|
+
components.hooks = true;
|
|
65
|
+
totalFiles += hooksResult.count;
|
|
66
|
+
totalSize += hooksResult.size;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Create metadata
|
|
70
|
+
const metadata = this.createMetadata(backupName, components, totalFiles, totalSize);
|
|
71
|
+
await this.saveMetadata(backupPath, metadata);
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
name: backupName,
|
|
75
|
+
path: backupPath,
|
|
76
|
+
components,
|
|
77
|
+
totalFiles,
|
|
78
|
+
totalSize,
|
|
79
|
+
metadata
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Backup settings.json file
|
|
85
|
+
*/
|
|
86
|
+
async backupSettings(backupPath) {
|
|
87
|
+
if (!FileSystemUtils.isReadable(this.config.settingsPath)) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const destPath = path.join(backupPath, 'settings.json');
|
|
92
|
+
const success = FileSystemUtils.copyFile(this.config.settingsPath, destPath);
|
|
93
|
+
|
|
94
|
+
if (success) {
|
|
95
|
+
this.backedUpCount++;
|
|
96
|
+
console.log('✅ Backed up settings.json');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return success;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Backup commands directory
|
|
104
|
+
*/
|
|
105
|
+
async backupCommands(backupPath) {
|
|
106
|
+
if (!fs.existsSync(this.config.commandsDir)) {
|
|
107
|
+
return { count: 0, size: 0 };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const commandsBackupDir = path.join(backupPath, 'commands');
|
|
111
|
+
FileSystemUtils.ensureDirectory(commandsBackupDir);
|
|
112
|
+
|
|
113
|
+
let count = 0;
|
|
114
|
+
let size = 0;
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
const files = fs.readdirSync(this.config.commandsDir);
|
|
118
|
+
|
|
119
|
+
for (const file of files) {
|
|
120
|
+
if (file.endsWith('.md')) {
|
|
121
|
+
const sourcePath = path.join(this.config.commandsDir, file);
|
|
122
|
+
const destPath = path.join(commandsBackupDir, file);
|
|
123
|
+
|
|
124
|
+
if (FileSystemUtils.copyFile(sourcePath, destPath)) {
|
|
125
|
+
count++;
|
|
126
|
+
const stats = FileSystemUtils.getStats(sourcePath);
|
|
127
|
+
if (stats) size += stats.size;
|
|
128
|
+
this.backedUpCount++;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (count > 0) {
|
|
134
|
+
console.log(`✅ Backed up ${count} commands`);
|
|
135
|
+
}
|
|
136
|
+
} catch (error) {
|
|
137
|
+
console.warn(`⚠️ Warning: Could not backup commands - ${error.message}`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return { count, size };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Backup hooks directory
|
|
145
|
+
*/
|
|
146
|
+
async backupHooks(backupPath) {
|
|
147
|
+
const hooksDir = this.config.hooksDir;
|
|
148
|
+
|
|
149
|
+
if (!fs.existsSync(hooksDir)) {
|
|
150
|
+
return { count: 0, size: 0 };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const hooksBackupDir = path.join(backupPath, 'hooks');
|
|
154
|
+
FileSystemUtils.ensureDirectory(hooksBackupDir);
|
|
155
|
+
|
|
156
|
+
let count = 0;
|
|
157
|
+
let size = 0;
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
const files = fs.readdirSync(hooksDir);
|
|
161
|
+
|
|
162
|
+
for (const file of files) {
|
|
163
|
+
const sourcePath = path.join(hooksDir, file);
|
|
164
|
+
const stats = FileSystemUtils.getStats(sourcePath);
|
|
165
|
+
|
|
166
|
+
if (stats && stats.isFile()) {
|
|
167
|
+
const destPath = path.join(hooksBackupDir, file);
|
|
168
|
+
|
|
169
|
+
if (FileSystemUtils.copyFile(sourcePath, destPath)) {
|
|
170
|
+
count++;
|
|
171
|
+
size += stats.size;
|
|
172
|
+
this.backedUpCount++;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (count > 0) {
|
|
178
|
+
console.log(`✅ Backed up ${count} hooks`);
|
|
179
|
+
}
|
|
180
|
+
} catch (error) {
|
|
181
|
+
console.warn(`⚠️ Warning: Could not backup hooks - ${error.message}`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return { count, size };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Create backup metadata
|
|
189
|
+
*/
|
|
190
|
+
createMetadata(name, components, totalFiles, totalSize) {
|
|
191
|
+
return {
|
|
192
|
+
name,
|
|
193
|
+
timestamp: new Date().toISOString(),
|
|
194
|
+
components,
|
|
195
|
+
totalFiles,
|
|
196
|
+
totalSize,
|
|
197
|
+
claudeVersion: this.getClaudeVersion(),
|
|
198
|
+
system: {
|
|
199
|
+
platform: require('os').platform(),
|
|
200
|
+
arch: require('os').arch(),
|
|
201
|
+
nodeVersion: process.version
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Save metadata to backup directory
|
|
208
|
+
*/
|
|
209
|
+
async saveMetadata(backupPath, metadata) {
|
|
210
|
+
const metadataPath = path.join(backupPath, 'backup-metadata.json');
|
|
211
|
+
const content = JSON.stringify(metadata, null, 2);
|
|
212
|
+
|
|
213
|
+
return FileSystemUtils.writeFile(metadataPath, content);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Get Claude Code version
|
|
218
|
+
*/
|
|
219
|
+
getClaudeVersion() {
|
|
220
|
+
try {
|
|
221
|
+
const packagePath = path.join(__dirname, '..', '..', 'package.json');
|
|
222
|
+
const packageData = JSON.parse(FileSystemUtils.readFile(packagePath) || '{}');
|
|
223
|
+
return packageData.version || 'unknown';
|
|
224
|
+
} catch (error) {
|
|
225
|
+
return 'unknown';
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
module.exports = BackupService;
|