shell-mirror 1.5.112 → 1.5.114

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.112",
3
+ "version": "1.5.114",
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": {
@@ -239,7 +239,7 @@ body {
239
239
  .agent-name-row {
240
240
  display: flex;
241
241
  align-items: center;
242
- gap: 8px;
242
+ justify-content: space-between;
243
243
  margin-bottom: 4px;
244
244
  }
245
245
 
@@ -247,22 +247,65 @@ body {
247
247
  font-weight: 600;
248
248
  font-size: 1rem;
249
249
  color: var(--text-primary);
250
+ flex: 1;
251
+ min-width: 0;
252
+ }
253
+
254
+ /* Agent Menu Dropdown */
255
+ .agent-menu {
256
+ position: relative;
257
+ flex-shrink: 0;
258
+ margin-left: 8px;
250
259
  }
251
260
 
252
- .btn-delete-agent {
261
+ .btn-agent-menu {
253
262
  background: none;
254
263
  border: none;
264
+ color: var(--text-muted);
265
+ font-size: 1.2rem;
255
266
  cursor: pointer;
256
- font-size: 0.8rem;
257
- opacity: 0.3;
258
- transition: opacity 0.15s ease;
259
- padding: 4px;
260
- color: var(--text-secondary);
267
+ padding: 4px 8px;
268
+ border-radius: 4px;
269
+ transition: all 0.15s ease;
270
+ line-height: 1;
271
+ }
272
+
273
+ .btn-agent-menu:hover {
274
+ color: var(--text-primary);
275
+ background: var(--bg-hover);
276
+ }
277
+
278
+ .agent-menu-dropdown {
279
+ display: none;
280
+ position: absolute;
281
+ right: 0;
282
+ top: calc(100% + 2px);
283
+ background: var(--bg-secondary);
284
+ border: 1px solid var(--border);
285
+ border-radius: 6px;
286
+ min-width: 160px;
287
+ z-index: 100;
288
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
289
+ }
290
+
291
+ .agent-menu-dropdown.show {
292
+ display: block;
261
293
  }
262
294
 
263
- .btn-delete-agent:hover {
264
- opacity: 1;
295
+ .agent-menu-dropdown button {
296
+ width: 100%;
297
+ padding: 10px 14px;
298
+ background: none;
299
+ border: none;
265
300
  color: var(--danger);
301
+ text-align: left;
302
+ cursor: pointer;
303
+ font-size: 0.85rem;
304
+ transition: background 0.15s ease;
305
+ }
306
+
307
+ .agent-menu-dropdown button:hover {
308
+ background: var(--bg-hover);
266
309
  }
267
310
 
268
311
  .agent-status {
@@ -275,7 +318,7 @@ body {
275
318
  }
276
319
 
277
320
  .agent-status.online {
278
- color: var(--success);
321
+ color: var(--text-secondary);
279
322
  background: none;
280
323
  padding: 0;
281
324
  }
@@ -289,7 +332,7 @@ body {
289
332
  }
290
333
 
291
334
  .agent-status.recent {
292
- color: var(--warning);
335
+ color: var(--text-secondary);
293
336
  background: none;
294
337
  padding: 0;
295
338
  }
@@ -303,7 +346,7 @@ body {
303
346
  }
304
347
 
305
348
  .agent-status.offline {
306
- color: var(--danger);
349
+ color: var(--text-secondary);
307
350
  background: none;
308
351
  padding: 0;
309
352
  }
@@ -571,7 +614,7 @@ body {
571
614
  .command-box code {
572
615
  font-family: 'Monaco', 'Menlo', monospace;
573
616
  font-size: 0.85rem;
574
- color: var(--success);
617
+ color: var(--text-primary);
575
618
  flex: 1;
576
619
  }
577
620
 
@@ -76,13 +76,13 @@ class ShellMirrorDashboard {
76
76
  if (this.refreshInterval) {
77
77
  clearInterval(this.refreshInterval);
78
78
  }
79
-
80
- // Refresh agent data every 30 seconds (HTTP polling only)
79
+
80
+ // Refresh agent data every 5 seconds for responsive session updates
81
81
  this.refreshInterval = setInterval(async () => {
82
82
  if (this.isAuthenticated && !this.isRefreshing) {
83
83
  await this.refreshDashboardData();
84
84
  }
85
- }, 30000);
85
+ }, 5000);
86
86
  }
87
87
 
88
88
  async refreshDashboardData() {
@@ -586,9 +586,12 @@ class ShellMirrorDashboard {
586
586
  <div class="agent-info">
587
587
  <div class="agent-name-row">
588
588
  <span class="agent-name">${agent.machineName || agent.agentId}</span>
589
- <button class="btn-delete-agent" onclick="dashboard.deleteAgent('${agent.agentId}')" title="Remove agent">
590
- 🗑️
591
- </button>
589
+ <div class="agent-menu">
590
+ <button class="btn-agent-menu" onclick="event.stopPropagation(); dashboard.toggleAgentMenu('${agent.agentId}')">⋮</button>
591
+ <div class="agent-menu-dropdown" id="agent-menu-${agent.agentId}">
592
+ <button onclick="dashboard.showShutdownConfirm('${agent.agentId}')">Shut down agent</button>
593
+ </div>
594
+ </div>
592
595
  </div>
593
596
  <div class="agent-status ${agent.status}">
594
597
  ${statusText}${sessionCount > 0 ? ` · ${sessionCount} session${sessionCount !== 1 ? 's' : ''}` : ''}
@@ -1065,13 +1068,66 @@ class ShellMirrorDashboard {
1065
1068
  return true;
1066
1069
  }
1067
1070
 
1068
- async deleteAgent(agentId) {
1071
+ toggleAgentMenu(agentId) {
1072
+ // Close all other menus first
1073
+ document.querySelectorAll('.agent-menu-dropdown.show').forEach(el => {
1074
+ el.classList.remove('show');
1075
+ });
1076
+ const menu = document.getElementById(`agent-menu-${agentId}`);
1077
+ if (menu) {
1078
+ menu.classList.toggle('show');
1079
+ }
1080
+ }
1081
+
1082
+ showShutdownConfirm(agentId) {
1083
+ // Close menu
1084
+ document.querySelectorAll('.agent-menu-dropdown.show').forEach(el => {
1085
+ el.classList.remove('show');
1086
+ });
1087
+ this.showShutdownModal(agentId);
1088
+ }
1089
+
1090
+ showShutdownModal(agentId) {
1069
1091
  const agent = this.agents.find(a => a.agentId === agentId);
1070
1092
  const agentName = agent ? (agent.machineName || agentId) : agentId;
1071
1093
 
1072
- if (!confirm(`Are you sure you want to remove "${agentName}" from the dashboard?\n\nThis will unregister the agent. If it's still running, it will re-register on next heartbeat.`)) {
1073
- return;
1074
- }
1094
+ const modalOverlay = document.createElement('div');
1095
+ modalOverlay.className = 'modal-overlay';
1096
+ modalOverlay.id = 'shutdown-modal';
1097
+ modalOverlay.onclick = (e) => {
1098
+ if (e.target === modalOverlay) {
1099
+ document.body.removeChild(modalOverlay);
1100
+ }
1101
+ };
1102
+
1103
+ modalOverlay.innerHTML = `
1104
+ <div class="modal" onclick="event.stopPropagation()">
1105
+ <div class="modal-header">
1106
+ <h3>Shut down agent</h3>
1107
+ <button class="modal-close" onclick="document.getElementById('shutdown-modal').remove()">×</button>
1108
+ </div>
1109
+ <div class="modal-body">
1110
+ <p style="margin-bottom: 12px; color: var(--text-secondary);">
1111
+ Are you sure you want to shut down<br><strong style="color: var(--text-primary); word-break: break-all;">${agentName}</strong>?
1112
+ </p>
1113
+ <p style="font-size: 0.85rem; color: var(--text-muted);">
1114
+ This will unregister the agent from the dashboard. If the agent is still running, it will re-register on next heartbeat.
1115
+ </p>
1116
+ </div>
1117
+ <div class="modal-footer" style="display: flex; justify-content: flex-end; gap: 10px; padding: 16px; border-top: 1px solid var(--border);">
1118
+ <button onclick="document.getElementById('shutdown-modal').remove()" style="padding: 8px 16px; background: var(--bg-tertiary); color: var(--text-secondary); border: 1px solid var(--border); border-radius: 6px; cursor: pointer;">Cancel</button>
1119
+ <button onclick="dashboard.shutdownAgent('${agentId}')" style="padding: 8px 16px; background: var(--danger); color: white; border: none; border-radius: 6px; cursor: pointer;">Shut down</button>
1120
+ </div>
1121
+ </div>
1122
+ `;
1123
+
1124
+ document.body.appendChild(modalOverlay);
1125
+ }
1126
+
1127
+ async shutdownAgent(agentId) {
1128
+ // Close modal
1129
+ const modal = document.getElementById('shutdown-modal');
1130
+ if (modal) modal.remove();
1075
1131
 
1076
1132
  try {
1077
1133
  const response = await fetch('/php-backend/api/delete-agent.php', {
@@ -1083,14 +1139,14 @@ class ShellMirrorDashboard {
1083
1139
 
1084
1140
  const data = await response.json();
1085
1141
  if (data.success) {
1086
- console.log('[DASHBOARD] Agent deleted:', agentId);
1142
+ console.log('[DASHBOARD] Agent shut down:', agentId);
1087
1143
  await this.refreshDashboardData();
1088
1144
  } else {
1089
- alert('Failed to remove agent: ' + (data.message || 'Unknown error'));
1145
+ alert('Failed to shut down agent: ' + (data.message || 'Unknown error'));
1090
1146
  }
1091
1147
  } catch (error) {
1092
- console.error('[DASHBOARD] Delete agent failed:', error);
1093
- alert('Failed to remove agent: ' + error.message);
1148
+ console.error('[DASHBOARD] Shutdown agent failed:', error);
1149
+ alert('Failed to shut down agent: ' + error.message);
1094
1150
  }
1095
1151
  }
1096
1152
 
@@ -1322,4 +1378,13 @@ window.addEventListener('beforeunload', () => {
1322
1378
  if (dashboard && dashboard.refreshInterval) {
1323
1379
  clearInterval(dashboard.refreshInterval);
1324
1380
  }
1381
+ });
1382
+
1383
+ // Close agent menu when clicking outside
1384
+ document.addEventListener('click', (e) => {
1385
+ if (!e.target.closest('.agent-menu')) {
1386
+ document.querySelectorAll('.agent-menu-dropdown.show').forEach(el => {
1387
+ el.classList.remove('show');
1388
+ });
1389
+ }
1325
1390
  });