zuppaclaude 1.0.0 → 1.0.2

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/README.md CHANGED
@@ -197,6 +197,27 @@ npx zuppaclaude settings reset # 🔄 Fabrika ayarları
197
197
  | 🔐 API key koruması | Base64 encoded saklama |
198
198
  | 🛡️ Uninstall koruması | Kaldırırken ayarları koruma opsiyonu |
199
199
 
200
+ ### 💾 Session Yedekleme
201
+
202
+ Claude Code session'larınızı yedekleyin ve geri yükleyin. Context kaybı, format veya compacting durumlarında kullanışlı.
203
+
204
+ ```bash
205
+ npx zuppaclaude session list # 📋 Tüm session'ları listele
206
+ npx zuppaclaude session backup # 💾 Tüm session'ları yedekle
207
+ npx zuppaclaude session backups # 📦 Mevcut yedekleri listele
208
+ npx zuppaclaude session restore <id> # ♻️ Yedekten geri yükle
209
+ npx zuppaclaude session export <id> # 📤 Belirli session'ı export et
210
+ ```
211
+
212
+ **📁 Yedek konumu:** `~/.config/zuppaclaude/backups/`
213
+
214
+ **✨ Özellikler:**
215
+ | Özellik | Açıklama |
216
+ |---------|----------|
217
+ | 🔒 Güvenli restore | Mevcut session'lar üzerine yazılmaz |
218
+ | 📜 History desteği | Command history de yedeklenir |
219
+ | 📋 Manifest | Her yedekte metadata saklanır |
220
+
200
221
  ### 🗑️ Kaldırma
201
222
 
202
223
  **📦 NPM ile:**
@@ -423,6 +444,27 @@ npx zuppaclaude settings reset # 🔄 Reset to defaults
423
444
  | 🔐 API key protection | Base64 encoded storage |
424
445
  | 🛡️ Uninstall protection | Option to preserve settings when uninstalling |
425
446
 
447
+ ### 💾 Session Backup
448
+
449
+ Backup and restore your Claude Code sessions. Useful for context loss, formatting, or conversation compacting.
450
+
451
+ ```bash
452
+ npx zuppaclaude session list # 📋 List all sessions
453
+ npx zuppaclaude session backup # 💾 Backup all sessions
454
+ npx zuppaclaude session backups # 📦 List available backups
455
+ npx zuppaclaude session restore <id> # ♻️ Restore from backup
456
+ npx zuppaclaude session export <id> # 📤 Export specific session
457
+ ```
458
+
459
+ **📁 Backup location:** `~/.config/zuppaclaude/backups/`
460
+
461
+ **✨ Features:**
462
+ | Feature | Description |
463
+ |---------|-------------|
464
+ | 🔒 Safe restore | Existing sessions are not overwritten |
465
+ | 📜 History support | Command history is also backed up |
466
+ | 📋 Manifest | Metadata saved with each backup |
467
+
426
468
  ### 🗑️ Uninstall
427
469
 
428
470
  **📦 Via NPM:**
@@ -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.2",
4
4
  "description": "Claude Code power-up installer - SuperClaude + Spec Kit + Claude-Z + Claude HUD",
5
5
  "main": "lib/index.js",
6
6
  "bin": {