zuppaclaude 1.3.2 → 1.3.4

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.
@@ -8,6 +8,7 @@
8
8
  const { Installer } = require('../lib/installer');
9
9
  const { Settings } = require('../lib/settings');
10
10
  const { Logger } = require('../lib/utils/logger');
11
+ const { UpdateManager } = require('../lib/components/updater');
11
12
 
12
13
  const args = process.argv.slice(2);
13
14
  const command = args[0] || 'install';
@@ -23,11 +24,26 @@ function getCloudArg(args) {
23
24
  return null;
24
25
  }
25
26
 
27
+ /**
28
+ * Check for updates at startup (non-blocking for some commands)
29
+ */
30
+ async function checkUpdates(skipCommands = ['version', 'v', '-v', '--version', 'help', 'h', '-h', '--help', 'update']) {
31
+ if (skipCommands.includes(command)) {
32
+ return;
33
+ }
34
+
35
+ const updater = new UpdateManager();
36
+ await updater.checkAndUpdate();
37
+ }
38
+
26
39
  async function main() {
27
40
  const logger = new Logger();
28
41
 
29
42
  logger.banner();
30
43
 
44
+ // Check for updates at startup
45
+ await checkUpdates();
46
+
31
47
  try {
32
48
  switch (command) {
33
49
  case 'install':
@@ -192,6 +208,37 @@ async function main() {
192
208
  }
193
209
  break;
194
210
 
211
+ case 'update':
212
+ const updateMgr = new UpdateManager();
213
+ const updateCmd = args[1] || 'check';
214
+
215
+ switch (updateCmd) {
216
+ case 'check':
217
+ await updateMgr.status();
218
+ break;
219
+ case 'now':
220
+ case 'install':
221
+ const result = await updateMgr.checkForUpdates();
222
+ if (result.hasUpdate) {
223
+ await updateMgr.update();
224
+ } else {
225
+ logger.success('You are already on the latest version');
226
+ }
227
+ break;
228
+ case 'enable':
229
+ case 'on':
230
+ updateMgr.enableAutoUpdate();
231
+ break;
232
+ case 'disable':
233
+ case 'off':
234
+ updateMgr.disableAutoUpdate();
235
+ break;
236
+ default:
237
+ logger.error(`Unknown update command: ${updateCmd}`);
238
+ showUpdateHelp();
239
+ }
240
+ break;
241
+
195
242
  case 'version':
196
243
  case 'v':
197
244
  case '-v':
@@ -233,6 +280,7 @@ Commands:
233
280
  settings, s Manage settings
234
281
  session Manage Claude Code sessions
235
282
  cloud Manage cloud remotes (rclone)
283
+ update Check for updates and manage auto-update
236
284
  version, v Show version
237
285
  help, h Show this help
238
286
 
@@ -249,6 +297,12 @@ Cloud Commands:
249
297
  cloud download <r> Download backups from remote
250
298
  cloud backups <r> List cloud backups
251
299
 
300
+ Update Commands:
301
+ update Check for updates
302
+ update now Update to latest version
303
+ update enable Enable auto-update (default)
304
+ update disable Disable auto-update
305
+
252
306
  Session Commands:
253
307
  session list List all sessions
254
308
  session backup Backup sessions only
@@ -262,6 +316,7 @@ Examples:
262
316
  npx zuppaclaude backup --cloud gdrive # Backup to Google Drive
263
317
  npx zuppaclaude restore 2026-01-05T12-00-00 # Restore from backup
264
318
  npx zuppaclaude cloud setup # Configure cloud
319
+ npx zuppaclaude update # Check for updates
265
320
  `);
266
321
  }
267
322
 
@@ -321,4 +376,20 @@ Supported providers (via rclone):
321
376
  `);
322
377
  }
323
378
 
379
+ function showUpdateHelp() {
380
+ console.log(`
381
+ Update Commands:
382
+ check Check for updates (default)
383
+ now Update to latest version immediately
384
+ enable Enable auto-update at startup
385
+ disable Disable auto-update
386
+
387
+ Examples:
388
+ zuppaclaude update # Check for updates
389
+ zuppaclaude update now # Update immediately
390
+ zuppaclaude update enable # Enable auto-update
391
+ zuppaclaude update disable # Disable auto-update
392
+ `);
393
+ }
394
+
324
395
  main();
@@ -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
  */
@@ -11,6 +11,7 @@ const { SessionManager } = require('./session');
11
11
  const { CloudManager } = require('./cloud');
12
12
  const { BackupManager } = require('./backup');
13
13
  const { CommandsInstaller } = require('./commands');
14
+ const { UpdateManager } = require('./updater');
14
15
 
15
16
  module.exports = {
16
17
  SuperClaudeInstaller,
@@ -21,5 +22,6 @@ module.exports = {
21
22
  SessionManager,
22
23
  CloudManager,
23
24
  BackupManager,
24
- CommandsInstaller
25
+ CommandsInstaller,
26
+ UpdateManager
25
27
  };
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Auto-Update Manager
3
+ * Checks for updates and auto-updates if enabled
4
+ */
5
+
6
+ const { execSync } = require('child_process');
7
+ const https = require('https');
8
+ const { Logger } = require('../utils/logger');
9
+ const { Settings } = require('../settings');
10
+
11
+ class UpdateManager {
12
+ constructor() {
13
+ this.logger = new Logger();
14
+ this.settings = new Settings();
15
+ this.packageName = 'zuppaclaude';
16
+ this.currentVersion = require('../../package.json').version;
17
+ }
18
+
19
+ /**
20
+ * Get latest version from npm
21
+ */
22
+ async getLatestVersion() {
23
+ return new Promise((resolve, reject) => {
24
+ const url = `https://registry.npmjs.org/${this.packageName}/latest`;
25
+
26
+ https.get(url, (res) => {
27
+ let data = '';
28
+
29
+ res.on('data', (chunk) => {
30
+ data += chunk;
31
+ });
32
+
33
+ res.on('end', () => {
34
+ try {
35
+ const json = JSON.parse(data);
36
+ resolve(json.version);
37
+ } catch (e) {
38
+ reject(e);
39
+ }
40
+ });
41
+ }).on('error', (err) => {
42
+ reject(err);
43
+ });
44
+ });
45
+ }
46
+
47
+ /**
48
+ * Compare versions (returns true if latest > current)
49
+ */
50
+ isNewerVersion(latest, current) {
51
+ const latestParts = latest.split('.').map(Number);
52
+ const currentParts = current.split('.').map(Number);
53
+
54
+ for (let i = 0; i < 3; i++) {
55
+ if (latestParts[i] > currentParts[i]) return true;
56
+ if (latestParts[i] < currentParts[i]) return false;
57
+ }
58
+ return false;
59
+ }
60
+
61
+ /**
62
+ * Check for updates
63
+ */
64
+ async checkForUpdates(silent = false) {
65
+ try {
66
+ const latestVersion = await this.getLatestVersion();
67
+ const hasUpdate = this.isNewerVersion(latestVersion, this.currentVersion);
68
+
69
+ if (hasUpdate && !silent) {
70
+ console.log('');
71
+ console.log('\x1b[33m╔═══════════════════════════════════════════════════════════════════╗\x1b[0m');
72
+ console.log('\x1b[33m║ Update Available! ║\x1b[0m');
73
+ console.log('\x1b[33m╚═══════════════════════════════════════════════════════════════════╝\x1b[0m');
74
+ console.log('');
75
+ console.log(` Current version: \x1b[31m${this.currentVersion}\x1b[0m`);
76
+ console.log(` Latest version: \x1b[32m${latestVersion}\x1b[0m`);
77
+ console.log('');
78
+ }
79
+
80
+ return {
81
+ hasUpdate,
82
+ currentVersion: this.currentVersion,
83
+ latestVersion
84
+ };
85
+ } catch (error) {
86
+ // Silently fail - don't interrupt user workflow
87
+ return {
88
+ hasUpdate: false,
89
+ currentVersion: this.currentVersion,
90
+ latestVersion: null,
91
+ error: error.message
92
+ };
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Update to latest version
98
+ */
99
+ async update() {
100
+ try {
101
+ this.logger.step('Updating ZuppaClaude...');
102
+
103
+ // Use npm to update globally
104
+ execSync(`npm install -g ${this.packageName}@latest`, {
105
+ stdio: 'inherit'
106
+ });
107
+
108
+ // Verify update
109
+ const newVersion = await this.getLatestVersion();
110
+ this.logger.success(`Updated to v${newVersion}`);
111
+
112
+ return true;
113
+ } catch (error) {
114
+ this.logger.error(`Update failed: ${error.message}`);
115
+ this.logger.info(`Manual update: npm install -g ${this.packageName}@latest`);
116
+ return false;
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Check and auto-update if enabled
122
+ * Called at startup
123
+ */
124
+ async checkAndUpdate() {
125
+ const userSettings = this.settings.load() || {};
126
+ const autoUpdate = userSettings.autoUpdate !== false; // Default true
127
+
128
+ const result = await this.checkForUpdates(true);
129
+
130
+ if (result.hasUpdate) {
131
+ if (autoUpdate) {
132
+ console.log('');
133
+ console.log(`\x1b[36m[i]\x1b[0m New version available: v${result.latestVersion}`);
134
+ console.log('\x1b[36m[i]\x1b[0m Auto-updating...');
135
+ console.log('');
136
+
137
+ const updated = await this.update();
138
+
139
+ if (updated) {
140
+ console.log('');
141
+ console.log('\x1b[32m[✓]\x1b[0m Update complete! Please restart to use the new version.');
142
+ console.log('');
143
+ return { updated: true, version: result.latestVersion };
144
+ }
145
+ } else {
146
+ // Just notify
147
+ console.log('');
148
+ console.log(`\x1b[33m[!]\x1b[0m Update available: v${this.currentVersion} → v${result.latestVersion}`);
149
+ console.log(`\x1b[33m[!]\x1b[0m Run: npm install -g ${this.packageName}@latest`);
150
+ console.log('');
151
+ return { updated: false, version: result.latestVersion };
152
+ }
153
+ }
154
+
155
+ return { updated: false, version: this.currentVersion };
156
+ }
157
+
158
+ /**
159
+ * Enable auto-update
160
+ */
161
+ enableAutoUpdate() {
162
+ const userSettings = this.settings.load() || {};
163
+ userSettings.autoUpdate = true;
164
+ this.settings.save(userSettings);
165
+ this.logger.success('Auto-update enabled');
166
+ }
167
+
168
+ /**
169
+ * Disable auto-update
170
+ */
171
+ disableAutoUpdate() {
172
+ const userSettings = this.settings.load() || {};
173
+ userSettings.autoUpdate = false;
174
+ this.settings.save(userSettings);
175
+ this.logger.success('Auto-update disabled');
176
+ }
177
+
178
+ /**
179
+ * Show update status
180
+ */
181
+ async status() {
182
+ const userSettings = this.settings.load() || {};
183
+ const autoUpdate = userSettings.autoUpdate !== false;
184
+
185
+ console.log('');
186
+ console.log('\x1b[35m═══════════════════════════════════════════════════════════════════\x1b[0m');
187
+ console.log('\x1b[35m Update Status\x1b[0m');
188
+ console.log('\x1b[35m═══════════════════════════════════════════════════════════════════\x1b[0m');
189
+ console.log('');
190
+ console.log(` Current version: \x1b[36m${this.currentVersion}\x1b[0m`);
191
+ console.log(` Auto-update: ${autoUpdate ? '\x1b[32menabled\x1b[0m' : '\x1b[31mdisabled\x1b[0m'}`);
192
+ console.log('');
193
+
194
+ this.logger.info('Checking for updates...');
195
+ const result = await this.checkForUpdates(true);
196
+
197
+ if (result.latestVersion) {
198
+ console.log(` Latest version: \x1b[36m${result.latestVersion}\x1b[0m`);
199
+
200
+ if (result.hasUpdate) {
201
+ console.log('');
202
+ console.log(' \x1b[33mUpdate available!\x1b[0m');
203
+ console.log(` Run: \x1b[36mnpm install -g ${this.packageName}@latest\x1b[0m`);
204
+ } else {
205
+ console.log('');
206
+ console.log(' \x1b[32mYou are up to date!\x1b[0m');
207
+ }
208
+ } else {
209
+ console.log(' \x1b[31mCould not check for updates\x1b[0m');
210
+ }
211
+
212
+ console.log('');
213
+ }
214
+ }
215
+
216
+ module.exports = { UpdateManager };
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zuppaclaude",
3
- "version": "1.3.2",
3
+ "version": "1.3.4",
4
4
  "description": "Claude Code power-up installer - SuperClaude + Spec Kit + Claude-Z + Claude HUD",
5
5
  "main": "lib/index.js",
6
6
  "bin": {