shell-mirror 1.5.99 → 1.5.105
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/mac-agent/agent.js +4 -1
- package/package.json +1 -1
- package/public/app/dashboard.css +75 -0
- package/public/app/dashboard.js +33 -16
- package/public/app/terminal.html +2 -5
- package/public/app/terminal.js +46 -7
package/mac-agent/agent.js
CHANGED
|
@@ -169,11 +169,14 @@ class SessionManager {
|
|
|
169
169
|
this.maxSessions = 10;
|
|
170
170
|
this.defaultSessionTimeout = 24 * 60 * 60 * 1000; // 24 hours
|
|
171
171
|
this.clientSessions = {}; // Maps clientId to sessionId
|
|
172
|
+
this.sessionCounter = 0; // Incrementing counter for unique session names (never resets)
|
|
172
173
|
}
|
|
173
174
|
|
|
174
175
|
createSession(sessionName = null, clientId = null) {
|
|
175
176
|
const sessionId = `ses_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
176
|
-
|
|
177
|
+
// Use incrementing counter for unique names (doesn't reuse after deletion)
|
|
178
|
+
this.sessionCounter++;
|
|
179
|
+
const name = sessionName || `Session ${this.sessionCounter}`;
|
|
177
180
|
|
|
178
181
|
logToFile(`[SESSION] Creating new session: ${sessionId} (${name})`);
|
|
179
182
|
|
package/package.json
CHANGED
package/public/app/dashboard.css
CHANGED
|
@@ -225,6 +225,81 @@ body {
|
|
|
225
225
|
transform: scale(1.05);
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
+
/* Text Action Buttons (No Emoji) */
|
|
229
|
+
.btn-text-action {
|
|
230
|
+
background: #f8f9fa;
|
|
231
|
+
border: 1px solid #dee2e6;
|
|
232
|
+
border-radius: 6px;
|
|
233
|
+
color: #495057;
|
|
234
|
+
padding: 6px 12px;
|
|
235
|
+
font-size: 0.8rem;
|
|
236
|
+
font-weight: 500;
|
|
237
|
+
cursor: pointer;
|
|
238
|
+
transition: all 0.15s ease;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.btn-text-action:hover:not(:disabled) {
|
|
242
|
+
background: #e9ecef;
|
|
243
|
+
border-color: #ced4da;
|
|
244
|
+
color: #212529;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.btn-text-action.loading {
|
|
248
|
+
opacity: 0.7;
|
|
249
|
+
cursor: not-allowed;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.btn-text-action.btn-cleanup {
|
|
253
|
+
background: #fff5f5;
|
|
254
|
+
border-color: #feb2b2;
|
|
255
|
+
color: #c53030;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.btn-text-action.btn-cleanup:hover:not(:disabled) {
|
|
259
|
+
background: #fed7d7;
|
|
260
|
+
border-color: #fc8181;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/* Refresh Time in Card Header */
|
|
264
|
+
.refresh-time {
|
|
265
|
+
font-size: 0.75rem;
|
|
266
|
+
color: #718096;
|
|
267
|
+
font-weight: 400;
|
|
268
|
+
margin-left: 8px;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/* Animated Loading Dots */
|
|
272
|
+
.loading-dots {
|
|
273
|
+
display: inline-flex;
|
|
274
|
+
gap: 2px;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.loading-dots span {
|
|
278
|
+
animation: loadingDot 1.4s infinite;
|
|
279
|
+
opacity: 0.2;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.loading-dots span:nth-child(1) {
|
|
283
|
+
animation-delay: 0s;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.loading-dots span:nth-child(2) {
|
|
287
|
+
animation-delay: 0.2s;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.loading-dots span:nth-child(3) {
|
|
291
|
+
animation-delay: 0.4s;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
@keyframes loadingDot {
|
|
295
|
+
0%, 80%, 100% {
|
|
296
|
+
opacity: 0.2;
|
|
297
|
+
}
|
|
298
|
+
40% {
|
|
299
|
+
opacity: 1;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
228
303
|
.connection-status {
|
|
229
304
|
font-size: 0.8rem;
|
|
230
305
|
font-weight: 500;
|
package/public/app/dashboard.js
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
// Dashboard functionality for Shell Mirror
|
|
2
|
+
|
|
3
|
+
// Session color palette (matches terminal.js)
|
|
4
|
+
const SESSION_COLORS = [
|
|
5
|
+
{ border: '#2196f3', text: '#1565c0' }, // Blue
|
|
6
|
+
{ border: '#4caf50', text: '#2e7d32' }, // Green
|
|
7
|
+
{ border: '#ff9800', text: '#e65100' }, // Orange
|
|
8
|
+
{ border: '#9c27b0', text: '#6a1b9a' }, // Purple
|
|
9
|
+
{ border: '#00bcd4', text: '#00838f' }, // Teal
|
|
10
|
+
{ border: '#e91e63', text: '#ad1457' }, // Pink
|
|
11
|
+
];
|
|
12
|
+
|
|
2
13
|
class ShellMirrorDashboard {
|
|
3
14
|
constructor() {
|
|
4
15
|
this.isAuthenticated = false;
|
|
@@ -513,10 +524,9 @@ class ShellMirrorDashboard {
|
|
|
513
524
|
document.getElementById('user-section').innerHTML = `
|
|
514
525
|
<div class="dashboard-controls">
|
|
515
526
|
<span id="connection-status" class="connection-status" style="display: none;"></span>
|
|
516
|
-
<span id="refresh-status" class="refresh-status">Loading...</span>
|
|
517
527
|
</div>
|
|
518
528
|
<button class="help-button" onclick="dashboard.showAgentInstructions()" title="How to Use">
|
|
519
|
-
|
|
529
|
+
How to Use
|
|
520
530
|
</button>
|
|
521
531
|
<div class="user-info">
|
|
522
532
|
<span class="user-name">${this.user?.name || this.user?.email || 'User'}</span>
|
|
@@ -576,16 +586,17 @@ class ShellMirrorDashboard {
|
|
|
576
586
|
? this.formatPreciseLastSeen(agent.timeSinceLastSeen)
|
|
577
587
|
: this.formatLastSeen(agent.lastSeen);
|
|
578
588
|
|
|
579
|
-
// Build inline session list
|
|
580
|
-
const sessionsHtml = sessions.map(session => {
|
|
589
|
+
// Build inline session list with colors
|
|
590
|
+
const sessionsHtml = sessions.map((session, index) => {
|
|
581
591
|
const sessionStatus = session.status === 'active' ? 'active' : 'crashed';
|
|
582
592
|
const activityText = this.formatLastActivity(session.lastActivity);
|
|
593
|
+
const color = SESSION_COLORS[index % SESSION_COLORS.length];
|
|
583
594
|
return `
|
|
584
|
-
<div class="inline-session-item">
|
|
585
|
-
<span class="session-status-dot ${
|
|
586
|
-
<span class="session-name">${session.name}</span>
|
|
595
|
+
<div class="inline-session-item" style="border-left: 3px solid ${color.border};">
|
|
596
|
+
<span class="session-status-dot" style="background-color: ${color.border};"></span>
|
|
597
|
+
<span class="session-name" style="color: ${color.text};">${session.name}</span>
|
|
587
598
|
<span class="session-activity">${activityText}</span>
|
|
588
|
-
<button class="btn-session-connect" onclick="dashboard.connectToSession('${agent.agentId}', '${session.id}')">
|
|
599
|
+
<button class="btn-session-connect" onclick="dashboard.connectToSession('${agent.agentId}', '${session.id}')" style="background-color: ${color.border};">
|
|
589
600
|
Connect
|
|
590
601
|
</button>
|
|
591
602
|
</div>
|
|
@@ -632,19 +643,25 @@ class ShellMirrorDashboard {
|
|
|
632
643
|
const offlineAgents = this.agents.filter(agent => agent.status === 'offline');
|
|
633
644
|
const showCleanup = offlineAgents.length > 0;
|
|
634
645
|
|
|
646
|
+
// Format last refresh time
|
|
647
|
+
const refreshTime = this.lastRefresh
|
|
648
|
+
? new Date(this.lastRefresh).toLocaleTimeString()
|
|
649
|
+
: '<span class="loading-dots"><span>.</span><span>.</span><span>.</span></span>';
|
|
650
|
+
|
|
635
651
|
return `
|
|
636
652
|
<div class="dashboard-card">
|
|
637
653
|
<div class="card-header">
|
|
638
654
|
<div class="card-title-section">
|
|
639
|
-
<h2
|
|
640
|
-
<span class="agent-count">${agentCount}
|
|
655
|
+
<h2>Active Agents</h2>
|
|
656
|
+
<span class="agent-count">${agentCount}</span>
|
|
657
|
+
<span class="refresh-time">${this.lastRefresh ? 'Updated ' : ''}${refreshTime}</span>
|
|
641
658
|
</div>
|
|
642
659
|
<div class="agent-actions-header">
|
|
643
|
-
<button id="refresh-btn" class="
|
|
644
|
-
|
|
660
|
+
<button id="refresh-btn" class="btn-text-action" onclick="dashboard.manualRefresh()" title="Refresh agents">
|
|
661
|
+
Refresh
|
|
645
662
|
</button>
|
|
646
|
-
${showCleanup ? `<button class="
|
|
647
|
-
|
|
663
|
+
${showCleanup ? `<button class="btn-text-action btn-cleanup" onclick="dashboard.cleanupOfflineAgents()" title="Remove offline agents">
|
|
664
|
+
Clean
|
|
648
665
|
</button>` : ''}
|
|
649
666
|
</div>
|
|
650
667
|
</div>
|
|
@@ -659,7 +676,7 @@ class ShellMirrorDashboard {
|
|
|
659
676
|
return `
|
|
660
677
|
<div class="empty-agent-state">
|
|
661
678
|
<div class="empty-state-header">
|
|
662
|
-
<h3
|
|
679
|
+
<h3>Get Started with Shell Mirror</h3>
|
|
663
680
|
<p>Connect your Mac in 2 simple steps:</p>
|
|
664
681
|
</div>
|
|
665
682
|
|
|
@@ -688,7 +705,7 @@ class ShellMirrorDashboard {
|
|
|
688
705
|
</div>
|
|
689
706
|
|
|
690
707
|
<div class="empty-state-footer">
|
|
691
|
-
<p
|
|
708
|
+
<p>Your agent will appear here once connected</p>
|
|
692
709
|
</div>
|
|
693
710
|
</div>
|
|
694
711
|
`;
|
package/public/app/terminal.html
CHANGED
|
@@ -172,11 +172,8 @@
|
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
.session-tab.active {
|
|
175
|
-
|
|
175
|
+
/* Colors set by inline styles from JavaScript */
|
|
176
176
|
font-weight: 600;
|
|
177
|
-
border-bottom-color: #667eea;
|
|
178
|
-
background: #333;
|
|
179
|
-
box-shadow: inset 0 -3px 0 #667eea;
|
|
180
177
|
}
|
|
181
178
|
|
|
182
179
|
.session-tab-btn {
|
|
@@ -579,6 +576,6 @@
|
|
|
579
576
|
}
|
|
580
577
|
</script>
|
|
581
578
|
|
|
582
|
-
<script src="/app/terminal.js?v=1.5.
|
|
579
|
+
<script src="/app/terminal.js?v=1.5.90"></script>
|
|
583
580
|
</body>
|
|
584
581
|
</html>
|
package/public/app/terminal.js
CHANGED
|
@@ -407,9 +407,10 @@ function setupDirectConnection(directWs) {
|
|
|
407
407
|
}
|
|
408
408
|
}
|
|
409
409
|
|
|
410
|
-
// Clear terminal and show success message
|
|
410
|
+
// Clear terminal and show success message with session color
|
|
411
411
|
term.clear();
|
|
412
|
-
|
|
412
|
+
const sessionColor = getSessionColor(currentSession.id);
|
|
413
|
+
term.write(`\r\n\x1b[38;2;${sessionColor.ansi}m✨ New session created: ${currentSession.name}\x1b[0m\r\n\r\n`);
|
|
413
414
|
|
|
414
415
|
// Update URL with session ID so refresh reconnects to same session
|
|
415
416
|
updateUrlWithSession(data.sessionId);
|
|
@@ -965,6 +966,28 @@ function updateSessionDisplay() {
|
|
|
965
966
|
}
|
|
966
967
|
}
|
|
967
968
|
|
|
969
|
+
// Session tab color palette (fixed colors by creation order)
|
|
970
|
+
const SESSION_TAB_COLORS = [
|
|
971
|
+
{ bg: '#e3f2fd', border: '#2196f3', text: '#1565c0', muted: '#5a9fd4', ansi: '33;150;243' }, // Blue
|
|
972
|
+
{ bg: '#e8f5e9', border: '#4caf50', text: '#2e7d32', muted: '#6fbf73', ansi: '76;175;80' }, // Green
|
|
973
|
+
{ bg: '#fff3e0', border: '#ff9800', text: '#e65100', muted: '#ffb74d', ansi: '255;152;0' }, // Orange
|
|
974
|
+
{ bg: '#f3e5f5', border: '#9c27b0', text: '#6a1b9a', muted: '#ba68c8', ansi: '156;39;176' }, // Purple
|
|
975
|
+
{ bg: '#e0f7fa', border: '#00bcd4', text: '#00838f', muted: '#4dd0e1', ansi: '0;188;212' }, // Teal
|
|
976
|
+
{ bg: '#fce4ec', border: '#e91e63', text: '#ad1457', muted: '#f06292', ansi: '233;30;99' }, // Pink
|
|
977
|
+
];
|
|
978
|
+
|
|
979
|
+
// Track color assignments by session ID (persists across renders)
|
|
980
|
+
const sessionColorMap = {};
|
|
981
|
+
let nextColorIndex = 0;
|
|
982
|
+
|
|
983
|
+
function getSessionColor(sessionId) {
|
|
984
|
+
if (!sessionColorMap[sessionId]) {
|
|
985
|
+
sessionColorMap[sessionId] = nextColorIndex;
|
|
986
|
+
nextColorIndex = (nextColorIndex + 1) % SESSION_TAB_COLORS.length;
|
|
987
|
+
}
|
|
988
|
+
return SESSION_TAB_COLORS[sessionColorMap[sessionId]];
|
|
989
|
+
}
|
|
990
|
+
|
|
968
991
|
function renderTabs() {
|
|
969
992
|
const tabBar = document.getElementById('session-tab-bar');
|
|
970
993
|
if (!tabBar) {
|
|
@@ -996,15 +1019,24 @@ function renderTabs() {
|
|
|
996
1019
|
tabsHTML = sessionsToRender.map(session => {
|
|
997
1020
|
const isActive = currentSession && session.id === currentSession.id;
|
|
998
1021
|
const displayName = session.name || 'Terminal Session';
|
|
1022
|
+
const color = getSessionColor(session.id);
|
|
1023
|
+
|
|
1024
|
+
// Active tabs get full color, inactive tabs get muted version of their color
|
|
1025
|
+
const tabStyle = isActive
|
|
1026
|
+
? `background: ${color.bg}; border-color: ${color.border}; border-bottom: 3px solid ${color.border};`
|
|
1027
|
+
: `background: rgba(255,255,255,0.05); border-color: transparent; border-bottom: 3px solid ${color.muted}40;`;
|
|
1028
|
+
const textStyle = isActive
|
|
1029
|
+
? `color: ${color.text}; font-weight: 600;`
|
|
1030
|
+
: `color: ${color.muted};`;
|
|
999
1031
|
|
|
1000
1032
|
return `
|
|
1001
|
-
<div class="session-tab ${isActive ? 'active' : ''}" title="${displayName}">
|
|
1033
|
+
<div class="session-tab ${isActive ? 'active' : ''}" style="${tabStyle}" title="${displayName}" data-color-index="${sessionColorMap[session.id]}">
|
|
1002
1034
|
<button class="session-tab-btn"
|
|
1003
1035
|
onclick="switchToSession('${session.id}')"
|
|
1004
|
-
${
|
|
1036
|
+
style="${textStyle}">
|
|
1005
1037
|
<span class="session-tab-name">${displayName}</span>
|
|
1006
1038
|
</button>
|
|
1007
|
-
<button class="session-tab-close" onclick="closeSession('${session.id}', event)" title="Close session">×</button>
|
|
1039
|
+
<button class="session-tab-close" onclick="closeSession('${session.id}', event)" title="Close session" style="color: ${isActive ? color.text : color.muted}">×</button>
|
|
1008
1040
|
</div>
|
|
1009
1041
|
`;
|
|
1010
1042
|
}).join('');
|
|
@@ -1180,9 +1212,10 @@ function handleSessionMessage(message) {
|
|
|
1180
1212
|
availableSessions.push(currentSession);
|
|
1181
1213
|
}
|
|
1182
1214
|
|
|
1183
|
-
// Clear terminal for new session
|
|
1215
|
+
// Clear terminal for new session with session color
|
|
1184
1216
|
term.clear();
|
|
1185
|
-
|
|
1217
|
+
const sessionColor = getSessionColor(currentSession.id);
|
|
1218
|
+
term.write(`\r\n\x1b[38;2;${sessionColor.ansi}m✨ New session created: ${currentSession.name}\x1b[0m\r\n\r\n`);
|
|
1186
1219
|
|
|
1187
1220
|
// Update URL with session ID so refresh reconnects to same session
|
|
1188
1221
|
updateUrlWithSession(message.sessionId);
|
|
@@ -1192,6 +1225,9 @@ function handleSessionMessage(message) {
|
|
|
1192
1225
|
|
|
1193
1226
|
// Save to localStorage
|
|
1194
1227
|
saveSessionToLocalStorage(AGENT_ID, currentSession);
|
|
1228
|
+
|
|
1229
|
+
// Focus terminal for keyboard input
|
|
1230
|
+
term.focus();
|
|
1195
1231
|
break;
|
|
1196
1232
|
|
|
1197
1233
|
case 'session-switched':
|
|
@@ -1208,6 +1244,9 @@ function handleSessionMessage(message) {
|
|
|
1208
1244
|
|
|
1209
1245
|
// Save updated session info
|
|
1210
1246
|
saveSessionToLocalStorage(AGENT_ID, currentSession);
|
|
1247
|
+
|
|
1248
|
+
// Focus terminal for keyboard input
|
|
1249
|
+
term.focus();
|
|
1211
1250
|
break;
|
|
1212
1251
|
case 'session-ended':
|
|
1213
1252
|
term.write(`\r\n\x1b[31m❌ Session ended: ${message.reason}\x1b[0m\r\n`);
|