shell-mirror 1.5.99 → 1.5.105

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.
@@ -169,11 +169,14 @@ class SessionManager {
169
169
  this.maxSessions = 10;
170
170
  this.defaultSessionTimeout = 24 * 60 * 60 * 1000; // 24 hours
171
171
  this.clientSessions = {}; // Maps clientId to sessionId
172
+ this.sessionCounter = 0; // Incrementing counter for unique session names (never resets)
172
173
  }
173
174
 
174
175
  createSession(sessionName = null, clientId = null) {
175
176
  const sessionId = `ses_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
176
- const name = sessionName || `Session ${Object.keys(this.sessions).length + 1}`;
177
+ // Use incrementing counter for unique names (doesn't reuse after deletion)
178
+ this.sessionCounter++;
179
+ const name = sessionName || `Session ${this.sessionCounter}`;
177
180
 
178
181
  logToFile(`[SESSION] Creating new session: ${sessionId} (${name})`);
179
182
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shell-mirror",
3
- "version": "1.5.99",
3
+ "version": "1.5.105",
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": {
@@ -225,6 +225,81 @@ body {
225
225
  transform: scale(1.05);
226
226
  }
227
227
 
228
+ /* Text Action Buttons (No Emoji) */
229
+ .btn-text-action {
230
+ background: #f8f9fa;
231
+ border: 1px solid #dee2e6;
232
+ border-radius: 6px;
233
+ color: #495057;
234
+ padding: 6px 12px;
235
+ font-size: 0.8rem;
236
+ font-weight: 500;
237
+ cursor: pointer;
238
+ transition: all 0.15s ease;
239
+ }
240
+
241
+ .btn-text-action:hover:not(:disabled) {
242
+ background: #e9ecef;
243
+ border-color: #ced4da;
244
+ color: #212529;
245
+ }
246
+
247
+ .btn-text-action.loading {
248
+ opacity: 0.7;
249
+ cursor: not-allowed;
250
+ }
251
+
252
+ .btn-text-action.btn-cleanup {
253
+ background: #fff5f5;
254
+ border-color: #feb2b2;
255
+ color: #c53030;
256
+ }
257
+
258
+ .btn-text-action.btn-cleanup:hover:not(:disabled) {
259
+ background: #fed7d7;
260
+ border-color: #fc8181;
261
+ }
262
+
263
+ /* Refresh Time in Card Header */
264
+ .refresh-time {
265
+ font-size: 0.75rem;
266
+ color: #718096;
267
+ font-weight: 400;
268
+ margin-left: 8px;
269
+ }
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
+
228
303
  .connection-status {
229
304
  font-size: 0.8rem;
230
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;
@@ -513,10 +524,9 @@ class ShellMirrorDashboard {
513
524
  document.getElementById('user-section').innerHTML = `
514
525
  <div class="dashboard-controls">
515
526
  <span id="connection-status" class="connection-status" style="display: none;"></span>
516
- <span id="refresh-status" class="refresh-status">Loading...</span>
517
527
  </div>
518
528
  <button class="help-button" onclick="dashboard.showAgentInstructions()" title="How to Use">
519
- 📖 How to Use
529
+ How to Use
520
530
  </button>
521
531
  <div class="user-info">
522
532
  <span class="user-name">${this.user?.name || this.user?.email || 'User'}</span>
@@ -576,16 +586,17 @@ class ShellMirrorDashboard {
576
586
  ? this.formatPreciseLastSeen(agent.timeSinceLastSeen)
577
587
  : this.formatLastSeen(agent.lastSeen);
578
588
 
579
- // Build inline session list
580
- const sessionsHtml = sessions.map(session => {
589
+ // Build inline session list with colors
590
+ const sessionsHtml = sessions.map((session, index) => {
581
591
  const sessionStatus = session.status === 'active' ? 'active' : 'crashed';
582
592
  const activityText = this.formatLastActivity(session.lastActivity);
593
+ const color = SESSION_COLORS[index % SESSION_COLORS.length];
583
594
  return `
584
- <div class="inline-session-item">
585
- <span class="session-status-dot ${sessionStatus}"></span>
586
- <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>
587
598
  <span class="session-activity">${activityText}</span>
588
- <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};">
589
600
  Connect
590
601
  </button>
591
602
  </div>
@@ -632,19 +643,25 @@ class ShellMirrorDashboard {
632
643
  const offlineAgents = this.agents.filter(agent => agent.status === 'offline');
633
644
  const showCleanup = offlineAgents.length > 0;
634
645
 
646
+ // Format last refresh time
647
+ const refreshTime = this.lastRefresh
648
+ ? new Date(this.lastRefresh).toLocaleTimeString()
649
+ : '<span class="loading-dots"><span>.</span><span>.</span><span>.</span></span>';
650
+
635
651
  return `
636
652
  <div class="dashboard-card">
637
653
  <div class="card-header">
638
654
  <div class="card-title-section">
639
- <h2>🖥️ Active Agents</h2>
640
- <span class="agent-count">${agentCount} agent${agentCount !== 1 ? 's' : ''}</span>
655
+ <h2>Active Agents</h2>
656
+ <span class="agent-count">${agentCount}</span>
657
+ <span class="refresh-time">${this.lastRefresh ? 'Updated ' : ''}${refreshTime}</span>
641
658
  </div>
642
659
  <div class="agent-actions-header">
643
- <button id="refresh-btn" class="refresh-btn-inline" onclick="dashboard.manualRefresh()" title="Refresh agents">
644
- <span class="refresh-icon">🔄</span>
660
+ <button id="refresh-btn" class="btn-text-action" onclick="dashboard.manualRefresh()" title="Refresh agents">
661
+ Refresh
645
662
  </button>
646
- ${showCleanup ? `<button class="cleanup-btn-inline" onclick="dashboard.cleanupOfflineAgents()" title="Remove offline agents">
647
- <span>🧹</span>
663
+ ${showCleanup ? `<button class="btn-text-action btn-cleanup" onclick="dashboard.cleanupOfflineAgents()" title="Remove offline agents">
664
+ Clean
648
665
  </button>` : ''}
649
666
  </div>
650
667
  </div>
@@ -659,7 +676,7 @@ class ShellMirrorDashboard {
659
676
  return `
660
677
  <div class="empty-agent-state">
661
678
  <div class="empty-state-header">
662
- <h3>🚀 Get Started with Shell Mirror</h3>
679
+ <h3>Get Started with Shell Mirror</h3>
663
680
  <p>Connect your Mac in 2 simple steps:</p>
664
681
  </div>
665
682
 
@@ -688,7 +705,7 @@ class ShellMirrorDashboard {
688
705
  </div>
689
706
 
690
707
  <div class="empty-state-footer">
691
- <p>✨ Your agent will appear here once connected</p>
708
+ <p>Your agent will appear here once connected</p>
692
709
  </div>
693
710
  </div>
694
711
  `;
@@ -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.88"></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);
@@ -965,6 +966,28 @@ function updateSessionDisplay() {
965
966
  }
966
967
  }
967
968
 
969
+ // Session tab color palette (fixed colors by creation order)
970
+ const SESSION_TAB_COLORS = [
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
977
+ ];
978
+
979
+ // Track color assignments by session ID (persists across renders)
980
+ const sessionColorMap = {};
981
+ let nextColorIndex = 0;
982
+
983
+ function getSessionColor(sessionId) {
984
+ if (!sessionColorMap[sessionId]) {
985
+ sessionColorMap[sessionId] = nextColorIndex;
986
+ nextColorIndex = (nextColorIndex + 1) % SESSION_TAB_COLORS.length;
987
+ }
988
+ return SESSION_TAB_COLORS[sessionColorMap[sessionId]];
989
+ }
990
+
968
991
  function renderTabs() {
969
992
  const tabBar = document.getElementById('session-tab-bar');
970
993
  if (!tabBar) {
@@ -996,15 +1019,24 @@ function renderTabs() {
996
1019
  tabsHTML = sessionsToRender.map(session => {
997
1020
  const isActive = currentSession && session.id === currentSession.id;
998
1021
  const displayName = session.name || 'Terminal Session';
1022
+ const color = getSessionColor(session.id);
1023
+
1024
+ // Active tabs get full color, inactive tabs get muted version of their color
1025
+ const tabStyle = isActive
1026
+ ? `background: ${color.bg}; border-color: ${color.border}; border-bottom: 3px solid ${color.border};`
1027
+ : `background: rgba(255,255,255,0.05); border-color: transparent; border-bottom: 3px solid ${color.muted}40;`;
1028
+ const textStyle = isActive
1029
+ ? `color: ${color.text}; font-weight: 600;`
1030
+ : `color: ${color.muted};`;
999
1031
 
1000
1032
  return `
1001
- <div class="session-tab ${isActive ? 'active' : ''}" title="${displayName}">
1033
+ <div class="session-tab ${isActive ? 'active' : ''}" style="${tabStyle}" title="${displayName}" data-color-index="${sessionColorMap[session.id]}">
1002
1034
  <button class="session-tab-btn"
1003
1035
  onclick="switchToSession('${session.id}')"
1004
- ${isActive ? '' : ''}>
1036
+ style="${textStyle}">
1005
1037
  <span class="session-tab-name">${displayName}</span>
1006
1038
  </button>
1007
- <button class="session-tab-close" onclick="closeSession('${session.id}', event)" title="Close session">×</button>
1039
+ <button class="session-tab-close" onclick="closeSession('${session.id}', event)" title="Close session" style="color: ${isActive ? color.text : color.muted}">×</button>
1008
1040
  </div>
1009
1041
  `;
1010
1042
  }).join('');
@@ -1180,9 +1212,10 @@ function handleSessionMessage(message) {
1180
1212
  availableSessions.push(currentSession);
1181
1213
  }
1182
1214
 
1183
- // Clear terminal for new session
1215
+ // Clear terminal for new session with session color
1184
1216
  term.clear();
1185
- 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`);
1186
1219
 
1187
1220
  // Update URL with session ID so refresh reconnects to same session
1188
1221
  updateUrlWithSession(message.sessionId);
@@ -1192,6 +1225,9 @@ function handleSessionMessage(message) {
1192
1225
 
1193
1226
  // Save to localStorage
1194
1227
  saveSessionToLocalStorage(AGENT_ID, currentSession);
1228
+
1229
+ // Focus terminal for keyboard input
1230
+ term.focus();
1195
1231
  break;
1196
1232
 
1197
1233
  case 'session-switched':
@@ -1208,6 +1244,9 @@ function handleSessionMessage(message) {
1208
1244
 
1209
1245
  // Save updated session info
1210
1246
  saveSessionToLocalStorage(AGENT_ID, currentSession);
1247
+
1248
+ // Focus terminal for keyboard input
1249
+ term.focus();
1211
1250
  break;
1212
1251
  case 'session-ended':
1213
1252
  term.write(`\r\n\x1b[31m❌ Session ended: ${message.reason}\x1b[0m\r\n`);