zuppaclaude 1.0.2 → 1.2.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.
@@ -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 };
@@ -0,0 +1,139 @@
1
+ /**
2
+ * ZuppaClaude Commands Installer
3
+ * Installs /zc:* slash commands for Claude Code
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const { Logger } = require('../utils/logger');
9
+ const { Platform } = require('../utils/platform');
10
+
11
+ class CommandsInstaller {
12
+ constructor() {
13
+ this.platform = new Platform();
14
+ this.logger = new Logger();
15
+ this.commandsDir = path.join(this.platform.claudeDir, 'commands', 'zc');
16
+ this.sourceDir = path.join(__dirname, '..', '..', 'assets', 'commands', 'zc');
17
+ }
18
+
19
+ /**
20
+ * Check if commands are installed
21
+ */
22
+ isInstalled() {
23
+ return fs.existsSync(this.commandsDir) &&
24
+ fs.existsSync(path.join(this.commandsDir, 'help.md'));
25
+ }
26
+
27
+ /**
28
+ * Install ZuppaClaude commands
29
+ */
30
+ async install() {
31
+ this.logger.step('Installing ZuppaClaude Slash Commands');
32
+
33
+ try {
34
+ // Ensure commands directory exists
35
+ this.platform.ensureDir(this.commandsDir);
36
+
37
+ // Get list of command files
38
+ const files = fs.readdirSync(this.sourceDir);
39
+
40
+ let installed = 0;
41
+ for (const file of files) {
42
+ if (!file.endsWith('.md')) continue;
43
+
44
+ const srcPath = path.join(this.sourceDir, file);
45
+ const destPath = path.join(this.commandsDir, file);
46
+
47
+ try {
48
+ fs.copyFileSync(srcPath, destPath);
49
+ installed++;
50
+ } catch (error) {
51
+ this.logger.warning(`Failed to install ${file}: ${error.message}`);
52
+ }
53
+ }
54
+
55
+ this.logger.success(`Installed ${installed} slash commands`);
56
+ this.logger.info('Commands available: /zc:backup, /zc:restore, /zc:cloud, /zc:session, /zc:settings, /zc:help');
57
+
58
+ return true;
59
+ } catch (error) {
60
+ this.logger.error(`Failed to install commands: ${error.message}`);
61
+ return false;
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Uninstall commands
67
+ */
68
+ uninstall() {
69
+ if (!fs.existsSync(this.commandsDir)) {
70
+ this.logger.info('ZuppaClaude commands not found');
71
+ return true;
72
+ }
73
+
74
+ try {
75
+ fs.rmSync(this.commandsDir, { recursive: true });
76
+ this.logger.success('ZuppaClaude commands removed');
77
+ return true;
78
+ } catch (error) {
79
+ this.logger.error(`Failed to remove commands: ${error.message}`);
80
+ return false;
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Verify installation
86
+ */
87
+ verify() {
88
+ if (this.isInstalled()) {
89
+ this.logger.success('ZuppaClaude Commands: Installed');
90
+
91
+ // Count installed commands
92
+ try {
93
+ const files = fs.readdirSync(this.commandsDir)
94
+ .filter(f => f.endsWith('.md') && f !== 'README.md');
95
+ this.logger.info(`Available: /zc:${files.map(f => f.replace('.md', '')).join(', /zc:')}`);
96
+ } catch (e) {
97
+ // Ignore
98
+ }
99
+
100
+ return true;
101
+ } else {
102
+ this.logger.warning('ZuppaClaude Commands: Not installed');
103
+ return false;
104
+ }
105
+ }
106
+
107
+ /**
108
+ * List available commands
109
+ */
110
+ list() {
111
+ console.log('');
112
+ console.log('\x1b[35m═══════════════════════════════════════════════════════════════════\x1b[0m');
113
+ console.log('\x1b[35m ZuppaClaude Commands\x1b[0m');
114
+ console.log('\x1b[35m═══════════════════════════════════════════════════════════════════\x1b[0m');
115
+ console.log('');
116
+
117
+ const commands = [
118
+ { cmd: '/zc:backup', desc: 'Full backup (sessions + settings + cloud)' },
119
+ { cmd: '/zc:restore', desc: 'Restore from backup' },
120
+ { cmd: '/zc:cloud', desc: 'Cloud backup management' },
121
+ { cmd: '/zc:session', desc: 'Session management' },
122
+ { cmd: '/zc:settings', desc: 'Settings management' },
123
+ { cmd: '/zc:help', desc: 'Show help' }
124
+ ];
125
+
126
+ for (const { cmd, desc } of commands) {
127
+ console.log(` \x1b[36m${cmd.padEnd(18)}\x1b[0m ${desc}`);
128
+ }
129
+
130
+ console.log('');
131
+ console.log('Usage:');
132
+ console.log(' Type the command in Claude Code chat, e.g., /zc:backup');
133
+ console.log('');
134
+
135
+ return commands;
136
+ }
137
+ }
138
+
139
+ module.exports = { CommandsInstaller };
@@ -8,6 +8,9 @@ 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');
13
+ const { CommandsInstaller } = require('./commands');
11
14
 
12
15
  module.exports = {
13
16
  SuperClaudeInstaller,
@@ -15,5 +18,8 @@ module.exports = {
15
18
  ConfigInstaller,
16
19
  ClaudeZInstaller,
17
20
  ClaudeHUDInstaller,
18
- SessionManager
21
+ SessionManager,
22
+ CloudManager,
23
+ BackupManager,
24
+ CommandsInstaller
19
25
  };
package/lib/installer.js CHANGED
@@ -11,7 +11,8 @@ const {
11
11
  SpecKitInstaller,
12
12
  ConfigInstaller,
13
13
  ClaudeZInstaller,
14
- ClaudeHUDInstaller
14
+ ClaudeHUDInstaller,
15
+ CommandsInstaller
15
16
  } = require('./components');
16
17
 
17
18
  class Installer {
@@ -27,6 +28,7 @@ class Installer {
27
28
  this.config = new ConfigInstaller();
28
29
  this.claudeZ = new ClaudeZInstaller();
29
30
  this.claudeHUD = new ClaudeHUDInstaller();
31
+ this.commands = new CommandsInstaller();
30
32
  }
31
33
 
32
34
  /**
@@ -36,7 +38,7 @@ class Installer {
36
38
  this.logger.banner();
37
39
 
38
40
  // Step 1: Check dependencies
39
- this.logger.step('Step 1/7: Checking Dependencies');
41
+ this.logger.step('Step 1/8: Checking Dependencies');
40
42
  const depsOk = await this.checkDependencies();
41
43
  if (!depsOk) {
42
44
  this.logger.error('Dependency check failed. Please install required dependencies.');
@@ -110,8 +112,12 @@ class Installer {
110
112
  this.logger.info('Skipping Claude HUD installation');
111
113
  }
112
114
 
113
- // Step 7: Verification
114
- this.logger.step('Step 7/7: Verifying Installation');
115
+ // Step 7: Install ZuppaClaude Commands
116
+ this.logger.step('Step 7/8: Installing ZuppaClaude Slash Commands');
117
+ const cmdsInstalled = await this.commands.install();
118
+
119
+ // Step 8: Verification
120
+ this.logger.step('Step 8/8: Verifying Installation');
115
121
  console.log('');
116
122
 
117
123
  this.superClaude.verify();
@@ -119,6 +125,7 @@ class Installer {
119
125
  this.config.verify();
120
126
  if (installClaudeZ) this.claudeZ.verify();
121
127
  if (installClaudeHUD) this.claudeHUD.verify();
128
+ this.commands.verify();
122
129
 
123
130
  // Save settings
124
131
  const newSettings = {
@@ -139,7 +146,8 @@ class Installer {
139
146
  specKit: skInstalled,
140
147
  config: cfgInstalled,
141
148
  claudeZ: czInstalled,
142
- claudeHUD: hudInstalled
149
+ claudeHUD: hudInstalled,
150
+ commands: cmdsInstalled
143
151
  });
144
152
 
145
153
  return true;
@@ -205,7 +213,8 @@ class Installer {
205
213
  { name: 'Spec Kit', installed: results.specKit },
206
214
  { name: 'CLAUDE.md', installed: results.config },
207
215
  { name: 'Claude-Z', installed: results.claudeZ },
208
- { name: 'Claude HUD', installed: results.claudeHUD }
216
+ { name: 'Claude HUD', installed: results.claudeHUD },
217
+ { name: 'ZC Commands', installed: results.commands }
209
218
  ];
210
219
 
211
220
  let installed = 0;
@@ -223,7 +232,14 @@ class Installer {
223
232
  console.log(' Next steps:');
224
233
  console.log(' 1. Restart your terminal or run: source ~/.bashrc');
225
234
  console.log(' 2. Start Claude Code: claude');
226
- console.log(' 3. Try: /sc:help');
235
+ console.log(' 3. Try: /sc:help or /zc:help');
236
+ console.log('');
237
+
238
+ console.log(' ZuppaClaude commands:');
239
+ console.log(' /zc:backup - Full backup (sessions + settings)');
240
+ console.log(' /zc:restore - Restore from backup');
241
+ console.log(' /zc:cloud - Cloud backup management');
242
+ console.log(' /zc:help - Show all commands');
227
243
  console.log('');
228
244
 
229
245
  if (results.claudeZ) {
@@ -10,7 +10,8 @@ const {
10
10
  SpecKitInstaller,
11
11
  ConfigInstaller,
12
12
  ClaudeZInstaller,
13
- ClaudeHUDInstaller
13
+ ClaudeHUDInstaller,
14
+ CommandsInstaller
14
15
  } = require('./components');
15
16
 
16
17
  class Uninstaller {
@@ -25,6 +26,7 @@ class Uninstaller {
25
26
  this.config = new ConfigInstaller();
26
27
  this.claudeZ = new ClaudeZInstaller();
27
28
  this.claudeHUD = new ClaudeHUDInstaller();
29
+ this.commands = new CommandsInstaller();
28
30
  }
29
31
 
30
32
  /**
@@ -57,7 +59,8 @@ class Uninstaller {
57
59
  specKit: this.specKit.uninstall(),
58
60
  config: this.config.uninstall(),
59
61
  claudeZ: this.claudeZ.uninstall(),
60
- claudeHUD: this.claudeHUD.uninstall()
62
+ claudeHUD: this.claudeHUD.uninstall(),
63
+ commands: this.commands.uninstall()
61
64
  };
62
65
 
63
66
  // Handle settings
@@ -89,7 +92,8 @@ class Uninstaller {
89
92
  { name: 'Spec Kit', removed: results.specKit },
90
93
  { name: 'CLAUDE.md', removed: results.config },
91
94
  { name: 'Claude-Z', removed: results.claudeZ },
92
- { name: 'Claude HUD', removed: results.claudeHUD }
95
+ { name: 'Claude HUD', removed: results.claudeHUD },
96
+ { name: 'ZC Commands', removed: results.commands }
93
97
  ];
94
98
 
95
99
  let removed = 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zuppaclaude",
3
- "version": "1.0.2",
3
+ "version": "1.2.0",
4
4
  "description": "Claude Code power-up installer - SuperClaude + Spec Kit + Claude-Z + Claude HUD",
5
5
  "main": "lib/index.js",
6
6
  "bin": {