shell-mirror 1.5.75 → 1.5.77

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.75",
3
+ "version": "1.5.77",
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": {
@@ -1,6 +1,8 @@
1
1
  <!DOCTYPE html>
2
2
  <html>
3
3
  <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
4
6
  <title>Terminal Mirror</title>
5
7
 
6
8
  <!-- Google Analytics 4 -->
@@ -74,7 +76,7 @@
74
76
  flex-direction: column;
75
77
  }
76
78
 
77
- /* Session Header */
79
+ /* Session Header - Unified Design */
78
80
  .session-header {
79
81
  background: #2a2a2a;
80
82
  color: #ccc;
@@ -85,64 +87,157 @@
85
87
  justify-content: space-between;
86
88
  font-size: 0.9em;
87
89
  z-index: 100;
90
+ height: 40px;
88
91
  }
89
-
90
- .session-info {
92
+
93
+ .header-left {
91
94
  display: flex;
92
95
  align-items: center;
93
- gap: 12px;
96
+ gap: 10px;
97
+ position: relative;
94
98
  }
95
-
96
- .session-name {
97
- font-weight: bold;
98
- color: #fff;
99
+
100
+ .header-center {
101
+ flex: 1;
102
+ display: flex;
103
+ justify-content: center;
99
104
  }
100
-
101
- .session-controls {
105
+
106
+ .header-right {
102
107
  display: flex;
103
108
  align-items: center;
104
109
  gap: 8px;
105
110
  }
106
-
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;
135
+ }
136
+
137
+ .session-id {
138
+ color: #999;
139
+ font-size: 0.85em;
140
+ }
141
+
142
+ .dropdown-arrow {
143
+ color: #888;
144
+ font-size: 0.7em;
145
+ }
146
+
147
+ /* Session Info Dropdown */
148
+ .session-info-dropdown {
149
+ display: none;
150
+ position: absolute;
151
+ top: 100%;
152
+ left: 0;
153
+ margin-top: 8px;
154
+ 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;
168
+ }
169
+
170
+ .dropdown-divider {
171
+ height: 1px;
172
+ background: #444;
173
+ }
174
+
175
+ .session-detail-name {
176
+ font-weight: bold;
177
+ color: #fff;
178
+ margin-bottom: 4px;
179
+ }
180
+
181
+ .session-detail-id {
182
+ color: #999;
183
+ font-size: 0.85em;
184
+ font-family: monospace;
185
+ }
186
+
187
+ .connection-detail {
188
+ color: #ccc;
189
+ font-size: 0.9em;
190
+ }
191
+
192
+ .version-info {
193
+ color: #888;
194
+ font-size: 0.85em;
195
+ }
196
+
197
+ /* Sessions Dropdown */
107
198
  .session-dropdown {
108
199
  position: relative;
109
200
  display: inline-block;
110
201
  }
111
-
202
+
112
203
  .session-dropdown-btn {
113
- background: #3a3a3a;
204
+ background: transparent;
114
205
  color: #ccc;
115
- border: 1px solid #555;
116
- padding: 4px 8px;
206
+ border: 1px solid #444;
207
+ padding: 4px 12px;
117
208
  border-radius: 4px;
118
209
  cursor: pointer;
119
- font-size: 0.8em;
210
+ font-size: 0.9em;
120
211
  display: flex;
121
212
  align-items: center;
122
213
  gap: 4px;
214
+ transition: all 0.2s ease;
123
215
  }
124
-
216
+
125
217
  .session-dropdown-btn:hover {
126
- background: #4a4a4a;
218
+ background: #3a3a3a;
219
+ border-color: #667eea;
127
220
  }
128
-
221
+
129
222
  .session-dropdown-content {
130
223
  display: none;
131
224
  position: absolute;
132
225
  background: #2a2a2a;
133
226
  border: 1px solid #555;
134
227
  border-radius: 4px;
135
- right: 0;
228
+ left: 50%;
229
+ transform: translateX(-50%);
136
230
  top: 100%;
231
+ margin-top: 8px;
137
232
  min-width: 200px;
138
233
  z-index: 1000;
139
234
  box-shadow: 0 4px 8px rgba(0,0,0,0.3);
140
235
  }
141
-
236
+
142
237
  .session-dropdown-content.show {
143
238
  display: block;
144
239
  }
145
-
240
+
146
241
  .session-dropdown-item {
147
242
  padding: 8px 12px;
148
243
  cursor: pointer;
@@ -151,82 +246,79 @@
151
246
  justify-content: space-between;
152
247
  align-items: center;
153
248
  }
154
-
249
+
155
250
  .session-dropdown-item:last-child {
156
251
  border-bottom: none;
157
252
  }
158
-
253
+
159
254
  .session-dropdown-item:hover {
160
255
  background: #3a3a3a;
161
256
  }
162
-
257
+
163
258
  .session-dropdown-item.current {
164
259
  background: #4285f4;
165
260
  color: white;
166
261
  }
167
-
168
- #terminal {
169
- padding: 8px; /* Mac Terminal.app padding */
170
- background-color: #000000;
171
- height: calc(100% - 16px - 40px); /* Subtract session header height */
172
- width: calc(100% - 16px);
173
- flex: 1;
174
- }
175
- #connect-container { padding: 2em; text-align: center; }
176
- #agent-id-input { font-size: 1.2em; padding: 8px; width: 400px; margin-bottom: 1em; }
177
- #connect-btn { font-size: 1.2em; padding: 10px 20px; }
178
-
179
- /* How to Use Button */
180
- .how-to-use-btn {
181
- position: fixed;
182
- top: 20px;
183
- left: 170px;
184
- background: rgba(102, 126, 234, 0.9);
185
- color: white;
186
- border: none;
187
- border-radius: 8px;
188
- padding: 10px 16px;
262
+
263
+ /* Header Buttons */
264
+ .header-btn {
265
+ background: transparent;
266
+ border: 1px solid #444;
267
+ color: #ccc;
268
+ padding: 6px 12px;
269
+ border-radius: 4px;
189
270
  cursor: pointer;
190
- font-size: 0.9em;
191
- font-weight: 500;
192
271
  display: flex;
193
272
  align-items: center;
194
273
  gap: 6px;
195
- z-index: 10000;
196
- backdrop-filter: blur(10px);
274
+ font-size: 0.9em;
275
+ text-decoration: none;
197
276
  transition: all 0.2s ease;
198
277
  }
199
278
 
200
- .how-to-use-btn:hover {
201
- background: rgba(102, 126, 234, 1);
202
- transform: translateY(-1px);
279
+ .header-btn:hover {
280
+ background: #3a3a3a;
281
+ border-color: #667eea;
203
282
  }
204
283
 
205
- /* Back to Dashboard Button */
206
- .back-to-dashboard {
207
- position: fixed;
208
- top: 20px;
209
- right: 20px;
210
- background: rgba(66, 133, 244, 0.9);
211
- color: white;
212
- border: none;
213
- padding: 12px 20px;
214
- border-radius: 8px;
215
- font-weight: 500;
216
- cursor: pointer;
217
- z-index: 1000;
218
- transition: all 0.2s ease;
219
- display: flex;
220
- align-items: center;
221
- gap: 8px;
222
- text-decoration: none;
284
+ .help-btn .btn-text {
285
+ display: none;
223
286
  }
224
-
225
- .back-to-dashboard:hover {
226
- background: rgba(51, 103, 214, 0.9);
227
- transform: translateY(-1px);
287
+
288
+ @media (min-width: 768px) {
289
+ .help-btn .btn-text {
290
+ display: inline;
291
+ }
292
+ }
293
+
294
+ /* Dashboard button with status dot */
295
+ .dashboard-btn {
296
+ position: relative;
228
297
  }
229
298
 
299
+ .dashboard-btn::before {
300
+ content: '';
301
+ position: absolute;
302
+ top: 4px;
303
+ right: 4px;
304
+ width: 8px;
305
+ height: 8px;
306
+ border-radius: 50%;
307
+ background: #44ff44;
308
+ box-shadow: 0 0 8px rgba(68, 255, 68, 0.5);
309
+ }
310
+
311
+ #terminal {
312
+ padding: 8px; /* Mac Terminal.app padding */
313
+ background-color: #000000;
314
+ height: calc(100% - 16px - 40px); /* Subtract session header height */
315
+ width: calc(100% - 16px);
316
+ flex: 1;
317
+ }
318
+ #connect-container { padding: 2em; text-align: center; }
319
+ #agent-id-input { font-size: 1.2em; padding: 8px; width: 400px; margin-bottom: 1em; }
320
+ #connect-btn { font-size: 1.2em; padding: 10px 20px; }
321
+
230
322
  /* Connection Status Indicator */
231
323
  .connection-status {
232
324
  width: 8px;
@@ -254,29 +346,40 @@
254
346
  </style>
255
347
  </head>
256
348
  <body>
257
- <!-- Back to Dashboard Button -->
258
- <a href="/app/dashboard.html" class="back-to-dashboard" id="dashboard-btn">
259
- <div class="connection-status" id="connection-status"></div>
260
- <span>←</span>
261
- <span>Dashboard</span>
262
- </a>
263
-
264
- <!-- How to Use Button -->
265
- <button class="how-to-use-btn" id="how-to-use-btn" onclick="showHelpModal()">
266
- 📖 How to Use
267
- </button>
268
-
269
349
  <div id="connect-container">
270
350
  <h2>Terminal Mirror</h2>
271
351
  <p>Connecting to terminal...</p>
272
352
  </div>
273
353
  <div id="terminal-container">
274
354
  <div class="session-header" id="session-header" style="display: none;">
275
- <div class="session-info">
276
- <span class="session-name" id="session-name">Terminal Session</span>
277
- <span class="session-id" id="session-id"></span>
355
+ <div class="header-left">
356
+ <div class="connection-status" id="connection-status"></div>
357
+ <button class="session-info-btn" id="session-info-btn">
358
+ <span class="session-name" id="session-name">Terminal Session</span>
359
+ <span class="session-id" id="session-id"></span>
360
+ <span class="dropdown-arrow">▼</span>
361
+ </button>
362
+
363
+ <!-- Session Info Dropdown -->
364
+ <div class="session-info-dropdown" id="session-info-dropdown">
365
+ <div class="dropdown-section">
366
+ <div class="session-detail-name" id="detail-name">Session 1</div>
367
+ <div class="session-detail-id" id="detail-id">ses_1764...</div>
368
+ </div>
369
+ <div class="dropdown-divider"></div>
370
+ <div class="dropdown-section">
371
+ <div class="connection-detail" id="connection-detail">
372
+ 🟢 Connected via WebRTC
373
+ </div>
374
+ </div>
375
+ <div class="dropdown-divider"></div>
376
+ <div class="dropdown-section version-info">
377
+ <div id="version-info-dropdown">v1.5.76 • Built Nov 26, 2025</div>
378
+ </div>
379
+ </div>
278
380
  </div>
279
- <div class="session-controls">
381
+
382
+ <div class="header-center">
280
383
  <div class="session-dropdown">
281
384
  <button class="session-dropdown-btn" id="session-dropdown-btn">
282
385
  <span>Sessions</span>
@@ -289,6 +392,16 @@
289
392
  </div>
290
393
  </div>
291
394
  </div>
395
+
396
+ <div class="header-right">
397
+ <button class="header-btn help-btn" onclick="showHelpModal()">
398
+ <span>📖</span>
399
+ <span class="btn-text">Help</span>
400
+ </button>
401
+ <a href="/app/dashboard.html" class="header-btn dashboard-btn">
402
+ <span>📊</span>
403
+ </a>
404
+ </div>
292
405
  </div>
293
406
  <div id="terminal"></div>
294
407
  </div>
@@ -338,13 +451,6 @@
338
451
  </div>
339
452
  </div>
340
453
 
341
- <!-- Version Footer -->
342
- <footer style="background: #ff6b35; color: white; text-align: center; padding: 10px 0; font-size: 0.8rem; position: fixed; bottom: 0; left: 0; right: 0; z-index: 1000;">
343
- <div style="max-width: 1200px; margin: 0 auto;">
344
- <p id="terminal-version-info">Terminal Mirror • Loading version...</p>
345
- </div>
346
- </footer>
347
-
348
454
  <script>
349
455
  // Help Modal Functions
350
456
  function showHelpModal() {
@@ -155,7 +155,7 @@ const chunkAssembler = {
155
155
  function updateConnectionStatus(status) {
156
156
  const statusElement = document.getElementById('connection-status');
157
157
  if (!statusElement) return;
158
-
158
+
159
159
  statusElement.className = 'connection-status';
160
160
  switch(status) {
161
161
  case 'connecting':
@@ -169,6 +169,9 @@ function updateConnectionStatus(status) {
169
169
  // Default red styling already applied
170
170
  break;
171
171
  }
172
+
173
+ // Update connection detail in dropdown
174
+ updateConnectionDetail(status);
172
175
  }
173
176
 
174
177
  // Cleanup timer for chunk assembler
@@ -217,27 +220,63 @@ window.addEventListener('load', () => {
217
220
  }
218
221
  });
219
222
 
220
- // Load version info for footer
223
+ // Load version info into dropdown
221
224
  async function loadVersionInfo() {
222
225
  try {
223
226
  const response = await fetch('/build-info.json');
224
227
  const buildInfo = await response.json();
225
- const versionElement = document.getElementById('terminal-version-info');
226
- const footerElement = versionElement?.parentElement?.parentElement; // Get the footer element
227
-
228
- if (versionElement && buildInfo) {
229
- const buildDateTime = new Date(buildInfo.buildTime).toLocaleString();
230
- versionElement.textContent = `Terminal Mirror v${buildInfo.version} • Built ${buildDateTime}`;
231
-
232
- // Apply random footer color from build info
233
- if (footerElement && buildInfo.footerColor) {
234
- footerElement.style.background = buildInfo.footerColor;
235
- console.log(`🎨 Applied footer color: ${buildInfo.footerColor}`);
228
+
229
+ if (buildInfo) {
230
+ const buildDateTime = new Date(buildInfo.buildTime).toLocaleString('en-US', {
231
+ month: 'short',
232
+ day: 'numeric',
233
+ year: 'numeric',
234
+ hour: '2-digit',
235
+ minute: '2-digit'
236
+ });
237
+
238
+ const versionElement = document.getElementById('version-info-dropdown');
239
+ if (versionElement) {
240
+ versionElement.textContent = `v${buildInfo.version} • Built ${buildDateTime}`;
236
241
  }
237
242
  }
238
243
  } catch (error) {
239
244
  console.log('Could not load build info for terminal:', error);
240
- // Keep default version and color if build-info.json not available
245
+ }
246
+ }
247
+
248
+ // 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
+ }
241
280
  }
242
281
  }
243
282
 
@@ -262,6 +301,7 @@ function startConnection() {
262
301
  // Delay fit to ensure proper dimensions after CSS transitions
263
302
  setTimeout(() => {
264
303
  fitAddon.fit();
304
+ term.focus(); // Ensure cursor is visible even before connection
265
305
  }, 100);
266
306
  initialize();
267
307
  }
@@ -919,15 +959,18 @@ function updateSessionDisplay() {
919
959
  const sessionHeader = document.getElementById('session-header');
920
960
  const sessionName = document.getElementById('session-name');
921
961
  const sessionId = document.getElementById('session-id');
922
-
962
+
923
963
  if (currentSession) {
924
964
  sessionHeader.style.display = 'flex';
925
965
  sessionName.textContent = currentSession.name;
926
966
  sessionId.textContent = `(${currentSession.id.substring(0, 8)}...)`;
927
-
967
+
928
968
  // Update available sessions dropdown
929
969
  updateSessionDropdown();
930
-
970
+
971
+ // Update session details in dropdown
972
+ updateSessionDetails();
973
+
931
974
  console.log('[CLIENT] 📋 Session display updated:', currentSession);
932
975
  }
933
976
  }
@@ -997,22 +1040,49 @@ function createNewSession() {
997
1040
  window.location.href = url.toString();
998
1041
  }
999
1042
 
1000
- // Setup dropdown toggle
1043
+ // Setup dropdown toggles
1001
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
1002
1050
  const dropdownBtn = document.getElementById('session-dropdown-btn');
1003
1051
  const dropdownContent = document.getElementById('session-dropdown-content');
1004
-
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
1005
1066
  if (dropdownBtn && dropdownContent) {
1006
1067
  dropdownBtn.onclick = (e) => {
1007
1068
  e.stopPropagation();
1008
1069
  dropdownContent.classList.toggle('show');
1070
+ // Close session info dropdown if open
1071
+ if (sessionInfoDropdown) {
1072
+ sessionInfoDropdown.classList.remove('show');
1073
+ }
1009
1074
  };
1010
-
1011
- // Close dropdown when clicking outside
1012
- document.addEventListener('click', () => {
1013
- dropdownContent.classList.remove('show');
1014
- });
1015
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
+ });
1016
1086
  });
1017
1087
 
1018
1088
  // Handle session-related data channel messages