shell-mirror 1.5.80 → 1.5.82

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shell-mirror",
3
- "version": "1.5.80",
3
+ "version": "1.5.82",
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": {
@@ -109,155 +109,121 @@
109
109
  gap: 8px;
110
110
  }
111
111
 
112
- /* Session Info Button */
113
- .session-info-btn {
114
- background: transparent;
115
- border: 1px solid #444;
116
- color: #ccc;
117
- padding: 4px 12px;
118
- border-radius: 4px;
119
- cursor: pointer;
120
- display: flex;
121
- align-items: center;
122
- gap: 8px;
123
- font-size: 0.9em;
124
- transition: all 0.2s ease;
125
- }
126
-
127
- .session-info-btn:hover {
128
- background: #3a3a3a;
129
- border-color: #667eea;
130
- }
131
-
132
- .session-name {
133
- font-weight: bold;
134
- color: #fff;
112
+ /* Connection Status Indicator */
113
+ .connection-status {
114
+ width: 12px;
115
+ height: 12px;
116
+ border-radius: 50%;
117
+ background: #ff4444;
118
+ margin-right: 12px;
119
+ flex-shrink: 0;
135
120
  }
136
121
 
137
- .session-id {
138
- color: #999;
139
- font-size: 0.85em;
122
+ .connection-status.connecting {
123
+ background: #ffaa44;
124
+ animation: pulse 1.5s ease-in-out infinite;
140
125
  }
141
126
 
142
- .dropdown-arrow {
143
- color: #888;
144
- font-size: 0.7em;
127
+ .connection-status.connected {
128
+ background: #44ff44;
129
+ box-shadow: 0 0 6px rgba(68, 255, 68, 0.5);
145
130
  }
146
131
 
147
- /* Session Info Dropdown */
148
- .session-info-dropdown {
149
- display: none;
150
- position: absolute;
151
- top: 100%;
152
- left: 0;
153
- margin-top: 8px;
132
+ /* Session Tab Bar */
133
+ .session-tab-bar {
134
+ display: flex;
135
+ gap: 4px;
136
+ padding: 8px;
154
137
  background: #2a2a2a;
155
- border: 1px solid #555;
156
- border-radius: 4px;
157
- min-width: 280px;
158
- box-shadow: 0 4px 12px rgba(0,0,0,0.5);
159
- z-index: 1000;
160
- }
161
-
162
- .session-info-dropdown.show {
163
- display: block;
164
- }
165
-
166
- .dropdown-section {
167
- padding: 12px 16px;
138
+ overflow-x: auto;
139
+ -webkit-overflow-scrolling: touch;
140
+ flex: 1;
141
+ align-items: center;
168
142
  }
169
143
 
170
- .dropdown-divider {
171
- height: 1px;
172
- background: #444;
144
+ .session-tab-bar::-webkit-scrollbar {
145
+ height: 4px;
173
146
  }
174
147
 
175
- .session-detail-name {
176
- font-weight: bold;
177
- color: #fff;
178
- margin-bottom: 4px;
148
+ .session-tab-bar::-webkit-scrollbar-thumb {
149
+ background: #555;
150
+ border-radius: 2px;
179
151
  }
180
152
 
181
- .session-detail-id {
153
+ .session-tab {
154
+ display: flex;
155
+ align-items: center;
156
+ gap: 6px;
157
+ padding: 8px 12px;
158
+ background: transparent;
159
+ border: none;
160
+ border-bottom: 3px solid transparent;
182
161
  color: #999;
183
- font-size: 0.85em;
184
- font-family: monospace;
162
+ cursor: pointer;
163
+ min-width: 120px;
164
+ white-space: nowrap;
165
+ transition: all 0.2s ease;
166
+ flex-shrink: 0;
185
167
  }
186
168
 
187
- .connection-detail {
169
+ .session-tab:hover {
170
+ background: #3a3a3a;
188
171
  color: #ccc;
189
- font-size: 0.9em;
190
172
  }
191
173
 
192
- .version-info {
193
- color: #888;
194
- font-size: 0.85em;
174
+ .session-tab.active {
175
+ color: #fff;
176
+ font-weight: 600;
177
+ border-bottom-color: #667eea;
178
+ background: #1a1a1a;
195
179
  }
196
180
 
197
- /* Sessions Dropdown */
198
- .session-dropdown {
199
- position: relative;
200
- display: inline-block;
181
+ .session-tab-status {
182
+ width: 8px;
183
+ height: 8px;
184
+ border-radius: 50%;
185
+ flex-shrink: 0;
201
186
  }
202
187
 
203
- .session-dropdown-btn {
204
- background: transparent;
205
- color: #ccc;
206
- border: 1px solid #444;
207
- padding: 4px 12px;
208
- border-radius: 4px;
209
- cursor: pointer;
210
- font-size: 0.9em;
211
- display: flex;
212
- align-items: center;
213
- gap: 4px;
214
- transition: all 0.2s ease;
188
+ .session-tab-status.connected {
189
+ background: #44ff44;
190
+ box-shadow: 0 0 6px rgba(68, 255, 68, 0.5);
215
191
  }
216
192
 
217
- .session-dropdown-btn:hover {
218
- background: #3a3a3a;
219
- border-color: #667eea;
193
+ .session-tab-status.connecting {
194
+ background: #ffaa44;
195
+ animation: pulse 1.5s ease-in-out infinite;
220
196
  }
221
197
 
222
- .session-dropdown-content {
223
- display: none;
224
- position: absolute;
225
- background: #2a2a2a;
226
- border: 1px solid #555;
227
- border-radius: 4px;
228
- left: 50%;
229
- transform: translateX(-50%);
230
- top: 100%;
231
- margin-top: 8px;
232
- min-width: 200px;
233
- z-index: 1000;
234
- box-shadow: 0 4px 8px rgba(0,0,0,0.3);
198
+ .session-tab-status.disconnected {
199
+ background: #ff4444;
235
200
  }
236
201
 
237
- .session-dropdown-content.show {
238
- display: block;
202
+ .session-tab-name {
203
+ overflow: hidden;
204
+ text-overflow: ellipsis;
239
205
  }
240
206
 
241
- .session-dropdown-item {
242
- padding: 8px 12px;
207
+ .session-tab-new {
208
+ padding: 6px 12px;
209
+ background: #667eea;
210
+ border-radius: 4px;
211
+ color: white;
212
+ border: none;
243
213
  cursor: pointer;
244
- border-bottom: 1px solid #444;
245
- display: flex;
246
- justify-content: space-between;
247
- align-items: center;
248
- }
249
-
250
- .session-dropdown-item:last-child {
251
- border-bottom: none;
214
+ margin-left: 4px;
215
+ font-weight: 500;
216
+ transition: all 0.2s ease;
217
+ flex-shrink: 0;
252
218
  }
253
219
 
254
- .session-dropdown-item:hover {
255
- background: #3a3a3a;
220
+ .session-tab-new:hover {
221
+ background: #5568d3;
256
222
  }
257
223
 
258
- .session-dropdown-item.current {
259
- background: #4285f4;
260
- color: white;
224
+ @keyframes pulse {
225
+ 0%, 100% { opacity: 1; }
226
+ 50% { opacity: 0.5; }
261
227
  }
262
228
 
263
229
  /* Header Buttons */
@@ -434,54 +400,22 @@
434
400
  </div>
435
401
  <div id="terminal-container">
436
402
  <div class="session-header" id="session-header" style="display: none;">
437
- <div class="header-left">
438
- <div class="connection-status" id="connection-status"></div>
439
- <button class="session-info-btn" id="session-info-btn">
440
- <span class="session-name" id="session-name">Terminal Session</span>
441
- <span class="session-id" id="session-id"></span>
442
- <span class="dropdown-arrow">▼</span>
443
- </button>
444
-
445
- <!-- Session Info Dropdown -->
446
- <div class="session-info-dropdown" id="session-info-dropdown">
447
- <div class="dropdown-section">
448
- <div class="session-detail-name" id="detail-name">Session 1</div>
449
- <div class="session-detail-id" id="detail-id">ses_1764...</div>
450
- </div>
451
- <div class="dropdown-divider"></div>
452
- <div class="dropdown-section">
453
- <div class="connection-detail" id="connection-detail">
454
- 🟢 Connected via WebRTC
455
- </div>
456
- </div>
457
- <div class="dropdown-divider"></div>
458
- <div class="dropdown-section version-info">
459
- <div id="version-info-dropdown">v1.5.76 • Built Nov 26, 2025</div>
460
- </div>
461
- </div>
462
- </div>
403
+ <!-- Connection Status Indicator -->
404
+ <div class="connection-status" id="connection-status"></div>
463
405
 
464
- <div class="header-center">
465
- <div class="session-dropdown">
466
- <button class="session-dropdown-btn" id="session-dropdown-btn">
467
- <span>Sessions</span>
468
- <span>▼</span>
469
- </button>
470
- <div class="session-dropdown-content" id="session-dropdown-content">
471
- <div class="session-dropdown-item" onclick="createNewSession()">
472
- <span>+ New Session</span>
473
- </div>
474
- </div>
475
- </div>
406
+ <!-- Session Tab Bar -->
407
+ <div class="session-tab-bar" id="session-tab-bar">
408
+ <!-- Tabs will be rendered here by JavaScript -->
476
409
  </div>
477
410
 
411
+ <!-- Header Right Section -->
478
412
  <div class="header-right">
479
413
  <button class="header-btn help-btn" onclick="showHelpModal()">
480
414
  <span>📖</span>
481
415
  <span class="btn-text">Help</span>
482
416
  </button>
483
417
  <a href="/app/dashboard.html" class="header-btn dashboard-btn">
484
- <span>📊</span>
418
+ <span class="btn-text">Dashboard</span>
485
419
  </a>
486
420
  </div>
487
421
  </div>
@@ -170,8 +170,10 @@ function updateConnectionStatus(status) {
170
170
  break;
171
171
  }
172
172
 
173
- // Update connection detail in dropdown
174
- updateConnectionDetail(status);
173
+ // Update tab status indicators
174
+ if (currentSession) {
175
+ renderTabs();
176
+ }
175
177
  }
176
178
 
177
179
  // Cleanup timer for chunk assembler
@@ -246,42 +248,6 @@ async function loadVersionInfo() {
246
248
  }
247
249
 
248
250
  // Update connection detail in dropdown
249
- function updateConnectionDetail(status, method = 'WebRTC') {
250
- const detail = document.getElementById('connection-detail');
251
- if (!detail) return;
252
-
253
- const statusIcons = {
254
- connected: '🟢',
255
- connecting: '🟡',
256
- disconnected: '🔴'
257
- };
258
-
259
- const statusTexts = {
260
- connected: 'Connected',
261
- connecting: 'Connecting',
262
- disconnected: 'Disconnected'
263
- };
264
-
265
- detail.textContent = `${statusIcons[status] || '⚪'} ${statusTexts[status] || 'Unknown'} via ${method}`;
266
- }
267
-
268
- // Update session details in dropdown
269
- function updateSessionDetails() {
270
- if (currentSession) {
271
- const detailName = document.getElementById('detail-name');
272
- const detailId = document.getElementById('detail-id');
273
-
274
- if (detailName) {
275
- detailName.textContent = currentSession.name || 'Terminal Session';
276
- }
277
- if (detailId) {
278
- detailId.textContent = `ID: ${currentSession.id}`;
279
- }
280
- }
281
- }
282
-
283
-
284
-
285
251
  function startConnection() {
286
252
  updateConnectionStatus('connecting');
287
253
  connectContainer.style.display = 'none';
@@ -598,6 +564,11 @@ async function initializeWebRTCSignaling() {
598
564
  if (nextData.availableSessions) {
599
565
  availableSessions = nextData.availableSessions;
600
566
  console.log('[CLIENT] 📚 Available sessions:', availableSessions);
567
+
568
+ // Re-render tabs with updated session list
569
+ if (currentSession) {
570
+ renderTabs();
571
+ }
601
572
  }
602
573
 
603
574
  console.log('[CLIENT] Received WebRTC offer from agent.');
@@ -957,50 +928,84 @@ function sendMessage(message) {
957
928
  // Session Management Functions
958
929
  function updateSessionDisplay() {
959
930
  const sessionHeader = document.getElementById('session-header');
960
- const sessionName = document.getElementById('session-name');
961
- const sessionId = document.getElementById('session-id');
931
+ if (!sessionHeader) {
932
+ console.warn('[CLIENT] ⚠️ session-header element not found');
933
+ return;
934
+ }
962
935
 
963
936
  if (currentSession) {
964
937
  sessionHeader.style.display = 'flex';
965
- sessionName.textContent = currentSession.name;
966
- sessionId.textContent = `(${currentSession.id.substring(0, 8)}...)`;
967
-
968
- // Update available sessions dropdown
969
- updateSessionDropdown();
970
938
 
971
- // Update session details in dropdown
972
- updateSessionDetails();
939
+ // Update tabs
940
+ console.log('[CLIENT] 📋 Rendering tabs, availableSessions:', availableSessions);
941
+ renderTabs();
973
942
 
974
943
  console.log('[CLIENT] 📋 Session display updated:', currentSession);
975
944
  }
976
945
  }
977
946
 
978
- function updateSessionDropdown() {
979
- const dropdownContent = document.getElementById('session-dropdown-content');
980
-
981
- // Clear existing items except "New Session"
982
- dropdownContent.innerHTML = `
983
- <div class="session-dropdown-item" onclick="createNewSession()">
984
- <span>+ New Session</span>
985
- </div>
986
- `;
987
-
988
- // Add available sessions
947
+ function renderTabs() {
948
+ const tabBar = document.getElementById('session-tab-bar');
949
+ if (!tabBar) {
950
+ console.warn('[CLIENT] ⚠️ session-tab-bar element not found');
951
+ return;
952
+ }
953
+
954
+ // Get connection status
955
+ const connectionStatus = getConnectionStatus();
956
+
957
+ // Ensure we have sessions to display
958
+ let sessionsToRender = [];
959
+
989
960
  if (availableSessions && availableSessions.length > 0) {
990
- availableSessions.forEach(session => {
991
- const item = document.createElement('div');
992
- item.className = 'session-dropdown-item';
993
- if (currentSession && session.id === currentSession.id) {
994
- item.classList.add('current');
995
- }
996
-
997
- item.innerHTML = `
998
- <span>${session.name}</span>
999
- <small>${formatLastActivity(session.lastActivity)}</small>
961
+ // Use availableSessions from agent
962
+ sessionsToRender = availableSessions;
963
+ } else if (currentSession) {
964
+ // Fallback: show at least the current session
965
+ sessionsToRender = [currentSession];
966
+ }
967
+
968
+ console.log('[CLIENT] 🎨 Rendering tabs:', {
969
+ sessionCount: sessionsToRender.length,
970
+ currentSession: currentSession?.id,
971
+ connectionStatus,
972
+ source: availableSessions?.length > 0 ? 'agent' : 'fallback'
973
+ });
974
+
975
+ // Build tabs HTML
976
+ let tabsHTML = '';
977
+
978
+ if (sessionsToRender.length > 0) {
979
+ tabsHTML = sessionsToRender.map(session => {
980
+ const isActive = currentSession && session.id === currentSession.id;
981
+ const displayName = session.name || 'Terminal Session';
982
+
983
+ return `
984
+ <button class="session-tab ${isActive ? 'active' : ''}"
985
+ onclick="switchToSession('${session.id}')"
986
+ ${isActive ? 'disabled' : ''}
987
+ title="${displayName}">
988
+ <span class="session-tab-status ${connectionStatus}"></span>
989
+ <span class="session-tab-name">${displayName}</span>
990
+ </button>
1000
991
  `;
1001
- item.onclick = () => switchToSession(session.id);
1002
- dropdownContent.appendChild(item);
1003
- });
992
+ }).join('');
993
+ }
994
+
995
+ // Add new session button
996
+ tabsHTML += '<button class="session-tab-new" onclick="createNewSession()" title="New Session">+</button>';
997
+
998
+ tabBar.innerHTML = tabsHTML;
999
+ console.log('[CLIENT] ✅ Tabs rendered:', sessionsToRender.length, 'tabs');
1000
+ }
1001
+
1002
+ function getConnectionStatus() {
1003
+ if (dataChannel && dataChannel.readyState === 'open') {
1004
+ return 'connected';
1005
+ } else if (dataChannel && dataChannel.readyState === 'connecting') {
1006
+ return 'connecting';
1007
+ } else {
1008
+ return 'disconnected';
1004
1009
  }
1005
1010
  }
1006
1011
 
@@ -1022,15 +1027,12 @@ function switchToSession(sessionId) {
1022
1027
  console.error('[CLIENT] ❌ Cannot switch session - data channel not open');
1023
1028
  return;
1024
1029
  }
1025
-
1030
+
1026
1031
  console.log('[CLIENT] 🔄 Switching to session:', sessionId);
1027
- dataChannel.send(JSON.stringify({
1028
- type: 'session-switch',
1029
- sessionId: sessionId
1032
+ dataChannel.send(JSON.stringify({
1033
+ type: 'session-switch',
1034
+ sessionId: sessionId
1030
1035
  }));
1031
-
1032
- // Hide dropdown
1033
- document.getElementById('session-dropdown-content').classList.remove('show');
1034
1036
  }
1035
1037
 
1036
1038
  function createNewSession() {
@@ -1040,50 +1042,6 @@ function createNewSession() {
1040
1042
  window.location.href = url.toString();
1041
1043
  }
1042
1044
 
1043
- // Setup dropdown toggles
1044
- document.addEventListener('DOMContentLoaded', () => {
1045
- // Session info dropdown
1046
- const sessionInfoBtn = document.getElementById('session-info-btn');
1047
- const sessionInfoDropdown = document.getElementById('session-info-dropdown');
1048
-
1049
- // Sessions menu dropdown
1050
- const dropdownBtn = document.getElementById('session-dropdown-btn');
1051
- const dropdownContent = document.getElementById('session-dropdown-content');
1052
-
1053
- // Session info dropdown toggle
1054
- if (sessionInfoBtn && sessionInfoDropdown) {
1055
- sessionInfoBtn.onclick = (e) => {
1056
- e.stopPropagation();
1057
- sessionInfoDropdown.classList.toggle('show');
1058
- // Close sessions dropdown if open
1059
- if (dropdownContent) {
1060
- dropdownContent.classList.remove('show');
1061
- }
1062
- };
1063
- }
1064
-
1065
- // Sessions menu dropdown toggle
1066
- if (dropdownBtn && dropdownContent) {
1067
- dropdownBtn.onclick = (e) => {
1068
- e.stopPropagation();
1069
- dropdownContent.classList.toggle('show');
1070
- // Close session info dropdown if open
1071
- if (sessionInfoDropdown) {
1072
- sessionInfoDropdown.classList.remove('show');
1073
- }
1074
- };
1075
- }
1076
-
1077
- // Close both dropdowns when clicking outside
1078
- document.addEventListener('click', () => {
1079
- if (dropdownContent) {
1080
- dropdownContent.classList.remove('show');
1081
- }
1082
- if (sessionInfoDropdown) {
1083
- sessionInfoDropdown.classList.remove('show');
1084
- }
1085
- });
1086
- });
1087
1045
 
1088
1046
  // Handle session-related data channel messages
1089
1047
  function handleSessionMessage(message) {