shell-mirror 1.5.82 → 1.5.84

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.
@@ -910,12 +910,52 @@ function setupDataChannel(clientId) {
910
910
  sessionId: message.sessionId,
911
911
  sessionName: newSession.name
912
912
  }));
913
+
914
+ // Send buffered output for this session
915
+ const bufferedOutput = newSession.buffer.getAll();
916
+ if (bufferedOutput) {
917
+ logToFile(`[AGENT] 📤 Sending ${bufferedOutput.length} chars of buffered output for session switch`);
918
+ const success = sendLargeMessage(dataChannel, {
919
+ type: 'output',
920
+ data: bufferedOutput
921
+ }, '[AGENT]');
922
+ if (!success) {
923
+ logToFile('[AGENT] ❌ Failed to send buffered output');
924
+ }
925
+ } else {
926
+ logToFile('[AGENT] ℹ️ No buffered output for switched session');
927
+ }
913
928
  } else {
914
929
  dataChannel.send(JSON.stringify({
915
930
  type: 'error',
916
931
  message: `Session ${message.sessionId} not found`
917
932
  }));
918
933
  }
934
+ } else if (message.type === 'session-create') {
935
+ // Handle new session creation via data channel
936
+ logToFile(`[AGENT] Client ${clientId} creating new session`);
937
+
938
+ const newSessionId = sessionManager.createSession(null, clientId);
939
+
940
+ if (newSessionId) {
941
+ const newSession = sessionManager.getSession(newSessionId);
942
+
943
+ // Send confirmation with updated session list
944
+ dataChannel.send(JSON.stringify({
945
+ type: 'session-created',
946
+ sessionId: newSessionId,
947
+ sessionName: newSession.name,
948
+ availableSessions: sessionManager.getAllSessions()
949
+ }));
950
+
951
+ logToFile(`[AGENT] ✅ New session created: ${newSessionId}`);
952
+ } else {
953
+ dataChannel.send(JSON.stringify({
954
+ type: 'error',
955
+ message: 'Failed to create session - maximum sessions reached'
956
+ }));
957
+ logToFile(`[AGENT] ❌ Failed to create session for client ${clientId}`);
958
+ }
919
959
  }
920
960
  } catch (err) {
921
961
  logToFile(`[AGENT] Error parsing data channel message: ${err.message}`);
@@ -1023,6 +1063,7 @@ function startLocalServer() {
1023
1063
  localWs.send(JSON.stringify({
1024
1064
  type: 'session_created',
1025
1065
  sessionId,
1066
+ sessionName: `Session ${sessionId.slice(0, 8)}`,
1026
1067
  cols: message.cols || 120,
1027
1068
  rows: message.rows || 30
1028
1069
  }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shell-mirror",
3
- "version": "1.5.82",
3
+ "version": "1.5.84",
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": {
@@ -178,27 +178,6 @@
178
178
  background: #1a1a1a;
179
179
  }
180
180
 
181
- .session-tab-status {
182
- width: 8px;
183
- height: 8px;
184
- border-radius: 50%;
185
- flex-shrink: 0;
186
- }
187
-
188
- .session-tab-status.connected {
189
- background: #44ff44;
190
- box-shadow: 0 0 6px rgba(68, 255, 68, 0.5);
191
- }
192
-
193
- .session-tab-status.connecting {
194
- background: #ffaa44;
195
- animation: pulse 1.5s ease-in-out infinite;
196
- }
197
-
198
- .session-tab-status.disconnected {
199
- background: #ff4444;
200
- }
201
-
202
181
  .session-tab-name {
203
182
  overflow: hidden;
204
183
  text-overflow: ellipsis;
@@ -257,23 +236,6 @@
257
236
  }
258
237
  }
259
238
 
260
- /* Dashboard button with status dot */
261
- .dashboard-btn {
262
- position: relative;
263
- }
264
-
265
- .dashboard-btn::before {
266
- content: '';
267
- position: absolute;
268
- top: 4px;
269
- right: 4px;
270
- width: 8px;
271
- height: 8px;
272
- border-radius: 50%;
273
- background: #44ff44;
274
- box-shadow: 0 0 8px rgba(68, 255, 68, 0.5);
275
- }
276
-
277
239
  #terminal {
278
240
  padding: 8px; /* Mac Terminal.app padding */
279
241
  background-color: #000000;
@@ -284,31 +246,6 @@
284
246
  #connect-container { padding: 2em; text-align: center; }
285
247
  #agent-id-input { font-size: 1.2em; padding: 8px; width: 400px; margin-bottom: 1em; }
286
248
  #connect-btn { font-size: 1.2em; padding: 10px 20px; }
287
-
288
- /* Connection Status Indicator */
289
- .connection-status {
290
- width: 8px;
291
- height: 8px;
292
- border-radius: 50%;
293
- background: #ff4444;
294
- margin-right: 4px;
295
- transition: all 0.3s ease;
296
- }
297
-
298
- .connection-status.connected {
299
- background: #44ff44;
300
- box-shadow: 0 0 8px rgba(68, 255, 68, 0.5);
301
- }
302
-
303
- .connection-status.connecting {
304
- background: #ffaa44;
305
- animation: pulse 1.5s ease-in-out infinite alternate;
306
- }
307
-
308
- @keyframes pulse {
309
- from { opacity: 1; }
310
- to { opacity: 0.4; }
311
- }
312
249
 
313
250
  /* Help Modal - Tab Navigation */
314
251
  .help-tabs {
@@ -157,23 +157,12 @@ function updateConnectionStatus(status) {
157
157
  if (!statusElement) return;
158
158
 
159
159
  statusElement.className = 'connection-status';
160
- switch(status) {
161
- case 'connecting':
162
- statusElement.classList.add('connecting');
163
- break;
164
- case 'connected':
165
- statusElement.classList.add('connected');
166
- break;
167
- case 'disconnected':
168
- default:
169
- // Default red styling already applied
170
- break;
171
- }
172
-
173
- // Update tab status indicators
174
- if (currentSession) {
175
- renderTabs();
160
+ if (status === 'connecting') {
161
+ statusElement.classList.add('connecting');
162
+ } else if (status === 'connected') {
163
+ statusElement.classList.add('connected');
176
164
  }
165
+ // else: disconnected (default red)
177
166
  }
178
167
 
179
168
  // Cleanup timer for chunk assembler
@@ -399,8 +388,20 @@ function setupDirectConnection(directWs) {
399
388
 
400
389
  case 'session_created':
401
390
  console.log('[CLIENT] ✅ Direct session created:', data.sessionId);
402
- currentSession = { id: data.sessionId };
391
+ currentSession = {
392
+ id: data.sessionId,
393
+ name: data.sessionName || 'Terminal Session'
394
+ };
395
+
396
+ // Update available sessions
397
+ if (data.availableSessions) {
398
+ availableSessions = data.availableSessions;
399
+ }
400
+
403
401
  updateSessionDisplay();
402
+
403
+ // Save to localStorage
404
+ saveSessionToLocalStorage(AGENT_ID, currentSession);
404
405
  break;
405
406
 
406
407
  case 'output':
@@ -951,9 +952,6 @@ function renderTabs() {
951
952
  return;
952
953
  }
953
954
 
954
- // Get connection status
955
- const connectionStatus = getConnectionStatus();
956
-
957
955
  // Ensure we have sessions to display
958
956
  let sessionsToRender = [];
959
957
 
@@ -968,7 +966,6 @@ function renderTabs() {
968
966
  console.log('[CLIENT] 🎨 Rendering tabs:', {
969
967
  sessionCount: sessionsToRender.length,
970
968
  currentSession: currentSession?.id,
971
- connectionStatus,
972
969
  source: availableSessions?.length > 0 ? 'agent' : 'fallback'
973
970
  });
974
971
 
@@ -985,7 +982,6 @@ function renderTabs() {
985
982
  onclick="switchToSession('${session.id}')"
986
983
  ${isActive ? 'disabled' : ''}
987
984
  title="${displayName}">
988
- <span class="session-tab-status ${connectionStatus}"></span>
989
985
  <span class="session-tab-name">${displayName}</span>
990
986
  </button>
991
987
  `;
@@ -1000,13 +996,27 @@ function renderTabs() {
1000
996
  }
1001
997
 
1002
998
  function getConnectionStatus() {
999
+ // Check direct WebSocket connection
1000
+ if (ws && ws.readyState === WebSocket.OPEN) {
1001
+ return 'connected';
1002
+ }
1003
+
1004
+ // Check WebRTC data channel connection
1003
1005
  if (dataChannel && dataChannel.readyState === 'open') {
1004
1006
  return 'connected';
1005
- } else if (dataChannel && dataChannel.readyState === 'connecting') {
1007
+ }
1008
+
1009
+ // Check connecting states
1010
+ if (ws && ws.readyState === WebSocket.CONNECTING) {
1006
1011
  return 'connecting';
1007
- } else {
1008
- return 'disconnected';
1009
1012
  }
1013
+
1014
+ if (dataChannel && dataChannel.readyState === 'connecting') {
1015
+ return 'connecting';
1016
+ }
1017
+
1018
+ // Not connected
1019
+ return 'disconnected';
1010
1020
  }
1011
1021
 
1012
1022
  function formatLastActivity(timestamp) {
@@ -1036,16 +1046,59 @@ function switchToSession(sessionId) {
1036
1046
  }
1037
1047
 
1038
1048
  function createNewSession() {
1039
- // Navigate to terminal with new session request
1040
- const url = new URL(window.location.href);
1041
- url.searchParams.delete('session'); // Remove session param to create new one
1042
- window.location.href = url.toString();
1049
+ console.log('[CLIENT] 🆕 Creating new session...');
1050
+
1051
+ // Send create session message via active connection
1052
+ const message = {
1053
+ type: 'session-create',
1054
+ cols: term.cols,
1055
+ rows: term.rows
1056
+ };
1057
+
1058
+ if (ws && ws.readyState === WebSocket.OPEN) {
1059
+ // Direct WebSocket connection
1060
+ ws.send(JSON.stringify(message));
1061
+ } else if (dataChannel && dataChannel.readyState === 'open') {
1062
+ // WebRTC data channel connection
1063
+ dataChannel.send(JSON.stringify(message));
1064
+ } else {
1065
+ console.error('[CLIENT] ❌ Cannot create session - no active connection');
1066
+ term.write('\r\n\x1b[31m❌ Cannot create session - not connected\x1b[0m\r\n');
1067
+ }
1043
1068
  }
1044
1069
 
1045
1070
 
1046
1071
  // Handle session-related data channel messages
1047
1072
  function handleSessionMessage(message) {
1048
1073
  switch (message.type) {
1074
+ case 'session-created':
1075
+ console.log('[CLIENT] ✅ New session created:', message.sessionId);
1076
+
1077
+ // Update current session
1078
+ currentSession = {
1079
+ id: message.sessionId,
1080
+ name: message.sessionName || 'Terminal Session'
1081
+ };
1082
+
1083
+ // Update available sessions list
1084
+ if (message.availableSessions) {
1085
+ availableSessions = message.availableSessions;
1086
+ } else {
1087
+ // Add to local list if not provided
1088
+ availableSessions.push(currentSession);
1089
+ }
1090
+
1091
+ // Clear terminal for new session
1092
+ term.clear();
1093
+ term.write(`\r\n\x1b[36m✨ New session created: ${currentSession.name}\x1b[0m\r\n\r\n`);
1094
+
1095
+ // Update UI
1096
+ updateSessionDisplay();
1097
+
1098
+ // Save to localStorage
1099
+ saveSessionToLocalStorage(AGENT_ID, currentSession);
1100
+ break;
1101
+
1049
1102
  case 'session-switched':
1050
1103
  currentSession = {
1051
1104
  id: message.sessionId,
@@ -1054,7 +1107,7 @@ function handleSessionMessage(message) {
1054
1107
  updateSessionDisplay();
1055
1108
  term.clear(); // Clear terminal for new session
1056
1109
  console.log('[CLIENT] ✅ Switched to session:', currentSession);
1057
-
1110
+
1058
1111
  // Save updated session info
1059
1112
  saveSessionToLocalStorage(AGENT_ID, currentSession);
1060
1113
  break;