shell-mirror 1.5.100 → 1.5.106

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.
@@ -976,6 +976,20 @@ function setupDataChannel(clientId) {
976
976
  }));
977
977
  logToFile(`[AGENT] ❌ Failed to create session for client ${clientId}`);
978
978
  }
979
+ } else if (message.type === 'close_session') {
980
+ // Handle session closure request from client
981
+ logToFile(`[AGENT] Client ${clientId} closing session ${message.sessionId}`);
982
+
983
+ sessionManager.terminateSession(message.sessionId);
984
+
985
+ // Send confirmation with updated session list
986
+ dataChannel.send(JSON.stringify({
987
+ type: 'session-closed',
988
+ sessionId: message.sessionId,
989
+ availableSessions: sessionManager.getAllSessions()
990
+ }));
991
+
992
+ logToFile(`[AGENT] ✅ Session closed: ${message.sessionId}`);
979
993
  }
980
994
  } catch (err) {
981
995
  logToFile(`[AGENT] Error parsing data channel message: ${err.message}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shell-mirror",
3
- "version": "1.5.100",
3
+ "version": "1.5.106",
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": {
@@ -268,6 +268,38 @@ body {
268
268
  margin-left: 8px;
269
269
  }
270
270
 
271
+ /* Animated Loading Dots */
272
+ .loading-dots {
273
+ display: inline-flex;
274
+ gap: 2px;
275
+ }
276
+
277
+ .loading-dots span {
278
+ animation: loadingDot 1.4s infinite;
279
+ opacity: 0.2;
280
+ }
281
+
282
+ .loading-dots span:nth-child(1) {
283
+ animation-delay: 0s;
284
+ }
285
+
286
+ .loading-dots span:nth-child(2) {
287
+ animation-delay: 0.2s;
288
+ }
289
+
290
+ .loading-dots span:nth-child(3) {
291
+ animation-delay: 0.4s;
292
+ }
293
+
294
+ @keyframes loadingDot {
295
+ 0%, 80%, 100% {
296
+ opacity: 0.2;
297
+ }
298
+ 40% {
299
+ opacity: 1;
300
+ }
301
+ }
302
+
271
303
  .connection-status {
272
304
  font-size: 0.8rem;
273
305
  font-weight: 500;
@@ -1,4 +1,15 @@
1
1
  // Dashboard functionality for Shell Mirror
2
+
3
+ // Session color palette (matches terminal.js)
4
+ const SESSION_COLORS = [
5
+ { border: '#2196f3', text: '#1565c0' }, // Blue
6
+ { border: '#4caf50', text: '#2e7d32' }, // Green
7
+ { border: '#ff9800', text: '#e65100' }, // Orange
8
+ { border: '#9c27b0', text: '#6a1b9a' }, // Purple
9
+ { border: '#00bcd4', text: '#00838f' }, // Teal
10
+ { border: '#e91e63', text: '#ad1457' }, // Pink
11
+ ];
12
+
2
13
  class ShellMirrorDashboard {
3
14
  constructor() {
4
15
  this.isAuthenticated = false;
@@ -575,16 +586,17 @@ class ShellMirrorDashboard {
575
586
  ? this.formatPreciseLastSeen(agent.timeSinceLastSeen)
576
587
  : this.formatLastSeen(agent.lastSeen);
577
588
 
578
- // Build inline session list
579
- const sessionsHtml = sessions.map(session => {
589
+ // Build inline session list with colors
590
+ const sessionsHtml = sessions.map((session, index) => {
580
591
  const sessionStatus = session.status === 'active' ? 'active' : 'crashed';
581
592
  const activityText = this.formatLastActivity(session.lastActivity);
593
+ const color = SESSION_COLORS[index % SESSION_COLORS.length];
582
594
  return `
583
- <div class="inline-session-item">
584
- <span class="session-status-dot ${sessionStatus}"></span>
585
- <span class="session-name">${session.name}</span>
595
+ <div class="inline-session-item" style="border-left: 3px solid ${color.border};">
596
+ <span class="session-status-dot" style="background-color: ${color.border};"></span>
597
+ <span class="session-name" style="color: ${color.text};">${session.name}</span>
586
598
  <span class="session-activity">${activityText}</span>
587
- <button class="btn-session-connect" onclick="dashboard.connectToSession('${agent.agentId}', '${session.id}')">
599
+ <button class="btn-session-connect" onclick="dashboard.connectToSession('${agent.agentId}', '${session.id}')" style="background-color: ${color.border};">
588
600
  Connect
589
601
  </button>
590
602
  </div>
@@ -632,7 +644,9 @@ class ShellMirrorDashboard {
632
644
  const showCleanup = offlineAgents.length > 0;
633
645
 
634
646
  // Format last refresh time
635
- const refreshTime = this.lastRefresh ? new Date(this.lastRefresh).toLocaleTimeString() : 'Loading...';
647
+ const refreshTime = this.lastRefresh
648
+ ? new Date(this.lastRefresh).toLocaleTimeString()
649
+ : '<span class="loading-dots"><span>.</span><span>.</span><span>.</span></span>';
636
650
 
637
651
  return `
638
652
  <div class="dashboard-card">
@@ -640,7 +654,7 @@ class ShellMirrorDashboard {
640
654
  <div class="card-title-section">
641
655
  <h2>Active Agents</h2>
642
656
  <span class="agent-count">${agentCount}</span>
643
- <span class="refresh-time">Updated ${refreshTime}</span>
657
+ <span class="refresh-time">${this.lastRefresh ? 'Updated ' : ''}${refreshTime}</span>
644
658
  </div>
645
659
  <div class="agent-actions-header">
646
660
  <button id="refresh-btn" class="btn-text-action" onclick="dashboard.manualRefresh()" title="Refresh agents">
@@ -172,11 +172,8 @@
172
172
  }
173
173
 
174
174
  .session-tab.active {
175
- color: #fff;
175
+ /* Colors set by inline styles from JavaScript */
176
176
  font-weight: 600;
177
- border-bottom-color: #667eea;
178
- background: #333;
179
- box-shadow: inset 0 -3px 0 #667eea;
180
177
  }
181
178
 
182
179
  .session-tab-btn {
@@ -579,6 +576,6 @@
579
576
  }
580
577
  </script>
581
578
 
582
- <script src="/app/terminal.js?v=1.5.89"></script>
579
+ <script src="/app/terminal.js?v=1.5.90"></script>
583
580
  </body>
584
581
  </html>
@@ -407,9 +407,10 @@ function setupDirectConnection(directWs) {
407
407
  }
408
408
  }
409
409
 
410
- // Clear terminal and show success message
410
+ // Clear terminal and show success message with session color
411
411
  term.clear();
412
- term.write(`\r\n\x1b[36m✨ New session created: ${currentSession.name}\x1b[0m\r\n\r\n`);
412
+ const sessionColor = getSessionColor(currentSession.id);
413
+ term.write(`\r\n\x1b[38;2;${sessionColor.ansi}m✨ New session created: ${currentSession.name}\x1b[0m\r\n\r\n`);
413
414
 
414
415
  // Update URL with session ID so refresh reconnects to same session
415
416
  updateUrlWithSession(data.sessionId);
@@ -967,12 +968,12 @@ function updateSessionDisplay() {
967
968
 
968
969
  // Session tab color palette (fixed colors by creation order)
969
970
  const SESSION_TAB_COLORS = [
970
- { bg: '#e3f2fd', border: '#2196f3', text: '#1565c0' }, // Blue
971
- { bg: '#e8f5e9', border: '#4caf50', text: '#2e7d32' }, // Green
972
- { bg: '#fff3e0', border: '#ff9800', text: '#e65100' }, // Orange
973
- { bg: '#f3e5f5', border: '#9c27b0', text: '#6a1b9a' }, // Purple
974
- { bg: '#e0f7fa', border: '#00bcd4', text: '#00838f' }, // Teal
975
- { bg: '#fce4ec', border: '#e91e63', text: '#ad1457' }, // Pink
971
+ { bg: '#e3f2fd', border: '#2196f3', text: '#1565c0', muted: '#5a9fd4', ansi: '33;150;243' }, // Blue
972
+ { bg: '#e8f5e9', border: '#4caf50', text: '#2e7d32', muted: '#6fbf73', ansi: '76;175;80' }, // Green
973
+ { bg: '#fff3e0', border: '#ff9800', text: '#e65100', muted: '#ffb74d', ansi: '255;152;0' }, // Orange
974
+ { bg: '#f3e5f5', border: '#9c27b0', text: '#6a1b9a', muted: '#ba68c8', ansi: '156;39;176' }, // Purple
975
+ { bg: '#e0f7fa', border: '#00bcd4', text: '#00838f', muted: '#4dd0e1', ansi: '0;188;212' }, // Teal
976
+ { bg: '#fce4ec', border: '#e91e63', text: '#ad1457', muted: '#f06292', ansi: '233;30;99' }, // Pink
976
977
  ];
977
978
 
978
979
  // Track color assignments by session ID (persists across renders)
@@ -1020,13 +1021,13 @@ function renderTabs() {
1020
1021
  const displayName = session.name || 'Terminal Session';
1021
1022
  const color = getSessionColor(session.id);
1022
1023
 
1023
- // Active tabs get full color, inactive tabs get muted version
1024
+ // Active tabs get full color, inactive tabs get muted version of their color
1024
1025
  const tabStyle = isActive
1025
1026
  ? `background: ${color.bg}; border-color: ${color.border}; border-bottom: 3px solid ${color.border};`
1026
- : `background: transparent; border-color: transparent; opacity: 0.7;`;
1027
+ : `background: rgba(255,255,255,0.05); border-color: transparent; border-bottom: 3px solid ${color.muted}40;`;
1027
1028
  const textStyle = isActive
1028
1029
  ? `color: ${color.text}; font-weight: 600;`
1029
- : `color: #888;`;
1030
+ : `color: ${color.muted};`;
1030
1031
 
1031
1032
  return `
1032
1033
  <div class="session-tab ${isActive ? 'active' : ''}" style="${tabStyle}" title="${displayName}" data-color-index="${sessionColorMap[session.id]}">
@@ -1035,7 +1036,7 @@ function renderTabs() {
1035
1036
  style="${textStyle}">
1036
1037
  <span class="session-tab-name">${displayName}</span>
1037
1038
  </button>
1038
- <button class="session-tab-close" onclick="closeSession('${session.id}', event)" title="Close session" style="color: ${isActive ? color.text : '#888'}">×</button>
1039
+ <button class="session-tab-close" onclick="closeSession('${session.id}', event)" title="Close session" style="color: ${isActive ? color.text : color.muted}">×</button>
1039
1040
  </div>
1040
1041
  `;
1041
1042
  }).join('');
@@ -1211,9 +1212,10 @@ function handleSessionMessage(message) {
1211
1212
  availableSessions.push(currentSession);
1212
1213
  }
1213
1214
 
1214
- // Clear terminal for new session
1215
+ // Clear terminal for new session with session color
1215
1216
  term.clear();
1216
- term.write(`\r\n\x1b[36m✨ New session created: ${currentSession.name}\x1b[0m\r\n\r\n`);
1217
+ const sessionColor = getSessionColor(currentSession.id);
1218
+ term.write(`\r\n\x1b[38;2;${sessionColor.ansi}m✨ New session created: ${currentSession.name}\x1b[0m\r\n\r\n`);
1217
1219
 
1218
1220
  // Update URL with session ID so refresh reconnects to same session
1219
1221
  updateUrlWithSession(message.sessionId);