zuppaclaude 1.3.2 → 1.3.3
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/lib/components/cloud.js +122 -0
- package/lib/installer.js +15 -0
- package/package.json +1 -1
package/lib/components/cloud.js
CHANGED
|
@@ -4,15 +4,28 @@
|
|
|
4
4
|
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const path = require('path');
|
|
7
|
+
const { execSync, spawn } = require('child_process');
|
|
7
8
|
const { Logger } = require('../utils/logger');
|
|
8
9
|
const { Platform } = require('../utils/platform');
|
|
10
|
+
const { Prompts } = require('../utils/prompts');
|
|
9
11
|
|
|
10
12
|
class CloudManager {
|
|
11
13
|
constructor() {
|
|
12
14
|
this.platform = new Platform();
|
|
13
15
|
this.logger = new Logger();
|
|
16
|
+
this.prompts = new Prompts();
|
|
14
17
|
this.backupDir = path.join(this.platform.zuppaconfigDir, 'backups');
|
|
15
18
|
this.cloudPath = 'zuppaclaude-backups';
|
|
19
|
+
|
|
20
|
+
// Supported providers
|
|
21
|
+
this.providers = [
|
|
22
|
+
{ name: 'Google Drive', type: 'drive', remoteName: 'gdrive' },
|
|
23
|
+
{ name: 'Dropbox', type: 'dropbox', remoteName: 'dropbox' },
|
|
24
|
+
{ name: 'OneDrive', type: 'onedrive', remoteName: 'onedrive' },
|
|
25
|
+
{ name: 'Amazon S3', type: 's3', remoteName: 's3' },
|
|
26
|
+
{ name: 'SFTP', type: 'sftp', remoteName: 'sftp' },
|
|
27
|
+
{ name: 'Skip (configure later)', type: null, remoteName: null }
|
|
28
|
+
];
|
|
16
29
|
}
|
|
17
30
|
|
|
18
31
|
/**
|
|
@@ -76,6 +89,115 @@ class CloudManager {
|
|
|
76
89
|
}
|
|
77
90
|
}
|
|
78
91
|
|
|
92
|
+
/**
|
|
93
|
+
* Configure a cloud provider
|
|
94
|
+
*/
|
|
95
|
+
async configureProvider() {
|
|
96
|
+
if (!this.isRcloneInstalled()) {
|
|
97
|
+
this.logger.error('rclone is not installed');
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
console.log('');
|
|
102
|
+
console.log('\x1b[35m═══════════════════════════════════════════════════════════════════\x1b[0m');
|
|
103
|
+
console.log('\x1b[35m Cloud Provider Setup\x1b[0m');
|
|
104
|
+
console.log('\x1b[35m═══════════════════════════════════════════════════════════════════\x1b[0m');
|
|
105
|
+
console.log('');
|
|
106
|
+
|
|
107
|
+
// Show provider options
|
|
108
|
+
const providerNames = this.providers.map(p => p.name);
|
|
109
|
+
const selectedIndex = await this.prompts.select('Select a cloud provider:', providerNames);
|
|
110
|
+
const provider = this.providers[selectedIndex];
|
|
111
|
+
|
|
112
|
+
if (!provider.type) {
|
|
113
|
+
this.logger.info('Skipping cloud provider configuration');
|
|
114
|
+
this.logger.info('Run "rclone config" later to set up manually');
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
console.log('');
|
|
119
|
+
this.logger.step(`Configuring ${provider.name}...`);
|
|
120
|
+
|
|
121
|
+
// Check if remote already exists
|
|
122
|
+
const existingRemotes = this.getRemotes();
|
|
123
|
+
if (existingRemotes.includes(provider.remoteName)) {
|
|
124
|
+
this.logger.warning(`Remote "${provider.remoteName}" already exists`);
|
|
125
|
+
const overwrite = await this.prompts.confirm('Overwrite existing configuration?', false);
|
|
126
|
+
if (!overwrite) {
|
|
127
|
+
return provider.remoteName;
|
|
128
|
+
}
|
|
129
|
+
// Delete existing remote
|
|
130
|
+
try {
|
|
131
|
+
this.platform.exec(`rclone config delete ${provider.remoteName}`, { silent: true });
|
|
132
|
+
} catch (e) {
|
|
133
|
+
// Ignore
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Run rclone config create
|
|
138
|
+
try {
|
|
139
|
+
await this.runProviderAuth(provider);
|
|
140
|
+
|
|
141
|
+
// Verify configuration
|
|
142
|
+
if (this.getRemotes().includes(provider.remoteName)) {
|
|
143
|
+
this.logger.success(`${provider.name} configured successfully`);
|
|
144
|
+
console.log('');
|
|
145
|
+
console.log(' Backup path: \x1b[36m' + provider.remoteName + ':zuppaclaude-backups/\x1b[0m');
|
|
146
|
+
console.log(' ├── sessions/');
|
|
147
|
+
console.log(' └── settings/');
|
|
148
|
+
console.log('');
|
|
149
|
+
return provider.remoteName;
|
|
150
|
+
} else {
|
|
151
|
+
this.logger.error('Configuration failed');
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
} catch (error) {
|
|
155
|
+
this.logger.error(`Failed to configure ${provider.name}: ${error.message}`);
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Run provider-specific authentication
|
|
162
|
+
*/
|
|
163
|
+
async runProviderAuth(provider) {
|
|
164
|
+
return new Promise((resolve, reject) => {
|
|
165
|
+
console.log('');
|
|
166
|
+
|
|
167
|
+
if (provider.type === 'drive' || provider.type === 'dropbox' || provider.type === 'onedrive') {
|
|
168
|
+
// OAuth providers - need browser auth
|
|
169
|
+
this.logger.info('A browser window will open for authentication...');
|
|
170
|
+
this.logger.info('Please authorize ZuppaClaude to access your ' + provider.name);
|
|
171
|
+
console.log('');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Use spawn for interactive rclone config
|
|
175
|
+
const args = ['config', 'create', provider.remoteName, provider.type];
|
|
176
|
+
|
|
177
|
+
// Add provider-specific defaults
|
|
178
|
+
if (provider.type === 'drive') {
|
|
179
|
+
args.push('scope', 'drive');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const rclone = spawn('rclone', args, {
|
|
183
|
+
stdio: 'inherit',
|
|
184
|
+
shell: true
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
rclone.on('close', (code) => {
|
|
188
|
+
if (code === 0) {
|
|
189
|
+
resolve();
|
|
190
|
+
} else {
|
|
191
|
+
reject(new Error(`rclone exited with code ${code}`));
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
rclone.on('error', (err) => {
|
|
196
|
+
reject(err);
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
79
201
|
/**
|
|
80
202
|
* Verify rclone installation
|
|
81
203
|
*/
|
package/lib/installer.js
CHANGED
|
@@ -130,6 +130,7 @@ class Installer {
|
|
|
130
130
|
this.logger.step('Step 7/9: Cloud Backup Setup (rclone)');
|
|
131
131
|
let installRclone = false;
|
|
132
132
|
let rcloneInstalled = this.cloud.isRcloneInstalled();
|
|
133
|
+
let configuredRemote = null;
|
|
133
134
|
|
|
134
135
|
if (rcloneInstalled) {
|
|
135
136
|
this.logger.success('rclone is already installed');
|
|
@@ -147,6 +148,19 @@ class Installer {
|
|
|
147
148
|
}
|
|
148
149
|
}
|
|
149
150
|
|
|
151
|
+
// Configure cloud provider if rclone is installed
|
|
152
|
+
if (rcloneInstalled) {
|
|
153
|
+
const existingRemotes = this.cloud.getRemotes();
|
|
154
|
+
if (existingRemotes.length === 0) {
|
|
155
|
+
const configureNow = await this.prompts.confirm('Configure a cloud provider now?', true);
|
|
156
|
+
if (configureNow) {
|
|
157
|
+
configuredRemote = await this.cloud.configureProvider();
|
|
158
|
+
}
|
|
159
|
+
} else {
|
|
160
|
+
this.logger.info(`Existing remotes: ${existingRemotes.join(', ')}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
150
164
|
// Step 8: Install ZuppaClaude Commands
|
|
151
165
|
this.logger.step('Step 8/9: Installing ZuppaClaude Slash Commands');
|
|
152
166
|
const cmdsInstalled = await this.commands.install();
|
|
@@ -170,6 +184,7 @@ class Installer {
|
|
|
170
184
|
claudeZ: installClaudeZ,
|
|
171
185
|
claudeHUD: installClaudeHUD,
|
|
172
186
|
rclone: rcloneInstalled,
|
|
187
|
+
cloudRemote: configuredRemote,
|
|
173
188
|
zaiApiKey: zaiApiKey ? this.settings.encodeApiKey(zaiApiKey) : null,
|
|
174
189
|
installedAt: new Date().toISOString(),
|
|
175
190
|
version: require('../package.json').version
|