zuppaclaude 1.0.0 → 1.0.1

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.
@@ -71,6 +71,49 @@ async function main() {
71
71
  }
72
72
  break;
73
73
 
74
+ case 'session':
75
+ case 'sessions':
76
+ const { SessionManager } = require('../lib/components/session');
77
+ const sessionManager = new SessionManager();
78
+ const sessionCmd = args[1] || 'list';
79
+ const sessionArg = args[2];
80
+
81
+ switch (sessionCmd) {
82
+ case 'list':
83
+ case 'ls':
84
+ sessionManager.list();
85
+ break;
86
+ case 'backup':
87
+ case 'save':
88
+ sessionManager.backup();
89
+ break;
90
+ case 'backups':
91
+ sessionManager.listBackups();
92
+ break;
93
+ case 'restore':
94
+ if (!sessionArg) {
95
+ logger.error('Please specify backup ID');
96
+ logger.info('Usage: zuppaclaude session restore <backup-id>');
97
+ logger.info('Run "zuppaclaude session backups" to see available backups');
98
+ process.exit(1);
99
+ }
100
+ sessionManager.restore(sessionArg);
101
+ break;
102
+ case 'export':
103
+ if (!sessionArg) {
104
+ logger.error('Please specify session ID');
105
+ logger.info('Usage: zuppaclaude session export <session-id> [output-file]');
106
+ logger.info('Run "zuppaclaude session list" to see session IDs');
107
+ process.exit(1);
108
+ }
109
+ sessionManager.export(sessionArg, args[3]);
110
+ break;
111
+ default:
112
+ logger.error(`Unknown session command: ${sessionCmd}`);
113
+ showSessionHelp();
114
+ }
115
+ break;
116
+
74
117
  case 'version':
75
118
  case 'v':
76
119
  case '-v':
@@ -108,6 +151,7 @@ Commands:
108
151
  install, i Install ZuppaClaude components (default)
109
152
  uninstall, u Uninstall ZuppaClaude components
110
153
  settings, s Manage settings
154
+ session Manage Claude Code sessions
111
155
  version, v Show version
112
156
  help, h Show this help
113
157
 
@@ -118,12 +162,20 @@ Settings Commands:
118
162
  settings reset Reset settings to default
119
163
  settings path Show settings file path
120
164
 
165
+ Session Commands:
166
+ session list List all sessions
167
+ session backup Backup all sessions
168
+ session backups List available backups
169
+ session restore Restore from backup
170
+ session export Export a specific session
171
+
121
172
  Examples:
122
173
  npx zuppaclaude # Install
123
174
  npx zuppaclaude install # Install
124
175
  npx zuppaclaude uninstall # Uninstall
125
176
  npx zuppaclaude settings show # View settings
126
- npx zuppaclaude settings export ~/backup.json
177
+ npx zuppaclaude session backup # Backup sessions
178
+ npx zuppaclaude session restore 2026-01-05T12-00-00
127
179
  `);
128
180
  }
129
181
 
@@ -144,4 +196,22 @@ Examples:
144
196
  `);
145
197
  }
146
198
 
199
+ function showSessionHelp() {
200
+ console.log(`
201
+ Session Commands:
202
+ list List all Claude Code sessions
203
+ backup Backup all sessions to ~/.config/zuppaclaude/backups/
204
+ backups List available backups
205
+ restore Restore sessions from a backup
206
+ export Export a specific session to a file
207
+
208
+ Examples:
209
+ zuppaclaude session list
210
+ zuppaclaude session backup
211
+ zuppaclaude session backups
212
+ zuppaclaude session restore 2026-01-05T12-00-00
213
+ zuppaclaude session export abc123 ./my-session.jsonl
214
+ `);
215
+ }
216
+
147
217
  main();
@@ -7,11 +7,13 @@ const { SpecKitInstaller } = require('./speckit');
7
7
  const { ConfigInstaller } = require('./config');
8
8
  const { ClaudeZInstaller } = require('./claudez');
9
9
  const { ClaudeHUDInstaller } = require('./claudehud');
10
+ const { SessionManager } = require('./session');
10
11
 
11
12
  module.exports = {
12
13
  SuperClaudeInstaller,
13
14
  SpecKitInstaller,
14
15
  ConfigInstaller,
15
16
  ClaudeZInstaller,
16
- ClaudeHUDInstaller
17
+ ClaudeHUDInstaller,
18
+ SessionManager
17
19
  };
@@ -0,0 +1,425 @@
1
+ /**
2
+ * Session Manager - Backup and restore Claude Code sessions
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 SessionManager {
11
+ constructor() {
12
+ this.platform = new Platform();
13
+ this.logger = new Logger();
14
+ this.claudeDir = this.platform.claudeDir;
15
+ this.projectsDir = path.join(this.claudeDir, 'projects');
16
+ this.backupDir = path.join(this.platform.zuppaconfigDir, 'backups');
17
+ }
18
+
19
+ /**
20
+ * Get all projects with sessions
21
+ */
22
+ getProjects() {
23
+ if (!fs.existsSync(this.projectsDir)) {
24
+ return [];
25
+ }
26
+
27
+ return fs.readdirSync(this.projectsDir)
28
+ .filter(name => {
29
+ const projectPath = path.join(this.projectsDir, name);
30
+ return fs.statSync(projectPath).isDirectory() && name !== '.' && name !== '..';
31
+ })
32
+ .map(name => {
33
+ const projectPath = path.join(this.projectsDir, name);
34
+ const sessions = this.getProjectSessions(projectPath);
35
+ const realPath = name.replace(/-/g, '/').replace(/^\//, '');
36
+
37
+ return {
38
+ id: name,
39
+ path: realPath,
40
+ sessionsDir: projectPath,
41
+ sessions: sessions,
42
+ totalSize: sessions.reduce((sum, s) => sum + s.size, 0)
43
+ };
44
+ })
45
+ .filter(p => p.sessions.length > 0);
46
+ }
47
+
48
+ /**
49
+ * Get sessions for a project
50
+ */
51
+ getProjectSessions(projectPath) {
52
+ if (!fs.existsSync(projectPath)) {
53
+ return [];
54
+ }
55
+
56
+ return fs.readdirSync(projectPath)
57
+ .filter(name => name.endsWith('.jsonl'))
58
+ .map(name => {
59
+ const filePath = path.join(projectPath, name);
60
+ const stats = fs.statSync(filePath);
61
+ const isAgent = name.startsWith('agent-');
62
+
63
+ return {
64
+ id: name.replace('.jsonl', ''),
65
+ file: name,
66
+ path: filePath,
67
+ size: stats.size,
68
+ modified: stats.mtime,
69
+ isAgent: isAgent,
70
+ type: isAgent ? 'agent' : 'main'
71
+ };
72
+ })
73
+ .sort((a, b) => b.modified - a.modified);
74
+ }
75
+
76
+ /**
77
+ * List all sessions
78
+ */
79
+ list(options = {}) {
80
+ const projects = this.getProjects();
81
+
82
+ if (projects.length === 0) {
83
+ this.logger.warning('No sessions found');
84
+ return [];
85
+ }
86
+
87
+ console.log('');
88
+ console.log('\x1b[35m═══════════════════════════════════════════════════════════════════\x1b[0m');
89
+ console.log('\x1b[35m Claude Code Sessions\x1b[0m');
90
+ console.log('\x1b[35m═══════════════════════════════════════════════════════════════════\x1b[0m');
91
+ console.log('');
92
+
93
+ let totalSessions = 0;
94
+ let totalSize = 0;
95
+
96
+ for (const project of projects) {
97
+ const shortPath = project.path.length > 50
98
+ ? '...' + project.path.slice(-47)
99
+ : project.path;
100
+
101
+ console.log(`\x1b[36m📁 ${shortPath}\x1b[0m`);
102
+
103
+ for (const session of project.sessions) {
104
+ const sizeStr = this.formatSize(session.size);
105
+ const dateStr = this.formatDate(session.modified);
106
+ const typeIcon = session.isAgent ? '🤖' : '💬';
107
+ const shortId = session.id.length > 20 ? session.id.slice(0, 20) + '...' : session.id;
108
+
109
+ console.log(` ${typeIcon} ${shortId} ${sizeStr} ${dateStr}`);
110
+ totalSessions++;
111
+ totalSize += session.size;
112
+ }
113
+ console.log('');
114
+ }
115
+
116
+ console.log(`Total: ${totalSessions} sessions, ${this.formatSize(totalSize)}`);
117
+ console.log('');
118
+
119
+ return projects;
120
+ }
121
+
122
+ /**
123
+ * Backup all sessions
124
+ */
125
+ backup(options = {}) {
126
+ const projects = this.getProjects();
127
+
128
+ if (projects.length === 0) {
129
+ this.logger.warning('No sessions to backup');
130
+ return null;
131
+ }
132
+
133
+ // Create backup directory with timestamp
134
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
135
+ const backupPath = path.join(this.backupDir, timestamp);
136
+ const sessionsBackupPath = path.join(backupPath, 'sessions');
137
+
138
+ this.platform.ensureDir(sessionsBackupPath);
139
+
140
+ console.log('');
141
+ this.logger.step('Backing up Claude Code sessions...');
142
+ console.log('');
143
+
144
+ let backedUp = 0;
145
+ let totalSize = 0;
146
+ const manifest = {
147
+ timestamp: new Date().toISOString(),
148
+ version: require('../../package.json').version,
149
+ projects: []
150
+ };
151
+
152
+ for (const project of projects) {
153
+ const projectBackupPath = path.join(sessionsBackupPath, project.id);
154
+ this.platform.ensureDir(projectBackupPath);
155
+
156
+ const projectManifest = {
157
+ id: project.id,
158
+ path: project.path,
159
+ sessions: []
160
+ };
161
+
162
+ for (const session of project.sessions) {
163
+ try {
164
+ const destPath = path.join(projectBackupPath, session.file);
165
+ fs.copyFileSync(session.path, destPath);
166
+
167
+ projectManifest.sessions.push({
168
+ id: session.id,
169
+ file: session.file,
170
+ size: session.size,
171
+ modified: session.modified.toISOString(),
172
+ type: session.type
173
+ });
174
+
175
+ backedUp++;
176
+ totalSize += session.size;
177
+ } catch (error) {
178
+ this.logger.warning(`Failed to backup ${session.id}: ${error.message}`);
179
+ }
180
+ }
181
+
182
+ manifest.projects.push(projectManifest);
183
+ }
184
+
185
+ // Also backup history.jsonl if exists
186
+ const historyPath = path.join(this.claudeDir, 'history.jsonl');
187
+ if (fs.existsSync(historyPath)) {
188
+ try {
189
+ fs.copyFileSync(historyPath, path.join(backupPath, 'history.jsonl'));
190
+ const historyStats = fs.statSync(historyPath);
191
+ manifest.history = {
192
+ size: historyStats.size,
193
+ modified: historyStats.mtime.toISOString()
194
+ };
195
+ totalSize += historyStats.size;
196
+ this.logger.success('Command history backed up');
197
+ } catch (error) {
198
+ this.logger.warning(`Failed to backup history: ${error.message}`);
199
+ }
200
+ }
201
+
202
+ // Save manifest
203
+ fs.writeFileSync(
204
+ path.join(backupPath, 'manifest.json'),
205
+ JSON.stringify(manifest, null, 2)
206
+ );
207
+
208
+ console.log('');
209
+ this.logger.success(`Backup complete: ${backedUp} sessions, ${this.formatSize(totalSize)}`);
210
+ this.logger.info(`Location: ${backupPath}`);
211
+ console.log('');
212
+
213
+ return {
214
+ path: backupPath,
215
+ sessions: backedUp,
216
+ size: totalSize,
217
+ timestamp: timestamp
218
+ };
219
+ }
220
+
221
+ /**
222
+ * List available backups
223
+ */
224
+ listBackups() {
225
+ if (!fs.existsSync(this.backupDir)) {
226
+ this.logger.warning('No backups found');
227
+ return [];
228
+ }
229
+
230
+ const backups = fs.readdirSync(this.backupDir)
231
+ .filter(name => {
232
+ const backupPath = path.join(this.backupDir, name);
233
+ return fs.statSync(backupPath).isDirectory();
234
+ })
235
+ .map(name => {
236
+ const backupPath = path.join(this.backupDir, name);
237
+ const manifestPath = path.join(backupPath, 'manifest.json');
238
+
239
+ let manifest = null;
240
+ if (fs.existsSync(manifestPath)) {
241
+ try {
242
+ manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
243
+ } catch (e) {
244
+ // Ignore parse errors
245
+ }
246
+ }
247
+
248
+ const totalSessions = manifest?.projects?.reduce(
249
+ (sum, p) => sum + (p.sessions?.length || 0), 0
250
+ ) || 0;
251
+
252
+ return {
253
+ id: name,
254
+ path: backupPath,
255
+ timestamp: manifest?.timestamp || name,
256
+ sessions: totalSessions,
257
+ projects: manifest?.projects?.length || 0
258
+ };
259
+ })
260
+ .sort((a, b) => b.id.localeCompare(a.id));
261
+
262
+ if (backups.length === 0) {
263
+ this.logger.warning('No backups found');
264
+ return [];
265
+ }
266
+
267
+ console.log('');
268
+ console.log('\x1b[35m═══════════════════════════════════════════════════════════════════\x1b[0m');
269
+ console.log('\x1b[35m Available Backups\x1b[0m');
270
+ console.log('\x1b[35m═══════════════════════════════════════════════════════════════════\x1b[0m');
271
+ console.log('');
272
+
273
+ for (const backup of backups) {
274
+ const dateStr = backup.id.replace('T', ' ').replace(/-/g, ':').slice(0, 16).replace(/:/g, '-').slice(0,10) + ' ' + backup.id.slice(11, 16).replace(/-/g, ':');
275
+ console.log(` 📦 ${backup.id}`);
276
+ console.log(` ${backup.projects} projects, ${backup.sessions} sessions`);
277
+ console.log('');
278
+ }
279
+
280
+ return backups;
281
+ }
282
+
283
+ /**
284
+ * Restore from backup
285
+ */
286
+ restore(backupId) {
287
+ const backupPath = path.join(this.backupDir, backupId);
288
+
289
+ if (!fs.existsSync(backupPath)) {
290
+ this.logger.error(`Backup not found: ${backupId}`);
291
+ return false;
292
+ }
293
+
294
+ const manifestPath = path.join(backupPath, 'manifest.json');
295
+ if (!fs.existsSync(manifestPath)) {
296
+ this.logger.error('Invalid backup: manifest.json not found');
297
+ return false;
298
+ }
299
+
300
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
301
+
302
+ console.log('');
303
+ this.logger.step(`Restoring from backup: ${backupId}`);
304
+ console.log('');
305
+
306
+ let restored = 0;
307
+
308
+ for (const project of manifest.projects) {
309
+ const projectBackupPath = path.join(backupPath, 'sessions', project.id);
310
+ const projectDestPath = path.join(this.projectsDir, project.id);
311
+
312
+ if (!fs.existsSync(projectBackupPath)) continue;
313
+
314
+ this.platform.ensureDir(projectDestPath);
315
+
316
+ for (const session of project.sessions) {
317
+ const srcPath = path.join(projectBackupPath, session.file);
318
+ const destPath = path.join(projectDestPath, session.file);
319
+
320
+ if (fs.existsSync(srcPath)) {
321
+ try {
322
+ // Don't overwrite existing sessions, create with .restored suffix
323
+ if (fs.existsSync(destPath)) {
324
+ const restoredPath = destPath.replace('.jsonl', '.restored.jsonl');
325
+ fs.copyFileSync(srcPath, restoredPath);
326
+ } else {
327
+ fs.copyFileSync(srcPath, destPath);
328
+ }
329
+ restored++;
330
+ } catch (error) {
331
+ this.logger.warning(`Failed to restore ${session.id}: ${error.message}`);
332
+ }
333
+ }
334
+ }
335
+ }
336
+
337
+ // Restore history if exists
338
+ const historyBackupPath = path.join(backupPath, 'history.jsonl');
339
+ if (fs.existsSync(historyBackupPath)) {
340
+ const historyDestPath = path.join(this.claudeDir, 'history.jsonl');
341
+ try {
342
+ if (fs.existsSync(historyDestPath)) {
343
+ fs.copyFileSync(historyBackupPath, historyDestPath + '.restored');
344
+ this.logger.info('History restored as history.jsonl.restored');
345
+ } else {
346
+ fs.copyFileSync(historyBackupPath, historyDestPath);
347
+ this.logger.success('Command history restored');
348
+ }
349
+ } catch (error) {
350
+ this.logger.warning(`Failed to restore history: ${error.message}`);
351
+ }
352
+ }
353
+
354
+ console.log('');
355
+ this.logger.success(`Restored ${restored} sessions`);
356
+ this.logger.info('Restart Claude Code to see restored sessions');
357
+ console.log('');
358
+
359
+ return true;
360
+ }
361
+
362
+ /**
363
+ * Export a specific session to a file
364
+ */
365
+ export(sessionId, outputPath) {
366
+ const projects = this.getProjects();
367
+
368
+ let foundSession = null;
369
+ let foundProject = null;
370
+
371
+ for (const project of projects) {
372
+ for (const session of project.sessions) {
373
+ if (session.id === sessionId || session.id.startsWith(sessionId)) {
374
+ foundSession = session;
375
+ foundProject = project;
376
+ break;
377
+ }
378
+ }
379
+ if (foundSession) break;
380
+ }
381
+
382
+ if (!foundSession) {
383
+ this.logger.error(`Session not found: ${sessionId}`);
384
+ return false;
385
+ }
386
+
387
+ const destPath = outputPath || `${foundSession.id}.jsonl`;
388
+
389
+ try {
390
+ fs.copyFileSync(foundSession.path, destPath);
391
+ this.logger.success(`Session exported to: ${destPath}`);
392
+ this.logger.info(`Size: ${this.formatSize(foundSession.size)}`);
393
+ return true;
394
+ } catch (error) {
395
+ this.logger.error(`Failed to export: ${error.message}`);
396
+ return false;
397
+ }
398
+ }
399
+
400
+ /**
401
+ * Format file size
402
+ */
403
+ formatSize(bytes) {
404
+ if (bytes < 1024) return bytes + ' B';
405
+ if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
406
+ return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
407
+ }
408
+
409
+ /**
410
+ * Format date
411
+ */
412
+ formatDate(date) {
413
+ const now = new Date();
414
+ const diff = now - date;
415
+
416
+ if (diff < 60000) return 'just now';
417
+ if (diff < 3600000) return Math.floor(diff / 60000) + 'm ago';
418
+ if (diff < 86400000) return Math.floor(diff / 3600000) + 'h ago';
419
+ if (diff < 604800000) return Math.floor(diff / 86400000) + 'd ago';
420
+
421
+ return date.toISOString().slice(0, 10);
422
+ }
423
+ }
424
+
425
+ module.exports = { SessionManager };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zuppaclaude",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Claude Code power-up installer - SuperClaude + Spec Kit + Claude-Z + Claude HUD",
5
5
  "main": "lib/index.js",
6
6
  "bin": {