zuppaclaude 1.0.2 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -16
- package/bin/zuppaclaude.js +124 -17
- package/lib/components/backup.js +235 -0
- package/lib/components/cloud.js +335 -0
- package/lib/components/index.js +5 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -197,26 +197,48 @@ npx zuppaclaude settings reset # 🔄 Fabrika ayarları
|
|
|
197
197
|
| 🔐 API key koruması | Base64 encoded saklama |
|
|
198
198
|
| 🛡️ Uninstall koruması | Kaldırırken ayarları koruma opsiyonu |
|
|
199
199
|
|
|
200
|
-
### 💾
|
|
200
|
+
### 💾 Yedekleme & Geri Yükleme
|
|
201
201
|
|
|
202
|
-
Claude Code session'larınızı
|
|
202
|
+
Claude Code session'larınızı ve ayarlarınızı yedekleyin. Context kaybı, format veya compacting durumlarında kullanışlı.
|
|
203
203
|
|
|
204
|
+
**🔄 Tam Yedekleme (Sessions + Ayarlar):**
|
|
205
|
+
```bash
|
|
206
|
+
npx zuppaclaude backup # 💾 Lokal tam yedek
|
|
207
|
+
npx zuppaclaude backup --cloud gdrive # ☁️ Google Drive'a yedekle
|
|
208
|
+
npx zuppaclaude restore <id> # ♻️ Yedekten geri yükle
|
|
209
|
+
npx zuppaclaude restore <id> --cloud gdrive # ☁️ Cloud'dan geri yükle
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
**📋 Sadece Session İşlemleri:**
|
|
204
213
|
```bash
|
|
205
214
|
npx zuppaclaude session list # 📋 Tüm session'ları listele
|
|
206
|
-
npx zuppaclaude session backup # 💾
|
|
215
|
+
npx zuppaclaude session backup # 💾 Sadece session'ları yedekle
|
|
207
216
|
npx zuppaclaude session backups # 📦 Mevcut yedekleri listele
|
|
208
|
-
npx zuppaclaude session restore <id> # ♻️ Yedekten geri yükle
|
|
209
|
-
npx zuppaclaude session export <id> # 📤 Belirli session'ı export et
|
|
210
217
|
```
|
|
211
218
|
|
|
212
|
-
|
|
219
|
+
### ☁️ Cloud Yedekleme (rclone)
|
|
220
|
+
|
|
221
|
+
Google Drive, Dropbox, OneDrive, S3, SFTP ve 40+ cloud servise yedekleme.
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
# Kurulum
|
|
225
|
+
brew install rclone # macOS
|
|
226
|
+
rclone config # Remote ayarla (gdrive, dropbox vs.)
|
|
227
|
+
|
|
228
|
+
# Kullanım
|
|
229
|
+
npx zuppaclaude cloud setup # 📖 Kurulum talimatları
|
|
230
|
+
npx zuppaclaude cloud remotes # 📋 Mevcut remote'ları listele
|
|
231
|
+
npx zuppaclaude backup --cloud gdrive # ☁️ Yedekle ve upload et
|
|
232
|
+
npx zuppaclaude cloud backups gdrive # 📦 Cloud'daki yedekleri listele
|
|
233
|
+
```
|
|
213
234
|
|
|
214
235
|
**✨ Özellikler:**
|
|
215
236
|
| Özellik | Açıklama |
|
|
216
237
|
|---------|----------|
|
|
217
238
|
| 🔒 Güvenli restore | Mevcut session'lar üzerine yazılmaz |
|
|
218
|
-
| 📜
|
|
219
|
-
|
|
|
239
|
+
| 📜 Tam yedek | Sessions + Settings + History |
|
|
240
|
+
| ☁️ 40+ cloud | rclone ile Google Drive, Dropbox, S3, SFTP... |
|
|
241
|
+
| 🔐 Encryption | rclone encryption desteği |
|
|
220
242
|
|
|
221
243
|
### 🗑️ Kaldırma
|
|
222
244
|
|
|
@@ -444,26 +466,48 @@ npx zuppaclaude settings reset # 🔄 Reset to defaults
|
|
|
444
466
|
| 🔐 API key protection | Base64 encoded storage |
|
|
445
467
|
| 🛡️ Uninstall protection | Option to preserve settings when uninstalling |
|
|
446
468
|
|
|
447
|
-
### 💾
|
|
469
|
+
### 💾 Backup & Restore
|
|
448
470
|
|
|
449
|
-
Backup
|
|
471
|
+
Backup your Claude Code sessions and settings. Useful for context loss, formatting, or conversation compacting.
|
|
450
472
|
|
|
473
|
+
**🔄 Full Backup (Sessions + Settings):**
|
|
474
|
+
```bash
|
|
475
|
+
npx zuppaclaude backup # 💾 Local full backup
|
|
476
|
+
npx zuppaclaude backup --cloud gdrive # ☁️ Backup to Google Drive
|
|
477
|
+
npx zuppaclaude restore <id> # ♻️ Restore from backup
|
|
478
|
+
npx zuppaclaude restore <id> --cloud gdrive # ☁️ Restore from cloud
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
**📋 Session-only Operations:**
|
|
451
482
|
```bash
|
|
452
483
|
npx zuppaclaude session list # 📋 List all sessions
|
|
453
|
-
npx zuppaclaude session backup # 💾 Backup
|
|
484
|
+
npx zuppaclaude session backup # 💾 Backup sessions only
|
|
454
485
|
npx zuppaclaude session backups # 📦 List available backups
|
|
455
|
-
npx zuppaclaude session restore <id> # ♻️ Restore from backup
|
|
456
|
-
npx zuppaclaude session export <id> # 📤 Export specific session
|
|
457
486
|
```
|
|
458
487
|
|
|
459
|
-
|
|
488
|
+
### ☁️ Cloud Backup (rclone)
|
|
489
|
+
|
|
490
|
+
Backup to Google Drive, Dropbox, OneDrive, S3, SFTP, and 40+ cloud services.
|
|
491
|
+
|
|
492
|
+
```bash
|
|
493
|
+
# Setup
|
|
494
|
+
brew install rclone # macOS
|
|
495
|
+
rclone config # Configure remote (gdrive, dropbox, etc.)
|
|
496
|
+
|
|
497
|
+
# Usage
|
|
498
|
+
npx zuppaclaude cloud setup # 📖 Setup instructions
|
|
499
|
+
npx zuppaclaude cloud remotes # 📋 List configured remotes
|
|
500
|
+
npx zuppaclaude backup --cloud gdrive # ☁️ Backup and upload
|
|
501
|
+
npx zuppaclaude cloud backups gdrive # 📦 List cloud backups
|
|
502
|
+
```
|
|
460
503
|
|
|
461
504
|
**✨ Features:**
|
|
462
505
|
| Feature | Description |
|
|
463
506
|
|---------|-------------|
|
|
464
507
|
| 🔒 Safe restore | Existing sessions are not overwritten |
|
|
465
|
-
| 📜
|
|
466
|
-
|
|
|
508
|
+
| 📜 Full backup | Sessions + Settings + History |
|
|
509
|
+
| ☁️ 40+ clouds | Google Drive, Dropbox, S3, SFTP via rclone |
|
|
510
|
+
| 🔐 Encryption | rclone encryption support |
|
|
467
511
|
|
|
468
512
|
### 🗑️ Uninstall
|
|
469
513
|
|
package/bin/zuppaclaude.js
CHANGED
|
@@ -12,6 +12,17 @@ const { Logger } = require('../lib/utils/logger');
|
|
|
12
12
|
const args = process.argv.slice(2);
|
|
13
13
|
const command = args[0] || 'install';
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Extract --cloud <remote> argument
|
|
17
|
+
*/
|
|
18
|
+
function getCloudArg(args) {
|
|
19
|
+
const cloudIndex = args.indexOf('--cloud');
|
|
20
|
+
if (cloudIndex !== -1 && args[cloudIndex + 1]) {
|
|
21
|
+
return args[cloudIndex + 1];
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
15
26
|
async function main() {
|
|
16
27
|
const logger = new Logger();
|
|
17
28
|
|
|
@@ -114,6 +125,73 @@ async function main() {
|
|
|
114
125
|
}
|
|
115
126
|
break;
|
|
116
127
|
|
|
128
|
+
case 'backup':
|
|
129
|
+
const { BackupManager } = require('../lib/components/backup');
|
|
130
|
+
const backupMgr = new BackupManager();
|
|
131
|
+
const cloudArg = getCloudArg(args);
|
|
132
|
+
await backupMgr.backup({ cloud: cloudArg });
|
|
133
|
+
break;
|
|
134
|
+
|
|
135
|
+
case 'restore':
|
|
136
|
+
const { BackupManager: RestoreBackupManager } = require('../lib/components/backup');
|
|
137
|
+
const restoreMgr = new RestoreBackupManager();
|
|
138
|
+
const restoreId = args[1];
|
|
139
|
+
const restoreCloud = getCloudArg(args);
|
|
140
|
+
|
|
141
|
+
if (!restoreId || restoreId.startsWith('--')) {
|
|
142
|
+
logger.error('Please specify backup ID');
|
|
143
|
+
logger.info('Usage: zuppaclaude restore <backup-id> [--cloud <remote>]');
|
|
144
|
+
logger.info('Run "zuppaclaude backup list" to see available backups');
|
|
145
|
+
process.exit(1);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
await restoreMgr.restore(restoreId, { cloud: restoreCloud });
|
|
149
|
+
break;
|
|
150
|
+
|
|
151
|
+
case 'cloud':
|
|
152
|
+
const { CloudManager } = require('../lib/components/cloud');
|
|
153
|
+
const cloudMgr = new CloudManager();
|
|
154
|
+
const cloudCmd = args[1] || 'remotes';
|
|
155
|
+
const cloudRemote = args[2];
|
|
156
|
+
|
|
157
|
+
switch (cloudCmd) {
|
|
158
|
+
case 'setup':
|
|
159
|
+
cloudMgr.showSetupInstructions();
|
|
160
|
+
break;
|
|
161
|
+
case 'remotes':
|
|
162
|
+
case 'list':
|
|
163
|
+
cloudMgr.listRemotes();
|
|
164
|
+
break;
|
|
165
|
+
case 'upload':
|
|
166
|
+
if (!cloudRemote) {
|
|
167
|
+
logger.error('Please specify remote');
|
|
168
|
+
logger.info('Usage: zuppaclaude cloud upload <remote> [backup-id]');
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
await cloudMgr.upload(cloudRemote, args[3]);
|
|
172
|
+
break;
|
|
173
|
+
case 'download':
|
|
174
|
+
if (!cloudRemote) {
|
|
175
|
+
logger.error('Please specify remote');
|
|
176
|
+
logger.info('Usage: zuppaclaude cloud download <remote> [backup-id]');
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
await cloudMgr.download(cloudRemote, args[3]);
|
|
180
|
+
break;
|
|
181
|
+
case 'backups':
|
|
182
|
+
if (!cloudRemote) {
|
|
183
|
+
logger.error('Please specify remote');
|
|
184
|
+
logger.info('Usage: zuppaclaude cloud backups <remote>');
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}
|
|
187
|
+
await cloudMgr.listCloudBackups(cloudRemote);
|
|
188
|
+
break;
|
|
189
|
+
default:
|
|
190
|
+
logger.error(`Unknown cloud command: ${cloudCmd}`);
|
|
191
|
+
showCloudHelp();
|
|
192
|
+
}
|
|
193
|
+
break;
|
|
194
|
+
|
|
117
195
|
case 'version':
|
|
118
196
|
case 'v':
|
|
119
197
|
case '-v':
|
|
@@ -150,32 +228,40 @@ Usage: zuppaclaude [command] [options]
|
|
|
150
228
|
Commands:
|
|
151
229
|
install, i Install ZuppaClaude components (default)
|
|
152
230
|
uninstall, u Uninstall ZuppaClaude components
|
|
231
|
+
backup Full backup (sessions + settings) with cloud support
|
|
232
|
+
restore Restore from backup
|
|
153
233
|
settings, s Manage settings
|
|
154
234
|
session Manage Claude Code sessions
|
|
235
|
+
cloud Manage cloud remotes (rclone)
|
|
155
236
|
version, v Show version
|
|
156
237
|
help, h Show this help
|
|
157
238
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
239
|
+
Backup Commands:
|
|
240
|
+
backup Local backup (sessions + settings)
|
|
241
|
+
backup --cloud <remote> Backup and upload to cloud
|
|
242
|
+
restore <id> Restore from local backup
|
|
243
|
+
restore <id> --cloud <remote> Download and restore from cloud
|
|
244
|
+
|
|
245
|
+
Cloud Commands:
|
|
246
|
+
cloud setup Show rclone setup instructions
|
|
247
|
+
cloud remotes List configured remotes
|
|
248
|
+
cloud upload <r> Upload backups to remote
|
|
249
|
+
cloud download <r> Download backups from remote
|
|
250
|
+
cloud backups <r> List cloud backups
|
|
164
251
|
|
|
165
252
|
Session Commands:
|
|
166
|
-
session list
|
|
167
|
-
session backup
|
|
168
|
-
session backups
|
|
169
|
-
session restore
|
|
170
|
-
session export
|
|
253
|
+
session list List all sessions
|
|
254
|
+
session backup Backup sessions only
|
|
255
|
+
session backups List available backups
|
|
256
|
+
session restore Restore sessions only
|
|
257
|
+
session export Export a specific session
|
|
171
258
|
|
|
172
259
|
Examples:
|
|
173
|
-
npx zuppaclaude
|
|
174
|
-
npx zuppaclaude
|
|
175
|
-
npx zuppaclaude
|
|
176
|
-
npx zuppaclaude
|
|
177
|
-
npx zuppaclaude
|
|
178
|
-
npx zuppaclaude session restore 2026-01-05T12-00-00
|
|
260
|
+
npx zuppaclaude # Install
|
|
261
|
+
npx zuppaclaude backup # Full local backup
|
|
262
|
+
npx zuppaclaude backup --cloud gdrive # Backup to Google Drive
|
|
263
|
+
npx zuppaclaude restore 2026-01-05T12-00-00 # Restore from backup
|
|
264
|
+
npx zuppaclaude cloud setup # Configure cloud
|
|
179
265
|
`);
|
|
180
266
|
}
|
|
181
267
|
|
|
@@ -214,4 +300,25 @@ Examples:
|
|
|
214
300
|
`);
|
|
215
301
|
}
|
|
216
302
|
|
|
303
|
+
function showCloudHelp() {
|
|
304
|
+
console.log(`
|
|
305
|
+
Cloud Commands (requires rclone):
|
|
306
|
+
setup Show rclone installation and configuration instructions
|
|
307
|
+
remotes List configured cloud remotes
|
|
308
|
+
upload Upload backups to a cloud remote
|
|
309
|
+
download Download backups from a cloud remote
|
|
310
|
+
backups List backups stored on a cloud remote
|
|
311
|
+
|
|
312
|
+
Examples:
|
|
313
|
+
zuppaclaude cloud setup # Setup instructions
|
|
314
|
+
zuppaclaude cloud remotes # List remotes
|
|
315
|
+
zuppaclaude cloud upload gdrive # Upload all backups
|
|
316
|
+
zuppaclaude cloud download gdrive # Download all backups
|
|
317
|
+
zuppaclaude cloud backups gdrive # List cloud backups
|
|
318
|
+
|
|
319
|
+
Supported providers (via rclone):
|
|
320
|
+
Google Drive, Dropbox, OneDrive, S3, SFTP, FTP, and 40+ more
|
|
321
|
+
`);
|
|
322
|
+
}
|
|
323
|
+
|
|
217
324
|
main();
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified Backup Manager - Sessions + Settings + Cloud
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const { Logger } = require('../utils/logger');
|
|
8
|
+
const { Platform } = require('../utils/platform');
|
|
9
|
+
const { SessionManager } = require('./session');
|
|
10
|
+
const { CloudManager } = require('./cloud');
|
|
11
|
+
const { Settings } = require('../settings');
|
|
12
|
+
|
|
13
|
+
class BackupManager {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.platform = new Platform();
|
|
16
|
+
this.logger = new Logger();
|
|
17
|
+
this.sessionManager = new SessionManager();
|
|
18
|
+
this.cloudManager = new CloudManager();
|
|
19
|
+
this.settings = new Settings();
|
|
20
|
+
this.backupDir = path.join(this.platform.zuppaconfigDir, 'backups');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Full backup - sessions + settings
|
|
25
|
+
*/
|
|
26
|
+
async backup(options = {}) {
|
|
27
|
+
const { cloud } = options;
|
|
28
|
+
|
|
29
|
+
console.log('');
|
|
30
|
+
console.log('\x1b[35m═══════════════════════════════════════════════════════════════════\x1b[0m');
|
|
31
|
+
console.log('\x1b[35m ZuppaClaude Full Backup\x1b[0m');
|
|
32
|
+
console.log('\x1b[35m═══════════════════════════════════════════════════════════════════\x1b[0m');
|
|
33
|
+
console.log('');
|
|
34
|
+
|
|
35
|
+
// Step 1: Backup sessions
|
|
36
|
+
this.logger.step('Step 1/3: Backing up Claude Code sessions...');
|
|
37
|
+
const sessionResult = this.sessionManager.backup();
|
|
38
|
+
|
|
39
|
+
if (!sessionResult) {
|
|
40
|
+
this.logger.warning('No sessions to backup');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Step 2: Backup settings to the same directory
|
|
44
|
+
this.logger.step('Step 2/3: Backing up ZuppaClaude settings...');
|
|
45
|
+
|
|
46
|
+
if (sessionResult) {
|
|
47
|
+
const settingsBackupPath = path.join(sessionResult.path, 'zc-settings.json');
|
|
48
|
+
const currentSettings = this.settings.load();
|
|
49
|
+
|
|
50
|
+
if (currentSettings) {
|
|
51
|
+
fs.writeFileSync(settingsBackupPath, JSON.stringify(currentSettings, null, 2));
|
|
52
|
+
this.logger.success('Settings backed up');
|
|
53
|
+
} else {
|
|
54
|
+
this.logger.info('No settings to backup');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Update manifest with settings info
|
|
58
|
+
const manifestPath = path.join(sessionResult.path, 'manifest.json');
|
|
59
|
+
if (fs.existsSync(manifestPath)) {
|
|
60
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
61
|
+
manifest.settings = currentSettings ? true : false;
|
|
62
|
+
manifest.backupType = 'full';
|
|
63
|
+
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Step 3: Upload to cloud if specified
|
|
68
|
+
if (cloud) {
|
|
69
|
+
this.logger.step('Step 3/3: Uploading to cloud...');
|
|
70
|
+
|
|
71
|
+
if (!this.cloudManager.isRcloneInstalled()) {
|
|
72
|
+
this.logger.warning('rclone not installed, skipping cloud upload');
|
|
73
|
+
this.cloudManager.showSetupInstructions();
|
|
74
|
+
} else if (!this.cloudManager.remoteExists(cloud)) {
|
|
75
|
+
this.logger.error(`Remote not found: ${cloud}`);
|
|
76
|
+
this.cloudManager.listRemotes();
|
|
77
|
+
} else {
|
|
78
|
+
await this.cloudManager.upload(cloud, sessionResult?.timestamp);
|
|
79
|
+
}
|
|
80
|
+
} else {
|
|
81
|
+
this.logger.step('Step 3/3: Cloud upload skipped (use --cloud <remote>)');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Summary
|
|
85
|
+
this.printBackupSummary(sessionResult, cloud);
|
|
86
|
+
|
|
87
|
+
return sessionResult;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Full restore - sessions + settings
|
|
92
|
+
*/
|
|
93
|
+
async restore(backupId, options = {}) {
|
|
94
|
+
const { cloud, settingsOnly, sessionsOnly } = options;
|
|
95
|
+
|
|
96
|
+
console.log('');
|
|
97
|
+
console.log('\x1b[35m═══════════════════════════════════════════════════════════════════\x1b[0m');
|
|
98
|
+
console.log('\x1b[35m ZuppaClaude Restore\x1b[0m');
|
|
99
|
+
console.log('\x1b[35m═══════════════════════════════════════════════════════════════════\x1b[0m');
|
|
100
|
+
console.log('');
|
|
101
|
+
|
|
102
|
+
// Step 1: Download from cloud if specified
|
|
103
|
+
if (cloud) {
|
|
104
|
+
this.logger.step('Step 1/3: Downloading from cloud...');
|
|
105
|
+
|
|
106
|
+
if (!this.cloudManager.isRcloneInstalled()) {
|
|
107
|
+
this.logger.error('rclone not installed');
|
|
108
|
+
this.cloudManager.showSetupInstructions();
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!this.cloudManager.remoteExists(cloud)) {
|
|
113
|
+
this.logger.error(`Remote not found: ${cloud}`);
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
await this.cloudManager.download(cloud, backupId);
|
|
118
|
+
} else {
|
|
119
|
+
this.logger.info('Step 1/3: Using local backup');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const backupPath = path.join(this.backupDir, backupId);
|
|
123
|
+
|
|
124
|
+
if (!fs.existsSync(backupPath)) {
|
|
125
|
+
this.logger.error(`Backup not found: ${backupId}`);
|
|
126
|
+
this.logger.info('Run "npx zuppaclaude backup list" to see available backups');
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
let sessionsRestored = false;
|
|
131
|
+
let settingsRestored = false;
|
|
132
|
+
|
|
133
|
+
// Step 2: Restore sessions
|
|
134
|
+
if (!settingsOnly) {
|
|
135
|
+
this.logger.step('Step 2/3: Restoring sessions...');
|
|
136
|
+
sessionsRestored = this.sessionManager.restore(backupId);
|
|
137
|
+
} else {
|
|
138
|
+
this.logger.info('Step 2/3: Sessions restore skipped');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Step 3: Restore settings
|
|
142
|
+
if (!sessionsOnly) {
|
|
143
|
+
this.logger.step('Step 3/3: Restoring settings...');
|
|
144
|
+
|
|
145
|
+
const settingsBackupPath = path.join(backupPath, 'zc-settings.json');
|
|
146
|
+
|
|
147
|
+
if (fs.existsSync(settingsBackupPath)) {
|
|
148
|
+
try {
|
|
149
|
+
const backupSettings = JSON.parse(fs.readFileSync(settingsBackupPath, 'utf8'));
|
|
150
|
+
this.settings.save(backupSettings);
|
|
151
|
+
this.logger.success('Settings restored');
|
|
152
|
+
settingsRestored = true;
|
|
153
|
+
} catch (error) {
|
|
154
|
+
this.logger.warning(`Failed to restore settings: ${error.message}`);
|
|
155
|
+
}
|
|
156
|
+
} else {
|
|
157
|
+
this.logger.info('No settings in backup');
|
|
158
|
+
}
|
|
159
|
+
} else {
|
|
160
|
+
this.logger.info('Step 3/3: Settings restore skipped');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Summary
|
|
164
|
+
console.log('');
|
|
165
|
+
console.log('\x1b[32m═══════════════════════════════════════════════════════════════════\x1b[0m');
|
|
166
|
+
console.log('\x1b[32m Restore Complete\x1b[0m');
|
|
167
|
+
console.log('\x1b[32m═══════════════════════════════════════════════════════════════════\x1b[0m');
|
|
168
|
+
console.log('');
|
|
169
|
+
console.log(` Sessions: ${sessionsRestored ? '✅ Restored' : '⏭️ Skipped'}`);
|
|
170
|
+
console.log(` Settings: ${settingsRestored ? '✅ Restored' : '⏭️ Skipped'}`);
|
|
171
|
+
console.log('');
|
|
172
|
+
console.log(' Restart Claude Code to see restored sessions');
|
|
173
|
+
console.log('');
|
|
174
|
+
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* List all backups (local + cloud)
|
|
180
|
+
*/
|
|
181
|
+
async list(options = {}) {
|
|
182
|
+
const { cloud } = options;
|
|
183
|
+
|
|
184
|
+
// List local backups
|
|
185
|
+
this.sessionManager.listBackups();
|
|
186
|
+
|
|
187
|
+
// List cloud backups if remote specified
|
|
188
|
+
if (cloud) {
|
|
189
|
+
await this.cloudManager.listCloudBackups(cloud);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Print backup summary
|
|
195
|
+
*/
|
|
196
|
+
printBackupSummary(result, cloud) {
|
|
197
|
+
console.log('');
|
|
198
|
+
console.log('\x1b[32m═══════════════════════════════════════════════════════════════════\x1b[0m');
|
|
199
|
+
console.log('\x1b[32m Backup Complete\x1b[0m');
|
|
200
|
+
console.log('\x1b[32m═══════════════════════════════════════════════════════════════════\x1b[0m');
|
|
201
|
+
console.log('');
|
|
202
|
+
|
|
203
|
+
if (result) {
|
|
204
|
+
console.log(` 📦 Backup ID: ${result.timestamp}`);
|
|
205
|
+
console.log(` 📁 Sessions: ${result.sessions}`);
|
|
206
|
+
console.log(` 💾 Size: ${this.formatSize(result.size)}`);
|
|
207
|
+
console.log(` 📍 Location: ${result.path}`);
|
|
208
|
+
|
|
209
|
+
if (cloud) {
|
|
210
|
+
console.log(` ☁️ Cloud: ${cloud}:zuppaclaude-backups/${result.timestamp}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
console.log('');
|
|
215
|
+
console.log(' Restore with:');
|
|
216
|
+
if (result) {
|
|
217
|
+
console.log(` \x1b[36mnpx zuppaclaude restore ${result.timestamp}\x1b[0m`);
|
|
218
|
+
if (cloud) {
|
|
219
|
+
console.log(` \x1b[36mnpx zuppaclaude restore ${result.timestamp} --cloud ${cloud}\x1b[0m`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
console.log('');
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Format file size
|
|
227
|
+
*/
|
|
228
|
+
formatSize(bytes) {
|
|
229
|
+
if (bytes < 1024) return bytes + ' B';
|
|
230
|
+
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
|
|
231
|
+
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
module.exports = { BackupManager };
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloud Backup Manager - rclone integration for cloud sync
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const { Logger } = require('../utils/logger');
|
|
8
|
+
const { Platform } = require('../utils/platform');
|
|
9
|
+
|
|
10
|
+
class CloudManager {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.platform = new Platform();
|
|
13
|
+
this.logger = new Logger();
|
|
14
|
+
this.backupDir = path.join(this.platform.zuppaconfigDir, 'backups');
|
|
15
|
+
this.cloudPath = 'zuppaclaude-backups';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Check if rclone is installed
|
|
20
|
+
*/
|
|
21
|
+
isRcloneInstalled() {
|
|
22
|
+
return this.platform.commandExists('rclone');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get available rclone remotes
|
|
27
|
+
*/
|
|
28
|
+
getRemotes() {
|
|
29
|
+
if (!this.isRcloneInstalled()) {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const output = this.platform.exec('rclone listremotes', { silent: true });
|
|
35
|
+
if (!output) return [];
|
|
36
|
+
|
|
37
|
+
return output
|
|
38
|
+
.split('\n')
|
|
39
|
+
.map(r => r.trim().replace(/:$/, ''))
|
|
40
|
+
.filter(r => r.length > 0);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Check remote exists
|
|
48
|
+
*/
|
|
49
|
+
remoteExists(remote) {
|
|
50
|
+
const remotes = this.getRemotes();
|
|
51
|
+
return remotes.includes(remote);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Show rclone setup instructions
|
|
56
|
+
*/
|
|
57
|
+
showSetupInstructions() {
|
|
58
|
+
console.log('');
|
|
59
|
+
console.log('\x1b[35m═══════════════════════════════════════════════════════════════════\x1b[0m');
|
|
60
|
+
console.log('\x1b[35m Cloud Backup Setup\x1b[0m');
|
|
61
|
+
console.log('\x1b[35m═══════════════════════════════════════════════════════════════════\x1b[0m');
|
|
62
|
+
console.log('');
|
|
63
|
+
|
|
64
|
+
if (!this.isRcloneInstalled()) {
|
|
65
|
+
console.log('\x1b[33m[!]\x1b[0m rclone is not installed.');
|
|
66
|
+
console.log('');
|
|
67
|
+
console.log('Install rclone:');
|
|
68
|
+
console.log('');
|
|
69
|
+
if (this.platform.isMac) {
|
|
70
|
+
console.log(' \x1b[36mbrew install rclone\x1b[0m');
|
|
71
|
+
} else if (this.platform.isLinux) {
|
|
72
|
+
console.log(' \x1b[36mcurl https://rclone.org/install.sh | sudo bash\x1b[0m');
|
|
73
|
+
} else {
|
|
74
|
+
console.log(' \x1b[36mwinget install Rclone.Rclone\x1b[0m');
|
|
75
|
+
}
|
|
76
|
+
console.log('');
|
|
77
|
+
console.log('Or visit: https://rclone.org/install/');
|
|
78
|
+
console.log('');
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
console.log('\x1b[32m[✓]\x1b[0m rclone is installed');
|
|
83
|
+
console.log('');
|
|
84
|
+
|
|
85
|
+
const remotes = this.getRemotes();
|
|
86
|
+
|
|
87
|
+
if (remotes.length === 0) {
|
|
88
|
+
console.log('\x1b[33m[!]\x1b[0m No cloud remotes configured.');
|
|
89
|
+
console.log('');
|
|
90
|
+
console.log('Configure a remote:');
|
|
91
|
+
console.log('');
|
|
92
|
+
console.log(' \x1b[36mrclone config\x1b[0m');
|
|
93
|
+
console.log('');
|
|
94
|
+
console.log('Popular options:');
|
|
95
|
+
console.log(' • Google Drive: Choose "drive"');
|
|
96
|
+
console.log(' • Dropbox: Choose "dropbox"');
|
|
97
|
+
console.log(' • OneDrive: Choose "onedrive"');
|
|
98
|
+
console.log(' • S3/Minio: Choose "s3"');
|
|
99
|
+
console.log(' • SFTP: Choose "sftp"');
|
|
100
|
+
console.log(' • FTP: Choose "ftp"');
|
|
101
|
+
console.log('');
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
console.log('Available remotes:');
|
|
106
|
+
console.log('');
|
|
107
|
+
for (const remote of remotes) {
|
|
108
|
+
console.log(` \x1b[36m${remote}\x1b[0m`);
|
|
109
|
+
}
|
|
110
|
+
console.log('');
|
|
111
|
+
console.log('Usage:');
|
|
112
|
+
console.log('');
|
|
113
|
+
console.log(` \x1b[36mnpx zuppaclaude backup --cloud ${remotes[0]}\x1b[0m`);
|
|
114
|
+
console.log(` \x1b[36mnpx zuppaclaude restore --cloud ${remotes[0]}\x1b[0m`);
|
|
115
|
+
console.log('');
|
|
116
|
+
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* List remotes
|
|
122
|
+
*/
|
|
123
|
+
listRemotes() {
|
|
124
|
+
if (!this.isRcloneInstalled()) {
|
|
125
|
+
this.logger.error('rclone is not installed');
|
|
126
|
+
this.showSetupInstructions();
|
|
127
|
+
return [];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const remotes = this.getRemotes();
|
|
131
|
+
|
|
132
|
+
if (remotes.length === 0) {
|
|
133
|
+
this.logger.warning('No remotes configured');
|
|
134
|
+
this.showSetupInstructions();
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
console.log('');
|
|
139
|
+
console.log('\x1b[35m═══════════════════════════════════════════════════════════════════\x1b[0m');
|
|
140
|
+
console.log('\x1b[35m Available Cloud Remotes\x1b[0m');
|
|
141
|
+
console.log('\x1b[35m═══════════════════════════════════════════════════════════════════\x1b[0m');
|
|
142
|
+
console.log('');
|
|
143
|
+
|
|
144
|
+
for (const remote of remotes) {
|
|
145
|
+
// Try to get remote type
|
|
146
|
+
let type = 'unknown';
|
|
147
|
+
try {
|
|
148
|
+
const config = this.platform.exec(`rclone config show ${remote}`, { silent: true });
|
|
149
|
+
const typeMatch = config?.match(/type\s*=\s*(\w+)/);
|
|
150
|
+
if (typeMatch) type = typeMatch[1];
|
|
151
|
+
} catch (e) {
|
|
152
|
+
// Ignore
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const icon = this.getRemoteIcon(type);
|
|
156
|
+
console.log(` ${icon} \x1b[36m${remote}\x1b[0m (${type})`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
console.log('');
|
|
160
|
+
return remotes;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Get icon for remote type
|
|
165
|
+
*/
|
|
166
|
+
getRemoteIcon(type) {
|
|
167
|
+
const icons = {
|
|
168
|
+
'drive': '📁',
|
|
169
|
+
'dropbox': '📦',
|
|
170
|
+
'onedrive': '☁️',
|
|
171
|
+
's3': '🪣',
|
|
172
|
+
'sftp': '🔐',
|
|
173
|
+
'ftp': '📡',
|
|
174
|
+
'b2': '🅱️',
|
|
175
|
+
'box': '📥',
|
|
176
|
+
'mega': '🔷',
|
|
177
|
+
'pcloud': '☁️'
|
|
178
|
+
};
|
|
179
|
+
return icons[type] || '☁️';
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Upload backup to cloud
|
|
184
|
+
*/
|
|
185
|
+
async upload(remote, backupId = null) {
|
|
186
|
+
if (!this.isRcloneInstalled()) {
|
|
187
|
+
this.logger.error('rclone is not installed');
|
|
188
|
+
this.showSetupInstructions();
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (!this.remoteExists(remote)) {
|
|
193
|
+
this.logger.error(`Remote not found: ${remote}`);
|
|
194
|
+
this.logger.info('Run "npx zuppaclaude cloud remotes" to see available remotes');
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Determine what to upload
|
|
199
|
+
let sourcePath = this.backupDir;
|
|
200
|
+
let destPath = `${remote}:${this.cloudPath}`;
|
|
201
|
+
|
|
202
|
+
if (backupId) {
|
|
203
|
+
sourcePath = path.join(this.backupDir, backupId);
|
|
204
|
+
if (!fs.existsSync(sourcePath)) {
|
|
205
|
+
this.logger.error(`Backup not found: ${backupId}`);
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
destPath = `${remote}:${this.cloudPath}/${backupId}`;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (!fs.existsSync(sourcePath)) {
|
|
212
|
+
this.logger.error('No backups to upload');
|
|
213
|
+
this.logger.info('Run "npx zuppaclaude backup" first');
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
console.log('');
|
|
218
|
+
this.logger.step(`Uploading to ${remote}...`);
|
|
219
|
+
console.log('');
|
|
220
|
+
|
|
221
|
+
try {
|
|
222
|
+
// Use rclone sync for efficiency
|
|
223
|
+
const cmd = `rclone sync "${sourcePath}" "${destPath}" --progress`;
|
|
224
|
+
this.platform.exec(cmd, { silent: false, stdio: 'inherit' });
|
|
225
|
+
|
|
226
|
+
console.log('');
|
|
227
|
+
this.logger.success(`Backup uploaded to ${remote}:${this.cloudPath}`);
|
|
228
|
+
console.log('');
|
|
229
|
+
return true;
|
|
230
|
+
} catch (error) {
|
|
231
|
+
this.logger.error(`Upload failed: ${error.message}`);
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Download backup from cloud
|
|
238
|
+
*/
|
|
239
|
+
async download(remote, backupId = null) {
|
|
240
|
+
if (!this.isRcloneInstalled()) {
|
|
241
|
+
this.logger.error('rclone is not installed');
|
|
242
|
+
this.showSetupInstructions();
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (!this.remoteExists(remote)) {
|
|
247
|
+
this.logger.error(`Remote not found: ${remote}`);
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
let sourcePath = `${remote}:${this.cloudPath}`;
|
|
252
|
+
let destPath = this.backupDir;
|
|
253
|
+
|
|
254
|
+
if (backupId) {
|
|
255
|
+
sourcePath = `${remote}:${this.cloudPath}/${backupId}`;
|
|
256
|
+
destPath = path.join(this.backupDir, backupId);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
this.platform.ensureDir(destPath);
|
|
260
|
+
|
|
261
|
+
console.log('');
|
|
262
|
+
this.logger.step(`Downloading from ${remote}...`);
|
|
263
|
+
console.log('');
|
|
264
|
+
|
|
265
|
+
try {
|
|
266
|
+
const cmd = `rclone sync "${sourcePath}" "${destPath}" --progress`;
|
|
267
|
+
this.platform.exec(cmd, { silent: false, stdio: 'inherit' });
|
|
268
|
+
|
|
269
|
+
console.log('');
|
|
270
|
+
this.logger.success(`Backup downloaded from ${remote}`);
|
|
271
|
+
console.log('');
|
|
272
|
+
return true;
|
|
273
|
+
} catch (error) {
|
|
274
|
+
this.logger.error(`Download failed: ${error.message}`);
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* List cloud backups
|
|
281
|
+
*/
|
|
282
|
+
async listCloudBackups(remote) {
|
|
283
|
+
if (!this.isRcloneInstalled()) {
|
|
284
|
+
this.logger.error('rclone is not installed');
|
|
285
|
+
return [];
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (!this.remoteExists(remote)) {
|
|
289
|
+
this.logger.error(`Remote not found: ${remote}`);
|
|
290
|
+
return [];
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
try {
|
|
294
|
+
const cmd = `rclone lsd "${remote}:${this.cloudPath}" 2>/dev/null`;
|
|
295
|
+
const output = this.platform.exec(cmd, { silent: true });
|
|
296
|
+
|
|
297
|
+
if (!output) {
|
|
298
|
+
this.logger.warning('No cloud backups found');
|
|
299
|
+
return [];
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const backups = output
|
|
303
|
+
.split('\n')
|
|
304
|
+
.filter(line => line.trim())
|
|
305
|
+
.map(line => {
|
|
306
|
+
const parts = line.trim().split(/\s+/);
|
|
307
|
+
const name = parts[parts.length - 1];
|
|
308
|
+
return name;
|
|
309
|
+
})
|
|
310
|
+
.filter(name => name && name.match(/^\d{4}-\d{2}-\d{2}T/));
|
|
311
|
+
|
|
312
|
+
console.log('');
|
|
313
|
+
console.log('\x1b[35m═══════════════════════════════════════════════════════════════════\x1b[0m');
|
|
314
|
+
console.log(`\x1b[35m Cloud Backups (${remote})\x1b[0m`);
|
|
315
|
+
console.log('\x1b[35m═══════════════════════════════════════════════════════════════════\x1b[0m');
|
|
316
|
+
console.log('');
|
|
317
|
+
|
|
318
|
+
if (backups.length === 0) {
|
|
319
|
+
console.log(' No backups found');
|
|
320
|
+
} else {
|
|
321
|
+
for (const backup of backups) {
|
|
322
|
+
console.log(` 📦 ${backup}`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
console.log('');
|
|
327
|
+
return backups;
|
|
328
|
+
} catch (error) {
|
|
329
|
+
this.logger.warning('Could not list cloud backups');
|
|
330
|
+
return [];
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
module.exports = { CloudManager };
|
package/lib/components/index.js
CHANGED
|
@@ -8,6 +8,8 @@ const { ConfigInstaller } = require('./config');
|
|
|
8
8
|
const { ClaudeZInstaller } = require('./claudez');
|
|
9
9
|
const { ClaudeHUDInstaller } = require('./claudehud');
|
|
10
10
|
const { SessionManager } = require('./session');
|
|
11
|
+
const { CloudManager } = require('./cloud');
|
|
12
|
+
const { BackupManager } = require('./backup');
|
|
11
13
|
|
|
12
14
|
module.exports = {
|
|
13
15
|
SuperClaudeInstaller,
|
|
@@ -15,5 +17,7 @@ module.exports = {
|
|
|
15
17
|
ConfigInstaller,
|
|
16
18
|
ClaudeZInstaller,
|
|
17
19
|
ClaudeHUDInstaller,
|
|
18
|
-
SessionManager
|
|
20
|
+
SessionManager,
|
|
21
|
+
CloudManager,
|
|
22
|
+
BackupManager
|
|
19
23
|
};
|