shell-mirror 1.5.112 → 1.5.113

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.113",
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": {
@@ -249,20 +249,58 @@ body {
249
249
  color: var(--text-primary);
250
250
  }
251
251
 
252
- .btn-delete-agent {
252
+ /* Agent Menu Dropdown */
253
+ .agent-menu {
254
+ position: relative;
255
+ }
256
+
257
+ .btn-agent-menu {
253
258
  background: none;
254
259
  border: none;
260
+ color: var(--text-muted);
261
+ font-size: 1.2rem;
255
262
  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);
263
+ padding: 4px 8px;
264
+ border-radius: 4px;
265
+ transition: all 0.15s ease;
261
266
  }
262
267
 
263
- .btn-delete-agent:hover {
264
- opacity: 1;
268
+ .btn-agent-menu:hover {
269
+ color: var(--text-primary);
270
+ background: var(--bg-hover);
271
+ }
272
+
273
+ .agent-menu-dropdown {
274
+ display: none;
275
+ position: absolute;
276
+ right: 0;
277
+ top: calc(100% + 4px);
278
+ background: var(--bg-secondary);
279
+ border: 1px solid var(--border);
280
+ border-radius: 6px;
281
+ min-width: 160px;
282
+ z-index: 100;
283
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
284
+ }
285
+
286
+ .agent-menu-dropdown.show {
287
+ display: block;
288
+ }
289
+
290
+ .agent-menu-dropdown button {
291
+ width: 100%;
292
+ padding: 10px 14px;
293
+ background: none;
294
+ border: none;
265
295
  color: var(--danger);
296
+ text-align: left;
297
+ cursor: pointer;
298
+ font-size: 0.85rem;
299
+ transition: background 0.15s ease;
300
+ }
301
+
302
+ .agent-menu-dropdown button:hover {
303
+ background: var(--bg-hover);
266
304
  }
267
305
 
268
306
  .agent-status {
@@ -275,7 +313,7 @@ body {
275
313
  }
276
314
 
277
315
  .agent-status.online {
278
- color: var(--success);
316
+ color: var(--text-secondary);
279
317
  background: none;
280
318
  padding: 0;
281
319
  }
@@ -289,7 +327,7 @@ body {
289
327
  }
290
328
 
291
329
  .agent-status.recent {
292
- color: var(--warning);
330
+ color: var(--text-secondary);
293
331
  background: none;
294
332
  padding: 0;
295
333
  }
@@ -303,7 +341,7 @@ body {
303
341
  }
304
342
 
305
343
  .agent-status.offline {
306
- color: var(--danger);
344
+ color: var(--text-secondary);
307
345
  background: none;
308
346
  padding: 0;
309
347
  }
@@ -571,7 +609,7 @@ body {
571
609
  .command-box code {
572
610
  font-family: 'Monaco', 'Menlo', monospace;
573
611
  font-size: 0.85rem;
574
- color: var(--success);
612
+ color: var(--text-primary);
575
613
  flex: 1;
576
614
  }
577
615
 
@@ -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 <strong style="color: var(--text-primary);">${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
  });