zuppaclaude 1.3.1 → 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 +33 -5
- 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
|
@@ -56,15 +56,27 @@ class Installer {
|
|
|
56
56
|
useExisting = await this.prompts.confirm('Use previous settings?', true);
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
// Step 2: Install SuperClaude
|
|
60
|
-
|
|
59
|
+
// Step 2: Install SuperClaude (optional)
|
|
60
|
+
let installSuperClaude = true;
|
|
61
|
+
if (useExisting && existingSettings.superClaude !== undefined) {
|
|
62
|
+
installSuperClaude = existingSettings.superClaude;
|
|
63
|
+
} else {
|
|
64
|
+
installSuperClaude = await this.prompts.confirm('Install SuperClaude (30+ slash commands)?', true);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let scInstalled = false;
|
|
68
|
+
if (installSuperClaude) {
|
|
69
|
+
scInstalled = await this.superClaude.install();
|
|
70
|
+
} else {
|
|
71
|
+
this.logger.info('Skipping SuperClaude installation');
|
|
72
|
+
}
|
|
61
73
|
|
|
62
|
-
// Step 3: Install Spec Kit
|
|
74
|
+
// Step 3: Install Spec Kit (optional)
|
|
63
75
|
let installSpecKit = true;
|
|
64
76
|
if (useExisting && existingSettings.specKit !== undefined) {
|
|
65
77
|
installSpecKit = existingSettings.specKit;
|
|
66
78
|
} else {
|
|
67
|
-
installSpecKit = await this.prompts.confirm('Install Spec Kit (
|
|
79
|
+
installSpecKit = await this.prompts.confirm('Install Spec Kit (spec-driven development)?', true);
|
|
68
80
|
}
|
|
69
81
|
|
|
70
82
|
let skInstalled = false;
|
|
@@ -118,6 +130,7 @@ class Installer {
|
|
|
118
130
|
this.logger.step('Step 7/9: Cloud Backup Setup (rclone)');
|
|
119
131
|
let installRclone = false;
|
|
120
132
|
let rcloneInstalled = this.cloud.isRcloneInstalled();
|
|
133
|
+
let configuredRemote = null;
|
|
121
134
|
|
|
122
135
|
if (rcloneInstalled) {
|
|
123
136
|
this.logger.success('rclone is already installed');
|
|
@@ -135,6 +148,19 @@ class Installer {
|
|
|
135
148
|
}
|
|
136
149
|
}
|
|
137
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
|
+
|
|
138
164
|
// Step 8: Install ZuppaClaude Commands
|
|
139
165
|
this.logger.step('Step 8/9: Installing ZuppaClaude Slash Commands');
|
|
140
166
|
const cmdsInstalled = await this.commands.install();
|
|
@@ -143,7 +169,7 @@ class Installer {
|
|
|
143
169
|
this.logger.step('Step 9/9: Verifying Installation');
|
|
144
170
|
console.log('');
|
|
145
171
|
|
|
146
|
-
this.superClaude.verify();
|
|
172
|
+
if (installSuperClaude) this.superClaude.verify();
|
|
147
173
|
if (installSpecKit) this.specKit.verify();
|
|
148
174
|
this.config.verify();
|
|
149
175
|
if (installClaudeZ) this.claudeZ.verify();
|
|
@@ -153,10 +179,12 @@ class Installer {
|
|
|
153
179
|
|
|
154
180
|
// Save settings
|
|
155
181
|
const newSettings = {
|
|
182
|
+
superClaude: installSuperClaude,
|
|
156
183
|
specKit: installSpecKit,
|
|
157
184
|
claudeZ: installClaudeZ,
|
|
158
185
|
claudeHUD: installClaudeHUD,
|
|
159
186
|
rclone: rcloneInstalled,
|
|
187
|
+
cloudRemote: configuredRemote,
|
|
160
188
|
zaiApiKey: zaiApiKey ? this.settings.encodeApiKey(zaiApiKey) : null,
|
|
161
189
|
installedAt: new Date().toISOString(),
|
|
162
190
|
version: require('../package.json').version
|