claude-code-templates 1.24.17 → 1.25.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.
- package/bin/create-claude-config.js +1 -0
- package/package.json +2 -1
- package/src/analytics-web/chats_mobile.html +631 -67
- package/src/chats-mobile.js +38 -1
- package/src/index.js +44 -1
- package/src/session-sharing.js +396 -0
package/src/chats-mobile.js
CHANGED
|
@@ -10,6 +10,7 @@ const StateCalculator = require('./analytics/core/StateCalculator');
|
|
|
10
10
|
const FileWatcher = require('./analytics/core/FileWatcher');
|
|
11
11
|
const DataCache = require('./analytics/data/DataCache');
|
|
12
12
|
const WebSocketServer = require('./analytics/notifications/WebSocketServer');
|
|
13
|
+
const SessionSharing = require('./session-sharing');
|
|
13
14
|
|
|
14
15
|
class ChatsMobile {
|
|
15
16
|
constructor(options = {}) {
|
|
@@ -28,7 +29,10 @@ class ChatsMobile {
|
|
|
28
29
|
const homeDir = os.homedir();
|
|
29
30
|
const claudeDir = path.join(homeDir, '.claude');
|
|
30
31
|
this.conversationAnalyzer = new ConversationAnalyzer(claudeDir, this.dataCache);
|
|
31
|
-
|
|
32
|
+
|
|
33
|
+
// Initialize SessionSharing for export/import functionality
|
|
34
|
+
this.sessionSharing = new SessionSharing(this.conversationAnalyzer);
|
|
35
|
+
|
|
32
36
|
this.data = {
|
|
33
37
|
conversations: [],
|
|
34
38
|
conversationStates: {},
|
|
@@ -474,6 +478,39 @@ class ChatsMobile {
|
|
|
474
478
|
}
|
|
475
479
|
});
|
|
476
480
|
|
|
481
|
+
// API to share a conversation session
|
|
482
|
+
this.app.post('/api/conversations/:id/share', async (req, res) => {
|
|
483
|
+
try {
|
|
484
|
+
const conversationId = req.params.id;
|
|
485
|
+
const conversation = this.data.conversations.find(conv => conv.id === conversationId);
|
|
486
|
+
|
|
487
|
+
if (!conversation) {
|
|
488
|
+
return res.status(404).json({ error: 'Conversation not found' });
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
console.log(chalk.cyan(`📤 Sharing conversation ${conversationId}...`));
|
|
492
|
+
|
|
493
|
+
// Share the session using SessionSharing module
|
|
494
|
+
const shareResult = await this.sessionSharing.shareSession(conversationId, conversation);
|
|
495
|
+
|
|
496
|
+
res.json({
|
|
497
|
+
success: true,
|
|
498
|
+
conversationId: conversationId,
|
|
499
|
+
uploadUrl: shareResult.uploadUrl,
|
|
500
|
+
shareCommand: shareResult.shareCommand,
|
|
501
|
+
expiresIn: shareResult.expiresIn,
|
|
502
|
+
qrCode: shareResult.qrCode,
|
|
503
|
+
timestamp: new Date().toISOString()
|
|
504
|
+
});
|
|
505
|
+
} catch (error) {
|
|
506
|
+
console.error('Error sharing conversation:', error);
|
|
507
|
+
res.status(500).json({
|
|
508
|
+
error: 'Failed to share session',
|
|
509
|
+
message: error.message
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
|
|
477
514
|
// Serve the mobile chats page as default
|
|
478
515
|
this.app.get('/', (req, res) => {
|
|
479
516
|
res.sendFile(path.join(__dirname, 'analytics-web', 'chats_mobile.html'));
|
package/src/index.js
CHANGED
|
@@ -18,6 +18,8 @@ const { runHealthCheck } = require('./health-check');
|
|
|
18
18
|
const { runPluginDashboard } = require('./plugin-dashboard');
|
|
19
19
|
const { trackingService } = require('./tracking-service');
|
|
20
20
|
const { createGlobalAgent, listGlobalAgents, removeGlobalAgent, updateGlobalAgent } = require('./sdk/global-agent-manager');
|
|
21
|
+
const SessionSharing = require('./session-sharing');
|
|
22
|
+
const ConversationAnalyzer = require('./analytics/core/ConversationAnalyzer');
|
|
21
23
|
|
|
22
24
|
async function showMainMenu() {
|
|
23
25
|
console.log('');
|
|
@@ -222,7 +224,48 @@ async function createClaudeConfig(options = {}) {
|
|
|
222
224
|
await startChatsMobile(options);
|
|
223
225
|
return;
|
|
224
226
|
}
|
|
225
|
-
|
|
227
|
+
|
|
228
|
+
// Handle session clone (download and import shared session)
|
|
229
|
+
if (options.cloneSession) {
|
|
230
|
+
console.log(chalk.blue('📥 Cloning shared Claude Code session...'));
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
const os = require('os');
|
|
234
|
+
const homeDir = os.homedir();
|
|
235
|
+
const claudeDir = path.join(homeDir, '.claude');
|
|
236
|
+
|
|
237
|
+
// Initialize ConversationAnalyzer and SessionSharing
|
|
238
|
+
const conversationAnalyzer = new ConversationAnalyzer(claudeDir);
|
|
239
|
+
const sessionSharing = new SessionSharing(conversationAnalyzer);
|
|
240
|
+
|
|
241
|
+
// Clone the session (cloneSession method handles all console output)
|
|
242
|
+
const result = await sessionSharing.cloneSession(options.cloneSession, {
|
|
243
|
+
projectPath: options.directory || process.cwd()
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Track session clone
|
|
247
|
+
trackingService.trackAnalyticsDashboard({
|
|
248
|
+
page: 'session-clone',
|
|
249
|
+
source: 'command_line',
|
|
250
|
+
success: true
|
|
251
|
+
});
|
|
252
|
+
} catch (error) {
|
|
253
|
+
console.error(chalk.red('❌ Failed to clone session:'), error.message);
|
|
254
|
+
|
|
255
|
+
// Track failed clone
|
|
256
|
+
trackingService.trackAnalyticsDashboard({
|
|
257
|
+
page: 'session-clone',
|
|
258
|
+
source: 'command_line',
|
|
259
|
+
success: false,
|
|
260
|
+
error: error.message
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
process.exit(1);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
226
269
|
// Handle health check
|
|
227
270
|
let shouldRunSetup = false;
|
|
228
271
|
if (options.healthCheck || options.health || options.check || options.verify) {
|
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const { exec } = require('child_process');
|
|
6
|
+
const { promisify } = require('util');
|
|
7
|
+
const execAsync = promisify(exec);
|
|
8
|
+
const QRCode = require('qrcode');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* SessionSharing - Handles exporting and importing Claude Code sessions
|
|
12
|
+
* Uses x0.at - a simple, reliable file hosting service
|
|
13
|
+
*/
|
|
14
|
+
class SessionSharing {
|
|
15
|
+
constructor(conversationAnalyzer) {
|
|
16
|
+
this.conversationAnalyzer = conversationAnalyzer;
|
|
17
|
+
this.uploadService = 'x0.at';
|
|
18
|
+
this.uploadUrl = 'https://x0.at';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Export and share a conversation session
|
|
23
|
+
* @param {string} conversationId - Conversation ID to share
|
|
24
|
+
* @param {Object} conversationData - Full conversation data object
|
|
25
|
+
* @param {Object} options - Share options (messageLimit, etc.)
|
|
26
|
+
* @returns {Promise<Object>} Share result with URL, command, and QR code
|
|
27
|
+
*/
|
|
28
|
+
async shareSession(conversationId, conversationData, options = {}) {
|
|
29
|
+
console.log(chalk.blue(`📤 Preparing session ${conversationId} for sharing...`));
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
// 1. Export session to structured JSON format
|
|
33
|
+
const sessionExport = await this.exportSessionData(conversationId, conversationData, options);
|
|
34
|
+
|
|
35
|
+
// 2. Upload to x0.at
|
|
36
|
+
const uploadUrl = await this.uploadToX0(sessionExport, conversationId);
|
|
37
|
+
|
|
38
|
+
// 3. Generate share command
|
|
39
|
+
const shareCommand = `npx claude-code-templates@latest --clone-session "${uploadUrl}"`;
|
|
40
|
+
|
|
41
|
+
// 4. Generate QR code
|
|
42
|
+
const qrCode = await this.generateQRCode(shareCommand);
|
|
43
|
+
|
|
44
|
+
console.log(chalk.green(`✅ Session shared successfully!`));
|
|
45
|
+
console.log(chalk.cyan(`📋 Share command: ${shareCommand}`));
|
|
46
|
+
console.log(chalk.gray(`🔗 Direct URL: ${uploadUrl}`));
|
|
47
|
+
console.log(chalk.yellow(`⚠️ Files kept for 3-100 days (based on size)`));
|
|
48
|
+
console.log(chalk.gray(`🔓 Note: Files are not encrypted by default`));
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
success: true,
|
|
52
|
+
uploadUrl,
|
|
53
|
+
shareCommand,
|
|
54
|
+
expiresIn: 'After first download',
|
|
55
|
+
conversationId,
|
|
56
|
+
qrCode,
|
|
57
|
+
messageCount: sessionExport.conversation.messageCount,
|
|
58
|
+
totalMessageCount: sessionExport.conversation.totalMessageCount,
|
|
59
|
+
wasLimited: sessionExport.conversation.wasLimited
|
|
60
|
+
};
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error(chalk.red('❌ Failed to share session:'), error.message);
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Export session data to standardized format
|
|
69
|
+
* @param {string} conversationId - Conversation ID
|
|
70
|
+
* @param {Object} conversationData - Conversation metadata
|
|
71
|
+
* @param {Object} options - Export options
|
|
72
|
+
* @returns {Promise<Object>} Exported session object
|
|
73
|
+
*/
|
|
74
|
+
async exportSessionData(conversationId, conversationData, options = {}) {
|
|
75
|
+
// Get all messages from the conversation
|
|
76
|
+
const allMessages = await this.conversationAnalyzer.getParsedConversation(conversationData.filePath);
|
|
77
|
+
|
|
78
|
+
// Limit messages to avoid large file sizes (default: last 100 messages)
|
|
79
|
+
const messageLimit = options.messageLimit || 100;
|
|
80
|
+
const messages = allMessages.slice(-messageLimit);
|
|
81
|
+
|
|
82
|
+
// Convert parsed messages back to JSONL format (original Claude Code format)
|
|
83
|
+
const jsonlMessages = messages.map(msg => {
|
|
84
|
+
// Reconstruct original JSONL entry format
|
|
85
|
+
const entry = {
|
|
86
|
+
uuid: msg.uuid || msg.id,
|
|
87
|
+
type: msg.role === 'assistant' ? 'assistant' : 'user',
|
|
88
|
+
timestamp: msg.timestamp.toISOString(),
|
|
89
|
+
message: {
|
|
90
|
+
id: msg.id,
|
|
91
|
+
role: msg.role,
|
|
92
|
+
content: msg.content
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// Add model info for assistant messages
|
|
97
|
+
if (msg.model) {
|
|
98
|
+
entry.message.model = msg.model;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Add usage info
|
|
102
|
+
if (msg.usage) {
|
|
103
|
+
entry.message.usage = msg.usage;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Add compact summary flag if present
|
|
107
|
+
if (msg.isCompactSummary) {
|
|
108
|
+
entry.isCompactSummary = true;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return entry;
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Create export package
|
|
115
|
+
const exportData = {
|
|
116
|
+
version: '1.0.0',
|
|
117
|
+
exported_at: new Date().toISOString(),
|
|
118
|
+
conversation: {
|
|
119
|
+
id: conversationId,
|
|
120
|
+
project: conversationData.project || 'Unknown',
|
|
121
|
+
created: conversationData.created,
|
|
122
|
+
lastModified: conversationData.lastModified,
|
|
123
|
+
messageCount: messages.length,
|
|
124
|
+
totalMessageCount: allMessages.length,
|
|
125
|
+
wasLimited: allMessages.length > messageLimit,
|
|
126
|
+
tokens: conversationData.tokens,
|
|
127
|
+
model: conversationData.modelInfo?.primaryModel || 'Unknown'
|
|
128
|
+
},
|
|
129
|
+
messages: jsonlMessages,
|
|
130
|
+
metadata: {
|
|
131
|
+
exportTool: 'claude-code-templates',
|
|
132
|
+
exportVersion: require('../package.json').version || '1.0.0',
|
|
133
|
+
messageLimit: messageLimit,
|
|
134
|
+
description: 'Claude Code session export - can be cloned with: npx claude-code-templates@latest --clone-session <url>'
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// Log information about exported messages
|
|
139
|
+
if (allMessages.length > messageLimit) {
|
|
140
|
+
console.log(chalk.yellow(`⚠️ Session has ${allMessages.length} messages, exporting last ${messageLimit} messages`));
|
|
141
|
+
} else {
|
|
142
|
+
console.log(chalk.gray(`📊 Exporting ${messages.length} messages`));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return exportData;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Upload session to x0.at
|
|
150
|
+
* @param {Object} sessionData - Session export data
|
|
151
|
+
* @param {string} conversationId - Conversation ID for filename
|
|
152
|
+
* @returns {Promise<string>} Upload URL
|
|
153
|
+
*/
|
|
154
|
+
async uploadToX0(sessionData, conversationId) {
|
|
155
|
+
const tmpDir = path.join(os.tmpdir(), 'claude-code-sessions');
|
|
156
|
+
await fs.ensureDir(tmpDir);
|
|
157
|
+
|
|
158
|
+
const tmpFile = path.join(tmpDir, `session-${conversationId}.json`);
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
// Write session data to temp file
|
|
162
|
+
await fs.writeFile(tmpFile, JSON.stringify(sessionData, null, 2), 'utf8');
|
|
163
|
+
|
|
164
|
+
console.log(chalk.gray(`📁 Created temp file: ${tmpFile}`));
|
|
165
|
+
console.log(chalk.gray(`📤 Uploading to x0.at...`));
|
|
166
|
+
|
|
167
|
+
// Upload to x0.at using curl with form data
|
|
168
|
+
// x0.at API: curl -F'file=@yourfile.png' https://x0.at
|
|
169
|
+
// Response: Direct URL in plain text
|
|
170
|
+
const { stdout, stderr } = await execAsync(
|
|
171
|
+
`curl -s -F "file=@${tmpFile}" ${this.uploadUrl}`,
|
|
172
|
+
{ maxBuffer: 10 * 1024 * 1024 } // 10MB buffer
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
// x0.at returns URL directly in plain text
|
|
176
|
+
const uploadUrl = stdout.trim();
|
|
177
|
+
|
|
178
|
+
// Validate response
|
|
179
|
+
if (!uploadUrl || !uploadUrl.startsWith('http')) {
|
|
180
|
+
throw new Error(`Invalid response from x0.at: ${uploadUrl || stderr}`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
console.log(chalk.green(`✅ Uploaded to x0.at successfully`));
|
|
184
|
+
console.log(chalk.yellow(`⚠️ Files kept for 3-100 days (based on size)`));
|
|
185
|
+
console.log(chalk.gray(`🔓 Note: Files are not encrypted by default`));
|
|
186
|
+
|
|
187
|
+
// Clean up temp file
|
|
188
|
+
await fs.remove(tmpFile);
|
|
189
|
+
|
|
190
|
+
return uploadUrl;
|
|
191
|
+
} catch (error) {
|
|
192
|
+
// Clean up temp file on error
|
|
193
|
+
await fs.remove(tmpFile).catch(() => {});
|
|
194
|
+
throw error;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Clone a session from a shared URL
|
|
200
|
+
* Downloads the session and places it in the correct Claude Code location
|
|
201
|
+
* @param {string} url - URL to download session from
|
|
202
|
+
* @param {Object} options - Clone options
|
|
203
|
+
* @returns {Promise<Object>} Result with session path
|
|
204
|
+
*/
|
|
205
|
+
async cloneSession(url, options = {}) {
|
|
206
|
+
console.log(chalk.blue(`📥 Downloading session from ${url}...`));
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
// 1. Download session data
|
|
210
|
+
const sessionData = await this.downloadSession(url);
|
|
211
|
+
|
|
212
|
+
// 2. Validate session data
|
|
213
|
+
this.validateSessionData(sessionData);
|
|
214
|
+
|
|
215
|
+
console.log(chalk.green(`✅ Session downloaded successfully`));
|
|
216
|
+
console.log(chalk.gray(`📊 Project: ${sessionData.conversation.project}`));
|
|
217
|
+
console.log(chalk.gray(`💬 Messages: ${sessionData.conversation.messageCount}`));
|
|
218
|
+
console.log(chalk.gray(`🤖 Model: ${sessionData.conversation.model}`));
|
|
219
|
+
|
|
220
|
+
// 3. Install session in Claude Code directory
|
|
221
|
+
const installResult = await this.installSession(sessionData, options);
|
|
222
|
+
|
|
223
|
+
console.log(chalk.green(`\n✅ Session installed successfully!`));
|
|
224
|
+
console.log(chalk.cyan(`📂 Location: ${installResult.sessionPath}`));
|
|
225
|
+
|
|
226
|
+
// Show resume command
|
|
227
|
+
const resumeCommand = `claude --resume ${installResult.projectPath} ${installResult.conversationId}`;
|
|
228
|
+
console.log(chalk.yellow(`\n💡 To continue this conversation, run:`));
|
|
229
|
+
console.log(chalk.white(`\n ${resumeCommand}\n`));
|
|
230
|
+
console.log(chalk.gray(` Or open Claude Code to see it in your sessions list`));
|
|
231
|
+
|
|
232
|
+
return installResult;
|
|
233
|
+
} catch (error) {
|
|
234
|
+
console.error(chalk.red('❌ Failed to clone session:'), error.message);
|
|
235
|
+
throw error;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Download session data from URL
|
|
241
|
+
* @param {string} url - URL to download from
|
|
242
|
+
* @returns {Promise<Object>} Session data
|
|
243
|
+
*/
|
|
244
|
+
async downloadSession(url) {
|
|
245
|
+
try {
|
|
246
|
+
// Use curl to download (works with x0.at and other services)
|
|
247
|
+
const { stdout, stderr } = await execAsync(`curl -L "${url}"`, {
|
|
248
|
+
maxBuffer: 50 * 1024 * 1024 // 50MB buffer for large sessions
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
if (stderr && !stdout) {
|
|
252
|
+
throw new Error(`Download failed: ${stderr}`);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Parse JSON response
|
|
256
|
+
const sessionData = JSON.parse(stdout);
|
|
257
|
+
return sessionData;
|
|
258
|
+
} catch (error) {
|
|
259
|
+
if (error.message.includes('Unexpected token')) {
|
|
260
|
+
throw new Error('Invalid session file - corrupted or not a Claude Code session');
|
|
261
|
+
}
|
|
262
|
+
throw error;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Validate session data structure
|
|
268
|
+
* @param {Object} sessionData - Session data to validate
|
|
269
|
+
* @throws {Error} If validation fails
|
|
270
|
+
*/
|
|
271
|
+
validateSessionData(sessionData) {
|
|
272
|
+
if (!sessionData.version) {
|
|
273
|
+
throw new Error('Invalid session file - missing version');
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (!sessionData.conversation || !sessionData.conversation.id) {
|
|
277
|
+
throw new Error('Invalid session file - missing conversation data');
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (!sessionData.messages || !Array.isArray(sessionData.messages)) {
|
|
281
|
+
throw new Error('Invalid session file - missing or invalid messages');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (sessionData.messages.length === 0) {
|
|
285
|
+
throw new Error('Invalid session file - no messages found');
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Install session in Claude Code directory structure
|
|
291
|
+
* @param {Object} sessionData - Session data to install
|
|
292
|
+
* @param {Object} options - Installation options
|
|
293
|
+
* @returns {Promise<Object>} Installation result
|
|
294
|
+
*/
|
|
295
|
+
async installSession(sessionData, options = {}) {
|
|
296
|
+
const homeDir = os.homedir();
|
|
297
|
+
const claudeDir = path.join(homeDir, '.claude');
|
|
298
|
+
|
|
299
|
+
// Determine project directory
|
|
300
|
+
const projectName = sessionData.conversation.project || 'shared-session';
|
|
301
|
+
const projectDirName = this.sanitizeProjectName(projectName);
|
|
302
|
+
|
|
303
|
+
// Create project directory structure
|
|
304
|
+
// Format: ~/.claude/projects/-path-to-project/
|
|
305
|
+
const projectDir = path.join(claudeDir, 'projects', projectDirName);
|
|
306
|
+
await fs.ensureDir(projectDir);
|
|
307
|
+
|
|
308
|
+
// Generate conversation filename with original ID
|
|
309
|
+
const conversationId = sessionData.conversation.id;
|
|
310
|
+
const conversationFile = path.join(projectDir, `${conversationId}.jsonl`);
|
|
311
|
+
|
|
312
|
+
// Convert messages back to JSONL format (one JSON object per line)
|
|
313
|
+
const jsonlContent = sessionData.messages
|
|
314
|
+
.map(msg => JSON.stringify(msg))
|
|
315
|
+
.join('\n');
|
|
316
|
+
|
|
317
|
+
// Write conversation file
|
|
318
|
+
await fs.writeFile(conversationFile, jsonlContent, 'utf8');
|
|
319
|
+
|
|
320
|
+
console.log(chalk.gray(`📝 Created conversation file: ${conversationFile}`));
|
|
321
|
+
|
|
322
|
+
// Create or update settings.json
|
|
323
|
+
const settingsFile = path.join(projectDir, 'settings.json');
|
|
324
|
+
const settings = {
|
|
325
|
+
projectName: sessionData.conversation.project,
|
|
326
|
+
projectPath: options.projectPath || process.cwd(),
|
|
327
|
+
sharedSession: true,
|
|
328
|
+
originalExport: {
|
|
329
|
+
exportedAt: sessionData.exported_at,
|
|
330
|
+
exportTool: sessionData.metadata?.exportTool,
|
|
331
|
+
exportVersion: sessionData.metadata?.exportVersion
|
|
332
|
+
},
|
|
333
|
+
importedAt: new Date().toISOString()
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
await fs.writeFile(settingsFile, JSON.stringify(settings, null, 2), 'utf8');
|
|
337
|
+
|
|
338
|
+
console.log(chalk.gray(`⚙️ Created settings file: ${settingsFile}`));
|
|
339
|
+
|
|
340
|
+
return {
|
|
341
|
+
success: true,
|
|
342
|
+
sessionPath: conversationFile,
|
|
343
|
+
projectDir,
|
|
344
|
+
projectPath: settings.projectPath,
|
|
345
|
+
conversationId,
|
|
346
|
+
messageCount: sessionData.messages.length
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Generate QR code for share command
|
|
352
|
+
* @param {string} command - Command to encode in QR
|
|
353
|
+
* @returns {Promise<Object>} QR code data (Data URL for web display)
|
|
354
|
+
*/
|
|
355
|
+
async generateQRCode(command) {
|
|
356
|
+
try {
|
|
357
|
+
// Generate QR code as Data URL (for web display)
|
|
358
|
+
const qrDataUrl = await QRCode.toDataURL(command, {
|
|
359
|
+
errorCorrectionLevel: 'M',
|
|
360
|
+
type: 'image/png',
|
|
361
|
+
width: 300,
|
|
362
|
+
margin: 2,
|
|
363
|
+
color: {
|
|
364
|
+
dark: '#000000',
|
|
365
|
+
light: '#FFFFFF'
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
return {
|
|
370
|
+
dataUrl: qrDataUrl,
|
|
371
|
+
command: command
|
|
372
|
+
};
|
|
373
|
+
} catch (error) {
|
|
374
|
+
console.warn(chalk.yellow('⚠️ Could not generate QR code:'), error.message);
|
|
375
|
+
return {
|
|
376
|
+
dataUrl: null,
|
|
377
|
+
command: command
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Sanitize project name for directory usage
|
|
384
|
+
* @param {string} projectName - Original project name
|
|
385
|
+
* @returns {string} Sanitized name
|
|
386
|
+
*/
|
|
387
|
+
sanitizeProjectName(projectName) {
|
|
388
|
+
// Replace spaces and special chars with hyphens
|
|
389
|
+
return projectName
|
|
390
|
+
.replace(/[^a-zA-Z0-9-_]/g, '-')
|
|
391
|
+
.replace(/-+/g, '-')
|
|
392
|
+
.toLowerCase();
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
module.exports = SessionSharing;
|