zuppaclaude 1.3.5 → 1.3.7
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/bin/zuppaclaude.js +19 -0
- package/lib/components/backup.js +1 -1
- package/lib/components/cloud.js +237 -102
- package/lib/utils/prompts.js +18 -0
- package/package.json +1 -1
package/bin/zuppaclaude.js
CHANGED
|
@@ -202,6 +202,21 @@ async function main() {
|
|
|
202
202
|
}
|
|
203
203
|
await cloudMgr.listCloudBackups(cloudRemote);
|
|
204
204
|
break;
|
|
205
|
+
case 'delete':
|
|
206
|
+
case 'rm':
|
|
207
|
+
if (!cloudRemote) {
|
|
208
|
+
logger.error('Please specify remote');
|
|
209
|
+
logger.info('Usage: zuppaclaude cloud delete <remote> [backup-id]');
|
|
210
|
+
process.exit(1);
|
|
211
|
+
}
|
|
212
|
+
if (args[3]) {
|
|
213
|
+
// Direct delete with backup ID
|
|
214
|
+
await cloudMgr.deleteCloudBackup(cloudRemote, args[3]);
|
|
215
|
+
} else {
|
|
216
|
+
// Interactive delete menu
|
|
217
|
+
await cloudMgr.deleteCloudBackupInteractive(cloudRemote);
|
|
218
|
+
}
|
|
219
|
+
break;
|
|
205
220
|
default:
|
|
206
221
|
logger.error(`Unknown cloud command: ${cloudCmd}`);
|
|
207
222
|
showCloudHelp();
|
|
@@ -296,6 +311,7 @@ Cloud Commands:
|
|
|
296
311
|
cloud upload <r> Upload backups to remote
|
|
297
312
|
cloud download <r> Download backups from remote
|
|
298
313
|
cloud backups <r> List cloud backups
|
|
314
|
+
cloud delete <r> Delete backup from cloud (interactive)
|
|
299
315
|
|
|
300
316
|
Update Commands:
|
|
301
317
|
update Check for updates
|
|
@@ -363,6 +379,7 @@ Cloud Commands (requires rclone):
|
|
|
363
379
|
upload Upload backups to a cloud remote
|
|
364
380
|
download Download backups from a cloud remote
|
|
365
381
|
backups List backups stored on a cloud remote
|
|
382
|
+
delete Delete backup from cloud (interactive menu)
|
|
366
383
|
|
|
367
384
|
Examples:
|
|
368
385
|
zuppaclaude cloud setup # Setup instructions
|
|
@@ -370,6 +387,8 @@ Examples:
|
|
|
370
387
|
zuppaclaude cloud upload gdrive # Upload all backups
|
|
371
388
|
zuppaclaude cloud download gdrive # Download all backups
|
|
372
389
|
zuppaclaude cloud backups gdrive # List cloud backups
|
|
390
|
+
zuppaclaude cloud delete gdrive # Interactive delete menu
|
|
391
|
+
zuppaclaude cloud delete gdrive Jan-05-2026-14.09 # Direct delete
|
|
373
392
|
|
|
374
393
|
Supported providers (via rclone):
|
|
375
394
|
Google Drive, Dropbox, OneDrive, S3, SFTP, FTP, and 40+ more
|
package/lib/components/backup.js
CHANGED
|
@@ -212,7 +212,7 @@ class BackupManager {
|
|
|
212
212
|
console.log(` 📍 Location: ${result.path}`);
|
|
213
213
|
|
|
214
214
|
if (cloud) {
|
|
215
|
-
console.log(` ☁️ Cloud: ${cloud}:zuppaclaude-backups/${result.timestamp}`);
|
|
215
|
+
console.log(` ☁️ Cloud: ${cloud}:zuppaclaude-backups/sessions/${result.timestamp}.zip`);
|
|
216
216
|
}
|
|
217
217
|
}
|
|
218
218
|
|
package/lib/components/cloud.js
CHANGED
|
@@ -491,7 +491,7 @@ class CloudManager {
|
|
|
491
491
|
}
|
|
492
492
|
|
|
493
493
|
/**
|
|
494
|
-
* Upload a single backup as
|
|
494
|
+
* Upload a single backup as separate zips (sessions + settings)
|
|
495
495
|
*/
|
|
496
496
|
async uploadSingleBackup(remote, backupId) {
|
|
497
497
|
const sessionsPath = path.join(this.backupDir, 'sessions', backupId);
|
|
@@ -507,80 +507,72 @@ class CloudManager {
|
|
|
507
507
|
this.platform.ensureDir(tempDir);
|
|
508
508
|
|
|
509
509
|
const zipFileName = `${backupId}.zip`;
|
|
510
|
-
|
|
510
|
+
let totalSize = 0;
|
|
511
|
+
let uploaded = 0;
|
|
511
512
|
|
|
512
|
-
|
|
513
|
+
// Upload sessions zip
|
|
514
|
+
this.logger.info(`Compressing sessions/${backupId}...`);
|
|
515
|
+
const sessionsZipPath = path.join(tempDir, `sessions-${zipFileName}`);
|
|
513
516
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
+
if (this.createZip(sessionsPath, sessionsZipPath)) {
|
|
518
|
+
const zipSize = fs.statSync(sessionsZipPath).size;
|
|
519
|
+
totalSize += zipSize;
|
|
520
|
+
this.logger.info(`Sessions zip: ${this.formatSize(zipSize)}`);
|
|
517
521
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
522
|
+
try {
|
|
523
|
+
this.logger.info(`Uploading to sessions/${zipFileName}...`);
|
|
524
|
+
const cmd = `rclone copy "${sessionsZipPath}" "${remote}:${this.cloudPath}/sessions/" --progress`;
|
|
525
|
+
this.platform.exec(cmd, { silent: false, stdio: 'inherit' });
|
|
526
|
+
|
|
527
|
+
// Rename on remote to remove 'sessions-' prefix
|
|
528
|
+
this.platform.exec(`rclone moveto "${remote}:${this.cloudPath}/sessions/sessions-${zipFileName}" "${remote}:${this.cloudPath}/sessions/${zipFileName}"`, { silent: true });
|
|
529
|
+
|
|
530
|
+
uploaded++;
|
|
531
|
+
} catch (error) {
|
|
532
|
+
this.logger.error(`Failed to upload sessions: ${error.message}`);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Cleanup
|
|
536
|
+
try { fs.unlinkSync(sessionsZipPath); } catch (e) {}
|
|
526
537
|
}
|
|
527
538
|
|
|
528
|
-
//
|
|
539
|
+
// Upload settings zip if exists and has content
|
|
529
540
|
if (fs.existsSync(settingsPath)) {
|
|
530
541
|
const settingsFiles = fs.readdirSync(settingsPath);
|
|
531
542
|
if (settingsFiles.length > 0) {
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
try {
|
|
535
|
-
this.platform.exec(`cp -r "${settingsPath}"/* "${settingsDestDir}/"`, { silent: true });
|
|
536
|
-
} catch (e) {
|
|
537
|
-
// Ignore if empty
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
// Create zip
|
|
543
|
-
const zipCreated = this.createZip(combinedDir, zipPath);
|
|
544
|
-
if (!zipCreated) {
|
|
545
|
-
this.logger.error('Failed to create zip');
|
|
546
|
-
return false;
|
|
547
|
-
}
|
|
543
|
+
this.logger.info(`Compressing settings/${backupId}...`);
|
|
544
|
+
const settingsZipPath = path.join(tempDir, `settings-${zipFileName}`);
|
|
548
545
|
|
|
549
|
-
|
|
550
|
-
|
|
546
|
+
if (this.createZip(settingsPath, settingsZipPath)) {
|
|
547
|
+
const zipSize = fs.statSync(settingsZipPath).size;
|
|
548
|
+
totalSize += zipSize;
|
|
549
|
+
this.logger.info(`Settings zip: ${this.formatSize(zipSize)}`);
|
|
551
550
|
|
|
552
|
-
|
|
553
|
-
|
|
551
|
+
try {
|
|
552
|
+
this.logger.info(`Uploading to settings/${zipFileName}...`);
|
|
553
|
+
const cmd = `rclone copy "${settingsZipPath}" "${remote}:${this.cloudPath}/settings/" --progress`;
|
|
554
|
+
this.platform.exec(cmd, { silent: false, stdio: 'inherit' });
|
|
554
555
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
const cmd = `rclone copy "${zipPath}" "${remote}:${this.cloudPath}/" --progress`;
|
|
558
|
-
this.platform.exec(cmd, { silent: false, stdio: 'inherit' });
|
|
556
|
+
// Rename on remote
|
|
557
|
+
this.platform.exec(`rclone moveto "${remote}:${this.cloudPath}/settings/settings-${zipFileName}" "${remote}:${this.cloudPath}/settings/${zipFileName}"`, { silent: true });
|
|
559
558
|
|
|
560
|
-
|
|
559
|
+
uploaded++;
|
|
560
|
+
} catch (error) {
|
|
561
|
+
this.logger.error(`Failed to upload settings: ${error.message}`);
|
|
562
|
+
}
|
|
561
563
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
fs.rmSync(combinedDir, { recursive: true, force: true });
|
|
566
|
-
} catch (e) {
|
|
567
|
-
// Ignore cleanup errors
|
|
564
|
+
// Cleanup
|
|
565
|
+
try { fs.unlinkSync(settingsZipPath); } catch (e) {}
|
|
566
|
+
}
|
|
568
567
|
}
|
|
568
|
+
}
|
|
569
569
|
|
|
570
|
+
if (uploaded > 0) {
|
|
571
|
+
this.logger.success(`Uploaded ${backupId} (${this.formatSize(totalSize)})`);
|
|
570
572
|
return true;
|
|
571
|
-
} catch (error) {
|
|
572
|
-
this.logger.error(`Upload failed: ${error.message}`);
|
|
573
|
-
|
|
574
|
-
// Cleanup on error
|
|
575
|
-
try {
|
|
576
|
-
fs.unlinkSync(zipPath);
|
|
577
|
-
fs.rmSync(combinedDir, { recursive: true, force: true });
|
|
578
|
-
} catch (e) {
|
|
579
|
-
// Ignore
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
return false;
|
|
583
573
|
}
|
|
574
|
+
|
|
575
|
+
return false;
|
|
584
576
|
}
|
|
585
577
|
|
|
586
578
|
/**
|
|
@@ -652,78 +644,218 @@ class CloudManager {
|
|
|
652
644
|
}
|
|
653
645
|
|
|
654
646
|
/**
|
|
655
|
-
* Download and extract a single backup
|
|
647
|
+
* Download and extract a single backup (from sessions/ and settings/ folders)
|
|
656
648
|
*/
|
|
657
649
|
async downloadSingleBackup(remote, backupId) {
|
|
658
650
|
const zipFileName = `${backupId}.zip`;
|
|
659
651
|
const tempDir = path.join(this.backupDir, '.temp');
|
|
660
652
|
this.platform.ensureDir(tempDir);
|
|
661
|
-
const zipPath = path.join(tempDir, zipFileName);
|
|
662
653
|
|
|
654
|
+
let downloaded = 0;
|
|
655
|
+
|
|
656
|
+
// Download sessions zip
|
|
657
|
+
const sessionsZipPath = path.join(tempDir, `sessions-${zipFileName}`);
|
|
663
658
|
try {
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
const cmd = `rclone copy "${remote}:${this.cloudPath}/${zipFileName}" "${tempDir}/" --progress`;
|
|
659
|
+
this.logger.info(`Downloading sessions/${zipFileName}...`);
|
|
660
|
+
const cmd = `rclone copy "${remote}:${this.cloudPath}/sessions/${zipFileName}" "${tempDir}/" --progress`;
|
|
667
661
|
this.platform.exec(cmd, { silent: false, stdio: 'inherit' });
|
|
668
662
|
|
|
669
|
-
if (
|
|
670
|
-
|
|
671
|
-
|
|
663
|
+
// Check if downloaded (rclone names it as the original)
|
|
664
|
+
const downloadedPath = path.join(tempDir, zipFileName);
|
|
665
|
+
if (fs.existsSync(downloadedPath)) {
|
|
666
|
+
fs.renameSync(downloadedPath, sessionsZipPath);
|
|
672
667
|
}
|
|
673
668
|
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
const extractDir = path.join(tempDir, 'extract');
|
|
677
|
-
this.platform.ensureDir(extractDir);
|
|
678
|
-
this.extractZip(zipPath, extractDir);
|
|
679
|
-
|
|
680
|
-
// Move sessions and settings to proper locations
|
|
681
|
-
const extractedBackup = path.join(extractDir, backupId);
|
|
682
|
-
|
|
683
|
-
if (fs.existsSync(path.join(extractedBackup, 'sessions'))) {
|
|
669
|
+
if (fs.existsSync(sessionsZipPath)) {
|
|
670
|
+
this.logger.info(`Extracting sessions...`);
|
|
684
671
|
const sessionsDir = path.join(this.backupDir, 'sessions', backupId);
|
|
685
|
-
this.platform.ensureDir(
|
|
686
|
-
|
|
687
|
-
|
|
672
|
+
this.platform.ensureDir(sessionsDir);
|
|
673
|
+
this.extractZip(sessionsZipPath, sessionsDir);
|
|
674
|
+
|
|
675
|
+
// Move contents up if nested
|
|
676
|
+
const nestedDir = path.join(sessionsDir, backupId);
|
|
677
|
+
if (fs.existsSync(nestedDir)) {
|
|
678
|
+
const files = fs.readdirSync(nestedDir);
|
|
679
|
+
for (const file of files) {
|
|
680
|
+
fs.renameSync(path.join(nestedDir, file), path.join(sessionsDir, file));
|
|
681
|
+
}
|
|
682
|
+
fs.rmdirSync(nestedDir);
|
|
688
683
|
}
|
|
689
|
-
|
|
684
|
+
|
|
685
|
+
downloaded++;
|
|
686
|
+
try { fs.unlinkSync(sessionsZipPath); } catch (e) {}
|
|
687
|
+
}
|
|
688
|
+
} catch (error) {
|
|
689
|
+
this.logger.warning(`Sessions not found or failed: ${error.message}`);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// Download settings zip
|
|
693
|
+
const settingsZipPath = path.join(tempDir, `settings-${zipFileName}`);
|
|
694
|
+
try {
|
|
695
|
+
this.logger.info(`Downloading settings/${zipFileName}...`);
|
|
696
|
+
const cmd = `rclone copy "${remote}:${this.cloudPath}/settings/${zipFileName}" "${tempDir}/" --progress`;
|
|
697
|
+
this.platform.exec(cmd, { silent: false, stdio: 'inherit' });
|
|
698
|
+
|
|
699
|
+
const downloadedPath = path.join(tempDir, zipFileName);
|
|
700
|
+
if (fs.existsSync(downloadedPath)) {
|
|
701
|
+
fs.renameSync(downloadedPath, settingsZipPath);
|
|
690
702
|
}
|
|
691
703
|
|
|
692
|
-
if (fs.existsSync(
|
|
704
|
+
if (fs.existsSync(settingsZipPath)) {
|
|
705
|
+
this.logger.info(`Extracting settings...`);
|
|
693
706
|
const settingsDir = path.join(this.backupDir, 'settings', backupId);
|
|
694
|
-
this.platform.ensureDir(
|
|
695
|
-
|
|
696
|
-
|
|
707
|
+
this.platform.ensureDir(settingsDir);
|
|
708
|
+
this.extractZip(settingsZipPath, settingsDir);
|
|
709
|
+
|
|
710
|
+
// Move contents up if nested
|
|
711
|
+
const nestedDir = path.join(settingsDir, backupId);
|
|
712
|
+
if (fs.existsSync(nestedDir)) {
|
|
713
|
+
const files = fs.readdirSync(nestedDir);
|
|
714
|
+
for (const file of files) {
|
|
715
|
+
fs.renameSync(path.join(nestedDir, file), path.join(settingsDir, file));
|
|
716
|
+
}
|
|
717
|
+
fs.rmdirSync(nestedDir);
|
|
697
718
|
}
|
|
698
|
-
fs.renameSync(path.join(extractedBackup, 'settings'), settingsDir);
|
|
699
|
-
}
|
|
700
719
|
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
fs.unlinkSync(zipPath);
|
|
704
|
-
fs.rmSync(extractDir, { recursive: true, force: true });
|
|
705
|
-
} catch (e) {
|
|
706
|
-
// Ignore cleanup errors
|
|
720
|
+
downloaded++;
|
|
721
|
+
try { fs.unlinkSync(settingsZipPath); } catch (e) {}
|
|
707
722
|
}
|
|
723
|
+
} catch (error) {
|
|
724
|
+
// Settings might not exist, that's ok
|
|
725
|
+
}
|
|
708
726
|
|
|
727
|
+
if (downloaded > 0) {
|
|
709
728
|
this.logger.success(`Downloaded ${backupId}`);
|
|
710
729
|
return true;
|
|
711
|
-
}
|
|
712
|
-
this.logger.error(`Failed to download ${backupId}: ${error.message}`);
|
|
730
|
+
}
|
|
713
731
|
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
732
|
+
this.logger.error(`Backup not found: ${backupId}`);
|
|
733
|
+
return false;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
/**
|
|
737
|
+
* Delete a backup from cloud (both sessions and settings)
|
|
738
|
+
*/
|
|
739
|
+
async deleteCloudBackup(remote, backupId) {
|
|
740
|
+
if (!this.isRcloneInstalled()) {
|
|
741
|
+
this.logger.error('rclone is not installed');
|
|
742
|
+
return false;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
if (!this.remoteExists(remote)) {
|
|
746
|
+
this.logger.error(`Remote not found: ${remote}`);
|
|
747
|
+
return false;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
const zipFileName = `${backupId}.zip`;
|
|
751
|
+
let deleted = 0;
|
|
752
|
+
|
|
753
|
+
this.logger.step(`Deleting ${backupId} from ${remote}...`);
|
|
754
|
+
|
|
755
|
+
// Delete sessions zip
|
|
756
|
+
try {
|
|
757
|
+
const sessionsPath = `${remote}:${this.cloudPath}/sessions/${zipFileName}`;
|
|
758
|
+
this.platform.exec(`rclone delete "${sessionsPath}"`, { silent: true });
|
|
759
|
+
deleted++;
|
|
760
|
+
} catch (e) {
|
|
761
|
+
// Ignore
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// Delete settings zip
|
|
765
|
+
try {
|
|
766
|
+
const settingsPath = `${remote}:${this.cloudPath}/settings/${zipFileName}`;
|
|
767
|
+
this.platform.exec(`rclone delete "${settingsPath}"`, { silent: true });
|
|
768
|
+
deleted++;
|
|
769
|
+
} catch (e) {
|
|
770
|
+
// Ignore
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
if (deleted > 0) {
|
|
774
|
+
this.logger.success(`Deleted ${backupId} from ${remote}`);
|
|
775
|
+
return true;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
this.logger.error(`Backup not found on cloud: ${backupId}`);
|
|
779
|
+
return false;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
/**
|
|
783
|
+
* Interactive delete - show list and let user choose
|
|
784
|
+
*/
|
|
785
|
+
async deleteCloudBackupInteractive(remote) {
|
|
786
|
+
if (!this.isRcloneInstalled()) {
|
|
787
|
+
this.logger.error('rclone is not installed');
|
|
788
|
+
return false;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
if (!this.remoteExists(remote)) {
|
|
792
|
+
this.logger.error(`Remote not found: ${remote}`);
|
|
793
|
+
return false;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// Get list of backups
|
|
797
|
+
const backups = await this.getCloudBackupList(remote);
|
|
798
|
+
|
|
799
|
+
if (backups.length === 0) {
|
|
800
|
+
this.logger.warning('No backups found on cloud');
|
|
801
|
+
return false;
|
|
802
|
+
}
|
|
720
803
|
|
|
804
|
+
console.log('');
|
|
805
|
+
console.log('\x1b[35m═══════════════════════════════════════════════════════════════════\x1b[0m');
|
|
806
|
+
console.log(`\x1b[35m Delete Cloud Backup (${remote})\x1b[0m`);
|
|
807
|
+
console.log('\x1b[35m═══════════════════════════════════════════════════════════════════\x1b[0m');
|
|
808
|
+
console.log('');
|
|
809
|
+
|
|
810
|
+
// Show numbered list
|
|
811
|
+
for (let i = 0; i < backups.length; i++) {
|
|
812
|
+
console.log(` ${i + 1}. 📦 ${backups[i]}`);
|
|
813
|
+
}
|
|
814
|
+
console.log(` ${backups.length + 1}. ❌ Cancel`);
|
|
815
|
+
console.log('');
|
|
816
|
+
|
|
817
|
+
// Get user choice
|
|
818
|
+
const choice = await this.prompts.number(`Select backup to delete (1-${backups.length + 1}):`, 1, backups.length + 1);
|
|
819
|
+
|
|
820
|
+
if (choice === backups.length + 1 || choice === null) {
|
|
821
|
+
this.logger.info('Cancelled');
|
|
721
822
|
return false;
|
|
722
823
|
}
|
|
824
|
+
|
|
825
|
+
const selectedBackup = backups[choice - 1];
|
|
826
|
+
|
|
827
|
+
// Confirm deletion
|
|
828
|
+
const confirm = await this.prompts.confirm(`Delete "${selectedBackup}" from ${remote}?`, false);
|
|
829
|
+
|
|
830
|
+
if (!confirm) {
|
|
831
|
+
this.logger.info('Cancelled');
|
|
832
|
+
return false;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
return await this.deleteCloudBackup(remote, selectedBackup);
|
|
723
836
|
}
|
|
724
837
|
|
|
725
838
|
/**
|
|
726
|
-
*
|
|
839
|
+
* Get list of cloud backups (without printing) - looks in sessions folder
|
|
840
|
+
*/
|
|
841
|
+
async getCloudBackupList(remote) {
|
|
842
|
+
try {
|
|
843
|
+
const cmd = `rclone lsf "${remote}:${this.cloudPath}/sessions/" --files-only 2>/dev/null`;
|
|
844
|
+
const output = this.platform.exec(cmd, { silent: true });
|
|
845
|
+
|
|
846
|
+
if (!output) return [];
|
|
847
|
+
|
|
848
|
+
return output
|
|
849
|
+
.split('\n')
|
|
850
|
+
.filter(f => f.endsWith('.zip'))
|
|
851
|
+
.map(f => f.replace('.zip', ''));
|
|
852
|
+
} catch (error) {
|
|
853
|
+
return [];
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
/**
|
|
858
|
+
* List cloud backups (from sessions folder)
|
|
727
859
|
*/
|
|
728
860
|
async listCloudBackups(remote) {
|
|
729
861
|
if (!this.isRcloneInstalled()) {
|
|
@@ -737,7 +869,7 @@ class CloudManager {
|
|
|
737
869
|
}
|
|
738
870
|
|
|
739
871
|
try {
|
|
740
|
-
const cmd = `rclone lsf "${remote}:${this.cloudPath}/" --files-only 2>/dev/null`;
|
|
872
|
+
const cmd = `rclone lsf "${remote}:${this.cloudPath}/sessions/" --files-only 2>/dev/null`;
|
|
741
873
|
const output = this.platform.exec(cmd, { silent: true });
|
|
742
874
|
|
|
743
875
|
console.log('');
|
|
@@ -745,6 +877,8 @@ class CloudManager {
|
|
|
745
877
|
console.log(`\x1b[35m Cloud Backups (${remote})\x1b[0m`);
|
|
746
878
|
console.log('\x1b[35m═══════════════════════════════════════════════════════════════════\x1b[0m');
|
|
747
879
|
console.log('');
|
|
880
|
+
console.log(` Path: ${remote}:${this.cloudPath}/`);
|
|
881
|
+
console.log('');
|
|
748
882
|
|
|
749
883
|
if (!output) {
|
|
750
884
|
console.log(' No backups found');
|
|
@@ -762,6 +896,7 @@ class CloudManager {
|
|
|
762
896
|
} else {
|
|
763
897
|
for (const backup of backups) {
|
|
764
898
|
console.log(` 📦 ${backup}`);
|
|
899
|
+
console.log(` └── sessions/${backup}.zip`);
|
|
765
900
|
}
|
|
766
901
|
}
|
|
767
902
|
|
package/lib/utils/prompts.js
CHANGED
|
@@ -129,6 +129,24 @@ class Prompts {
|
|
|
129
129
|
});
|
|
130
130
|
});
|
|
131
131
|
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Ask for a number within a range
|
|
135
|
+
*/
|
|
136
|
+
async number(question, min = 1, max = 10) {
|
|
137
|
+
const rl = this.createInterface();
|
|
138
|
+
|
|
139
|
+
return new Promise((resolve) => {
|
|
140
|
+
rl.question(`${question} `, (answer) => {
|
|
141
|
+
const num = parseInt(answer, 10);
|
|
142
|
+
if (!isNaN(num) && num >= min && num <= max) {
|
|
143
|
+
resolve(num);
|
|
144
|
+
} else {
|
|
145
|
+
resolve(null);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
}
|
|
132
150
|
}
|
|
133
151
|
|
|
134
152
|
module.exports = { Prompts };
|