shell-mirror 1.5.98 → 1.5.99

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.
@@ -423,10 +423,20 @@ let heartbeatInterval;
423
423
 
424
424
  async function sendHeartbeat() {
425
425
  try {
426
+ // Get full session list for dashboard display
427
+ const sessionList = sessionManager.getAllSessions().map(session => ({
428
+ id: session.id,
429
+ name: session.name,
430
+ lastActivity: session.lastActivity,
431
+ createdAt: session.createdAt,
432
+ status: session.status
433
+ }));
434
+
426
435
  const heartbeatData = JSON.stringify({
427
436
  agentId: AGENT_ID,
428
437
  timestamp: Date.now(),
429
- activeSessions: Object.keys(sessionManager.sessions).length,
438
+ activeSessions: sessionList.length,
439
+ sessions: sessionList, // Full session list for dashboard
430
440
  localPort: process.env.LOCAL_PORT || 8080,
431
441
  capabilities: ['webrtc', 'direct_websocket']
432
442
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shell-mirror",
3
- "version": "1.5.98",
3
+ "version": "1.5.99",
4
4
  "description": "Access your Mac shell from any device securely. Perfect for mobile coding with Claude Code CLI, Gemini CLI, and any shell tool.",
5
5
  "main": "server.js",
6
6
  "bin": {
@@ -422,11 +422,19 @@ class ShellMirrorDashboard {
422
422
  if (agentsData.success && agentsData.data && agentsData.data.agents) {
423
423
  this.agents = agentsData.data.agents;
424
424
 
425
+ // Populate agentSessions from API response (sessions are sent via agent heartbeat)
426
+ this.agentSessions = {};
427
+ this.agents.forEach(agent => {
428
+ if (agent.sessions && agent.sessions.length > 0) {
429
+ this.agentSessions[agent.agentId] = agent.sessions;
430
+ }
431
+ });
432
+
425
433
  // Don't load stale sessions from localStorage - only show live sessions from agents
426
- // Sessions will be populated via WebSocket updates from connected agents
427
434
  localStorage.removeItem('shell-mirror-sessions'); // Clear any stale data
428
435
  } else {
429
436
  this.agents = [];
437
+ this.agentSessions = {};
430
438
  }
431
439
 
432
440
  // TODO: Load session history when API is available
@@ -499,9 +499,86 @@
499
499
  if (event.target === modal) {
500
500
  closeHelpModal();
501
501
  }
502
+ const closeModal = document.getElementById('close-session-modal');
503
+ if (event.target === closeModal) {
504
+ hideCloseSessionModal();
505
+ }
502
506
  });
503
507
  </script>
504
508
 
509
+ <!-- Close Session Confirmation Modal -->
510
+ <div id="close-session-modal" style="display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.85); align-items: center; justify-content: center; z-index: 20000;">
511
+ <div style="background: #2a2a2a; border-radius: 12px; max-width: 400px; width: 90%; overflow: hidden; border: 1px solid #444; box-shadow: 0 10px 40px rgba(0,0,0,0.5);">
512
+ <!-- Header -->
513
+ <div style="padding: 20px 24px; border-bottom: 1px solid #444; display: flex; justify-content: space-between; align-items: center;">
514
+ <h3 style="margin: 0; font-size: 1.1rem; color: #fff;">Close Session?</h3>
515
+ <button onclick="hideCloseSessionModal()" style="background: none; border: none; font-size: 1.5rem; cursor: pointer; padding: 0; width: 30px; height: 30px; display: flex; align-items: center; justify-content: center; border-radius: 50%; color: #888;">&times;</button>
516
+ </div>
517
+
518
+ <!-- Content -->
519
+ <div style="padding: 24px;">
520
+ <div style="display: flex; align-items: center; gap: 16px; margin-bottom: 20px;">
521
+ <div style="font-size: 2.5rem;">🗑️</div>
522
+ <div>
523
+ <div id="close-session-name" style="font-size: 1.1rem; color: #fff; font-weight: 500; margin-bottom: 4px;">Session 1</div>
524
+ <div id="close-session-duration" style="font-size: 0.85rem; color: #888;">Duration: 5 minutes</div>
525
+ </div>
526
+ </div>
527
+
528
+ <p style="color: #bbb; margin: 0 0 24px 0; font-size: 0.9rem; line-height: 1.5;">
529
+ This will terminate the terminal session. Any running processes will be stopped.
530
+ </p>
531
+
532
+ <!-- Buttons -->
533
+ <div style="display: flex; gap: 12px; justify-content: flex-end;">
534
+ <button onclick="hideCloseSessionModal()" style="padding: 10px 20px; background: #444; color: #fff; border: none; border-radius: 6px; cursor: pointer; font-size: 0.9rem; transition: background 0.2s;">Cancel</button>
535
+ <button id="confirm-close-session-btn" onclick="confirmCloseSession()" style="padding: 10px 20px; background: #dc3545; color: #fff; border: none; border-radius: 6px; cursor: pointer; font-size: 0.9rem; font-weight: 500; transition: background 0.2s;">Close Session</button>
536
+ </div>
537
+ </div>
538
+ </div>
539
+ </div>
540
+
541
+ <script>
542
+ // Close Session Modal Functions
543
+ let pendingCloseSessionId = null;
544
+
545
+ function showCloseSessionModal(sessionId, sessionName, createdAt) {
546
+ pendingCloseSessionId = sessionId;
547
+
548
+ // Calculate duration
549
+ const duration = createdAt ? formatDuration(Date.now() - createdAt) : 'Unknown';
550
+
551
+ document.getElementById('close-session-name').textContent = sessionName || 'Session';
552
+ document.getElementById('close-session-duration').textContent = `Duration: ${duration}`;
553
+ document.getElementById('close-session-modal').style.display = 'flex';
554
+ }
555
+
556
+ function hideCloseSessionModal() {
557
+ document.getElementById('close-session-modal').style.display = 'none';
558
+ pendingCloseSessionId = null;
559
+ }
560
+
561
+ function confirmCloseSession() {
562
+ if (pendingCloseSessionId) {
563
+ // Call the actual close function from terminal.js
564
+ doCloseSession(pendingCloseSessionId);
565
+ }
566
+ hideCloseSessionModal();
567
+ }
568
+
569
+ function formatDuration(ms) {
570
+ const seconds = Math.floor(ms / 1000);
571
+ const minutes = Math.floor(seconds / 60);
572
+ const hours = Math.floor(minutes / 60);
573
+ const days = Math.floor(hours / 24);
574
+
575
+ if (days > 0) return `${days}d ${hours % 24}h`;
576
+ if (hours > 0) return `${hours}h ${minutes % 60}m`;
577
+ if (minutes > 0) return `${minutes} minute${minutes !== 1 ? 's' : ''}`;
578
+ return `${seconds} second${seconds !== 1 ? 's' : ''}`;
579
+ }
580
+ </script>
581
+
505
582
  <script src="/app/terminal.js?v=1.5.88"></script>
506
583
  </body>
507
584
  </html>
@@ -1025,17 +1025,27 @@ function updateUrlWithSession(sessionId) {
1025
1025
  console.log('[CLIENT] 📍 URL updated with session:', sessionId);
1026
1026
  }
1027
1027
 
1028
- // Close a session with confirmation
1028
+ // Close a session with confirmation - shows custom modal
1029
1029
  function closeSession(sessionId, event) {
1030
1030
  event.stopPropagation(); // Don't trigger tab switch
1031
1031
 
1032
1032
  const session = availableSessions.find(s => s.id === sessionId);
1033
1033
  const sessionName = session?.name || 'this session';
1034
+ const createdAt = session?.createdAt || null;
1034
1035
 
1035
- if (!confirm(`Close "${sessionName}"?\n\nThis will terminate the terminal session.`)) {
1036
- return;
1036
+ // Show custom modal instead of browser confirm()
1037
+ if (typeof showCloseSessionModal === 'function') {
1038
+ showCloseSessionModal(sessionId, sessionName, createdAt);
1039
+ } else {
1040
+ // Fallback to native confirm if modal not available
1041
+ if (confirm(`Close "${sessionName}"?\n\nThis will terminate the terminal session.`)) {
1042
+ doCloseSession(sessionId);
1043
+ }
1037
1044
  }
1045
+ }
1038
1046
 
1047
+ // Actually close the session (called from modal confirmation)
1048
+ function doCloseSession(sessionId) {
1039
1049
  console.log('[CLIENT] 🗑️ Closing session:', sessionId);
1040
1050
 
1041
1051
  // Send close request to agent