claude-code-templates 1.24.16 → 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.
@@ -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: {},
@@ -173,6 +177,244 @@ class ChatsMobile {
173
177
  }
174
178
  });
175
179
 
180
+ // API to get unique working directories from conversations
181
+ this.app.get('/api/directories', (req, res) => {
182
+ try {
183
+ // Extract unique directories from conversations
184
+ const directories = new Set();
185
+
186
+ this.data.conversations.forEach(conv => {
187
+ if (conv.project && conv.project.trim()) {
188
+ directories.add(conv.project);
189
+ }
190
+ });
191
+
192
+ // Convert to array and sort alphabetically
193
+ const sortedDirectories = Array.from(directories).sort((a, b) =>
194
+ a.toLowerCase().localeCompare(b.toLowerCase())
195
+ );
196
+
197
+ res.json({
198
+ directories: sortedDirectories,
199
+ count: sortedDirectories.length,
200
+ timestamp: new Date().toISOString()
201
+ });
202
+ } catch (error) {
203
+ console.error('Error getting directories:', error);
204
+ res.status(500).json({ error: 'Internal server error' });
205
+ }
206
+ });
207
+
208
+ // API to search conversations with advanced filters
209
+ this.app.post('/api/search', async (req, res) => {
210
+ try {
211
+ const { query, workingDirectory, dateFrom, dateTo, contentSearch } = req.body;
212
+
213
+ let results = [...this.data.conversations];
214
+
215
+ // Filter by working directory (project)
216
+ if (workingDirectory && workingDirectory.trim()) {
217
+ results = results.filter(conv => {
218
+ if (!conv.project) return false;
219
+ return conv.project.toLowerCase().includes(workingDirectory.toLowerCase());
220
+ });
221
+ }
222
+
223
+ // Filter by date range
224
+ if (dateFrom) {
225
+ const fromDate = new Date(dateFrom);
226
+ results = results.filter(conv => new Date(conv.created) >= fromDate);
227
+ }
228
+
229
+ if (dateTo) {
230
+ const toDate = new Date(dateTo);
231
+ toDate.setHours(23, 59, 59, 999); // Include entire day
232
+ results = results.filter(conv => new Date(conv.created) <= toDate);
233
+ }
234
+
235
+ // Filter by conversation metadata (filename, id)
236
+ if (query && query.trim()) {
237
+ const searchTerm = query.toLowerCase();
238
+ results = results.filter(conv =>
239
+ conv.filename.toLowerCase().includes(searchTerm) ||
240
+ conv.id.toLowerCase().includes(searchTerm) ||
241
+ (conv.project && conv.project.toLowerCase().includes(searchTerm))
242
+ );
243
+ }
244
+
245
+ // Search within message content
246
+ if (contentSearch && contentSearch.trim()) {
247
+ const contentTerm = contentSearch.toLowerCase();
248
+ const matchingConversations = [];
249
+
250
+ for (const conv of results) {
251
+ try {
252
+ const messages = await this.conversationAnalyzer.getParsedConversation(conv.filePath);
253
+
254
+ // Search in message content
255
+ const hasMatch = messages.some(msg => {
256
+ // Search in text content
257
+ if (typeof msg.content === 'string') {
258
+ return msg.content.toLowerCase().includes(contentTerm);
259
+ }
260
+
261
+ // Search in array content (tool use, text blocks)
262
+ if (Array.isArray(msg.content)) {
263
+ return msg.content.some(block => {
264
+ if (block.type === 'text' && block.text) {
265
+ return block.text.toLowerCase().includes(contentTerm);
266
+ }
267
+ if (block.type === 'tool_use' && block.name) {
268
+ return block.name.toLowerCase().includes(contentTerm);
269
+ }
270
+ return false;
271
+ });
272
+ }
273
+
274
+ return false;
275
+ });
276
+
277
+ if (hasMatch) {
278
+ matchingConversations.push(conv);
279
+ }
280
+ } catch (error) {
281
+ // Skip conversations that can't be parsed
282
+ this.log('warn', `Error searching in conversation ${conv.id}:`, error.message);
283
+ }
284
+ }
285
+
286
+ results = matchingConversations;
287
+ }
288
+
289
+ // Sort by last modified (most recent first)
290
+ results.sort((a, b) => new Date(b.lastModified) - new Date(a.lastModified));
291
+
292
+ res.json({
293
+ results: results,
294
+ count: results.length,
295
+ filters: {
296
+ query,
297
+ workingDirectory,
298
+ dateFrom,
299
+ dateTo,
300
+ contentSearch
301
+ },
302
+ timestamp: new Date().toISOString()
303
+ });
304
+ } catch (error) {
305
+ console.error('Error searching conversations:', error);
306
+ res.status(500).json({ error: 'Internal server error', message: error.message });
307
+ }
308
+ });
309
+
310
+ // API to search within a specific conversation
311
+ this.app.post('/api/conversations/:id/search', async (req, res) => {
312
+ try {
313
+ const conversationId = req.params.id;
314
+ const { query } = req.body;
315
+ const conversation = this.data.conversations.find(conv => conv.id === conversationId);
316
+
317
+ if (!conversation) {
318
+ return res.status(404).json({ error: 'Conversation not found' });
319
+ }
320
+
321
+ if (!query || !query.trim()) {
322
+ return res.json({
323
+ matches: [],
324
+ totalMatches: 0,
325
+ conversationId: conversationId
326
+ });
327
+ }
328
+
329
+ // Get all messages from the conversation
330
+ const allMessages = await this.conversationAnalyzer.getParsedConversation(conversation.filePath);
331
+ const searchTerm = query.toLowerCase();
332
+ const matches = [];
333
+
334
+ // Search through all messages
335
+ allMessages.forEach((msg, index) => {
336
+ let messageText = '';
337
+ let allText = [];
338
+
339
+ // Extract text from message content
340
+ if (typeof msg.content === 'string') {
341
+ allText.push(msg.content);
342
+ } else if (Array.isArray(msg.content)) {
343
+ msg.content.forEach(block => {
344
+ if (block.type === 'text' && block.text) {
345
+ allText.push(block.text);
346
+ }
347
+ // Also search in tool_use content
348
+ if (block.type === 'tool_use') {
349
+ if (block.name) allText.push(block.name);
350
+ if (block.input) {
351
+ allText.push(JSON.stringify(block.input));
352
+ }
353
+ }
354
+ });
355
+ }
356
+
357
+ // IMPORTANT: Also search in tool results (this is where code blocks appear!)
358
+ if (msg.toolResults && Array.isArray(msg.toolResults)) {
359
+ msg.toolResults.forEach(toolResult => {
360
+ if (toolResult.content) {
361
+ if (typeof toolResult.content === 'string') {
362
+ allText.push(toolResult.content);
363
+ } else if (Array.isArray(toolResult.content)) {
364
+ toolResult.content.forEach(block => {
365
+ if (block.type === 'text' && block.text) {
366
+ allText.push(block.text);
367
+ }
368
+ });
369
+ }
370
+ }
371
+ });
372
+ }
373
+
374
+ // Combine all text
375
+ messageText = allText.join(' ');
376
+
377
+ // Search in the combined text
378
+ if (messageText.toLowerCase().includes(searchTerm)) {
379
+ // Find all positions of the search term in this message
380
+ const lowerText = messageText.toLowerCase();
381
+ let position = 0;
382
+ let matchCount = 0;
383
+
384
+ while ((position = lowerText.indexOf(searchTerm, position)) !== -1) {
385
+ matchCount++;
386
+ position += searchTerm.length;
387
+ }
388
+
389
+ matches.push({
390
+ messageIndex: index,
391
+ messageId: msg.id,
392
+ role: msg.role,
393
+ timestamp: msg.timestamp,
394
+ preview: this.getMessagePreview(messageText, searchTerm),
395
+ matchCount: matchCount
396
+ });
397
+ }
398
+ });
399
+
400
+ console.log(`🔍 Search in conversation ${conversationId}:`, {
401
+ query: query,
402
+ messagesWithMatches: matches.length,
403
+ totalOccurrences: matches.reduce((sum, m) => sum + m.matchCount, 0)
404
+ });
405
+
406
+ res.json({
407
+ matches: matches,
408
+ totalMatches: matches.length,
409
+ conversationId: conversationId,
410
+ query: query
411
+ });
412
+ } catch (error) {
413
+ console.error('Error searching in conversation:', error);
414
+ res.status(500).json({ error: 'Internal server error', message: error.message });
415
+ }
416
+ });
417
+
176
418
  // API to get specific conversation messages (with pagination support)
177
419
  this.app.get('/api/conversations/:id/messages', async (req, res) => {
178
420
  try {
@@ -236,6 +478,39 @@ class ChatsMobile {
236
478
  }
237
479
  });
238
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
+
239
514
  // Serve the mobile chats page as default
240
515
  this.app.get('/', (req, res) => {
241
516
  res.sendFile(path.join(__dirname, 'analytics-web', 'chats_mobile.html'));
@@ -418,6 +693,26 @@ class ChatsMobile {
418
693
  console.log(chalk.gray('🔧 WebSocket server setup prepared'));
419
694
  }
420
695
 
696
+ /**
697
+ * Helper function to get message preview with context
698
+ */
699
+ getMessagePreview(text, searchTerm, contextLength = 100) {
700
+ const lowerText = text.toLowerCase();
701
+ const lowerTerm = searchTerm.toLowerCase();
702
+ const position = lowerText.indexOf(lowerTerm);
703
+
704
+ if (position === -1) return text.substring(0, contextLength);
705
+
706
+ const start = Math.max(0, position - contextLength / 2);
707
+ const end = Math.min(text.length, position + searchTerm.length + contextLength / 2);
708
+
709
+ let preview = text.substring(start, end);
710
+ if (start > 0) preview = '...' + preview;
711
+ if (end < text.length) preview = preview + '...';
712
+
713
+ return preview;
714
+ }
715
+
421
716
  /**
422
717
  * Load initial conversation data
423
718
  */
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) {