claude-relay 2.3.1 → 2.4.0
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/README.md +20 -5
- package/bin/cli.js +206 -8
- package/lib/cli-sessions.js +270 -0
- package/lib/daemon.js +40 -0
- package/lib/project.js +121 -1
- package/lib/public/app.js +385 -76
- package/lib/public/css/base.css +41 -7
- package/lib/public/css/diff.css +6 -6
- package/lib/public/css/filebrowser.css +34 -54
- package/lib/public/css/highlight.css +144 -0
- package/lib/public/css/input.css +9 -9
- package/lib/public/css/menus.css +82 -23
- package/lib/public/css/messages.css +178 -34
- package/lib/public/css/overlays.css +166 -50
- package/lib/public/css/rewind.css +17 -17
- package/lib/public/css/sidebar.css +210 -137
- package/lib/public/index.html +73 -40
- package/lib/public/modules/filebrowser.js +2 -1
- package/lib/public/modules/markdown.js +10 -10
- package/lib/public/modules/notifications.js +38 -1
- package/lib/public/modules/sidebar.js +109 -31
- package/lib/public/modules/terminal.js +11 -23
- package/lib/public/modules/theme.js +622 -0
- package/lib/public/modules/tools.js +245 -4
- package/lib/public/modules/utils.js +21 -5
- package/lib/public/style.css +1 -0
- package/lib/sdk-bridge.js +95 -0
- package/lib/server.js +41 -0
- package/lib/sessions.js +16 -3
- package/lib/themes/ayu-light.json +9 -0
- package/lib/themes/catppuccin-latte.json +9 -0
- package/lib/themes/catppuccin-mocha.json +9 -0
- package/lib/themes/claude-light.json +9 -0
- package/lib/themes/claude.json +9 -0
- package/lib/themes/dracula.json +9 -0
- package/lib/themes/everforest-light.json +9 -0
- package/lib/themes/everforest.json +9 -0
- package/lib/themes/github-light.json +9 -0
- package/lib/themes/gruvbox-dark.json +9 -0
- package/lib/themes/gruvbox-light.json +9 -0
- package/lib/themes/monokai.json +9 -0
- package/lib/themes/nord-light.json +9 -0
- package/lib/themes/nord.json +9 -0
- package/lib/themes/one-dark.json +9 -0
- package/lib/themes/one-light.json +9 -0
- package/lib/themes/rose-pine-dawn.json +9 -0
- package/lib/themes/rose-pine.json +9 -0
- package/lib/themes/solarized-dark.json +9 -0
- package/lib/themes/solarized-light.json +9 -0
- package/lib/themes/tokyo-night-light.json +9 -0
- package/lib/themes/tokyo-night.json +9 -0
- package/package.json +2 -1
package/lib/public/index.html
CHANGED
|
@@ -13,9 +13,11 @@
|
|
|
13
13
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
14
14
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
15
15
|
<link href="https://fonts.googleapis.com/css2?family=Inter:opsz,wght@14..32,400;14..32,500;14..32,600&family=Styrene+A+Web:wght@400;500&display=swap" rel="stylesheet">
|
|
16
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11/build/styles/github-dark-dimmed.min.css">
|
|
17
16
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/file-icons-js@1/css/style.css">
|
|
18
17
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@xterm/xterm@5/css/xterm.min.css">
|
|
18
|
+
<script>
|
|
19
|
+
(function(){try{var k="claude-relay-theme-vars",v=localStorage.getItem(k);if(!v)return;var o=JSON.parse(v),r=document.documentElement,p;for(p in o)r.style.setProperty(p,o[p]);var vt=localStorage.getItem(k.replace("-vars","-variant"));if(vt==="light"){r.classList.add("light-theme");r.classList.remove("dark-theme")}else{r.classList.add("dark-theme");r.classList.remove("light-theme")}var m=document.querySelector('meta[name="theme-color"]');if(m&&o["--bg"])m.setAttribute("content",o["--bg"])}catch(e){}})();
|
|
20
|
+
</script>
|
|
19
21
|
<link rel="stylesheet" href="style.css">
|
|
20
22
|
</head>
|
|
21
23
|
<body>
|
|
@@ -28,51 +30,56 @@
|
|
|
28
30
|
<button id="sidebar-toggle-btn" title="Close sidebar"><i data-lucide="panel-left-close"></i></button>
|
|
29
31
|
</div>
|
|
30
32
|
<nav id="sidebar-nav">
|
|
31
|
-
<div id="project-
|
|
32
|
-
<
|
|
33
|
-
<
|
|
34
|
-
|
|
35
|
-
<div class="ps-current">
|
|
36
|
-
<span id="project-switcher-name"></span>
|
|
37
|
-
<span id="project-switcher-count" class="ps-count"></span>
|
|
38
|
-
</div>
|
|
39
|
-
</div>
|
|
40
|
-
<i data-lucide="chevron-down" class="ps-chevron"></i>
|
|
41
|
-
</button>
|
|
42
|
-
<div id="project-dropdown" class="hidden">
|
|
43
|
-
<div class="project-dropdown-header">Projects</div>
|
|
44
|
-
<div id="project-dropdown-list"></div>
|
|
45
|
-
<div class="project-dropdown-footer">
|
|
46
|
-
<button id="project-dropdown-add" type="button"><i data-lucide="plus"></i> <span>Add project</span></button>
|
|
47
|
-
</div>
|
|
33
|
+
<div id="project-list-section">
|
|
34
|
+
<div id="project-list-header">
|
|
35
|
+
<span class="project-list-title">Projects</span>
|
|
36
|
+
<button id="project-list-add" type="button" title="Add project"><i data-lucide="plus"></i></button>
|
|
48
37
|
</div>
|
|
38
|
+
<div id="project-list"></div>
|
|
49
39
|
</div>
|
|
50
40
|
<div id="project-hint" class="hidden">
|
|
51
41
|
<span class="project-hint-text">Run <code>npx claude-relay</code> in other directories to add more projects.</span>
|
|
52
42
|
<button id="project-hint-dismiss" type="button" aria-label="Dismiss"><i data-lucide="x"></i></button>
|
|
53
43
|
</div>
|
|
54
44
|
</nav>
|
|
55
|
-
<div id="sidebar-
|
|
45
|
+
<div id="sidebar-tools">
|
|
46
|
+
<div class="sidebar-section-header">
|
|
47
|
+
<span class="sidebar-section-title">Tools</span>
|
|
48
|
+
</div>
|
|
56
49
|
<div id="session-actions">
|
|
57
|
-
<button id="
|
|
58
|
-
<button id="resume-session-btn"><i data-lucide="link"></i> <span>Resume with ID</span></button>
|
|
50
|
+
<button id="resume-session-btn" title="Resume a CLI session"><i data-lucide="terminal"></i> <span>Resume CLI</span></button>
|
|
59
51
|
<button id="file-browser-btn"><i data-lucide="folder-tree"></i> <span>File browser</span></button>
|
|
60
52
|
<button id="terminal-sidebar-btn"><i data-lucide="square-terminal"></i> <span>Terminal</span></button>
|
|
61
53
|
</div>
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
54
|
+
</div>
|
|
55
|
+
<div id="sidebar-sessions-header">
|
|
56
|
+
<div id="sessions-header-content">
|
|
57
|
+
<div class="session-list-header">
|
|
58
|
+
<span>Sessions</span>
|
|
59
|
+
<div class="session-list-header-actions">
|
|
60
|
+
<button id="new-session-btn" type="button" title="New session"><i data-lucide="plus"></i></button>
|
|
61
|
+
<button id="search-session-btn" type="button" title="Search sessions"><i data-lucide="search"></i></button>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
<div id="session-search" class="hidden">
|
|
65
|
+
<input id="session-search-input" type="text" placeholder="Search sessions..." autocomplete="off" spellcheck="false" />
|
|
66
|
+
<button id="session-search-clear" type="button" aria-label="Clear search"><i data-lucide="x"></i></button>
|
|
67
|
+
</div>
|
|
65
68
|
</div>
|
|
66
|
-
<div id="
|
|
67
|
-
<
|
|
69
|
+
<div id="files-header-content" class="hidden">
|
|
70
|
+
<div class="session-list-header">
|
|
71
|
+
<span>File Browser</span>
|
|
72
|
+
<div class="session-list-header-actions">
|
|
73
|
+
<button id="file-panel-refresh" type="button" title="Refresh file tree"><i data-lucide="refresh-cw"></i></button>
|
|
74
|
+
<button id="file-panel-close" type="button" title="Close file browser"><i data-lucide="x"></i></button>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
68
77
|
</div>
|
|
78
|
+
</div>
|
|
79
|
+
<div id="sidebar-panel-sessions" class="sidebar-panel">
|
|
69
80
|
<div id="session-list"></div>
|
|
70
81
|
</div>
|
|
71
82
|
<div id="sidebar-panel-files" class="sidebar-panel hidden">
|
|
72
|
-
<div id="file-panel-header">
|
|
73
|
-
<button id="file-panel-back" type="button"><i data-lucide="arrow-left"></i> <span>Sessions</span></button>
|
|
74
|
-
<button id="file-panel-refresh" type="button" title="Refresh file tree"><i data-lucide="refresh-cw"></i></button>
|
|
75
|
-
</div>
|
|
76
83
|
<div id="file-tree"></div>
|
|
77
84
|
</div>
|
|
78
85
|
<div id="sidebar-footer">
|
|
@@ -82,8 +89,11 @@
|
|
|
82
89
|
</button>
|
|
83
90
|
<div id="sidebar-footer-menu" class="hidden">
|
|
84
91
|
<a class="sidebar-menu-item" href="https://github.com/chadbyte/claude-relay" target="_blank" rel="noopener">
|
|
85
|
-
<i data-lucide="
|
|
92
|
+
<i data-lucide="star"></i> <span>Star on GitHub</span>
|
|
86
93
|
</a>
|
|
94
|
+
<button class="sidebar-menu-item" id="footer-theme">
|
|
95
|
+
<i data-lucide="palette"></i> <span>Theme</span>
|
|
96
|
+
</button>
|
|
87
97
|
<button class="sidebar-menu-item" id="footer-status">
|
|
88
98
|
<i data-lucide="activity"></i> <span>Status</span>
|
|
89
99
|
</button>
|
|
@@ -112,7 +122,8 @@
|
|
|
112
122
|
<div id="header-left">
|
|
113
123
|
<button id="sidebar-expand-btn" title="Open sidebar"><i data-lucide="panel-left-open"></i></button>
|
|
114
124
|
<button id="hamburger-btn" aria-label="Toggle sidebar"><i data-lucide="menu"></i></button>
|
|
115
|
-
<span class="
|
|
125
|
+
<span class="header-title" id="header-title">Connecting...</span>
|
|
126
|
+
<button id="header-rename-btn" type="button" title="Rename session"><i data-lucide="pencil"></i></button>
|
|
116
127
|
</div>
|
|
117
128
|
<div class="status">
|
|
118
129
|
<div id="debug-menu-wrap" class="hidden">
|
|
@@ -293,9 +304,9 @@
|
|
|
293
304
|
<div id="terminal-container" class="hidden">
|
|
294
305
|
<div class="terminal-header">
|
|
295
306
|
<div id="terminal-tabs" class="terminal-tabs"></div>
|
|
307
|
+
<button class="terminal-new-tab-btn" id="terminal-new-tab" title="New tab"><i data-lucide="plus"></i></button>
|
|
296
308
|
<div class="terminal-header-actions">
|
|
297
|
-
<button class="file-viewer-btn" id="terminal-
|
|
298
|
-
<button class="file-viewer-btn" id="terminal-close" title="Close panel"><i data-lucide="x"></i></button>
|
|
309
|
+
<button class="file-viewer-btn" id="terminal-close" title="Hide panel"><i data-lucide="chevron-up"></i></button>
|
|
299
310
|
</div>
|
|
300
311
|
</div>
|
|
301
312
|
<div id="terminal-toolbar" class="hidden">
|
|
@@ -369,16 +380,20 @@
|
|
|
369
380
|
|
|
370
381
|
<div id="resume-modal" class="hidden">
|
|
371
382
|
<div class="confirm-backdrop"></div>
|
|
372
|
-
<div class="confirm-dialog">
|
|
383
|
+
<div class="confirm-dialog resume-picker-dialog">
|
|
373
384
|
<div class="resume-modal-title">Resume CLI session</div>
|
|
374
|
-
<div class="resume-
|
|
375
|
-
<
|
|
376
|
-
|
|
377
|
-
|
|
385
|
+
<div class="resume-picker-body">
|
|
386
|
+
<div id="resume-picker-loading" class="resume-picker-loading">
|
|
387
|
+
<div class="resume-picker-spinner"></div>
|
|
388
|
+
<span>Loading sessions...</span>
|
|
389
|
+
</div>
|
|
390
|
+
<div id="resume-picker-empty" class="resume-picker-empty hidden">
|
|
391
|
+
No CLI sessions found for this project.
|
|
392
|
+
</div>
|
|
393
|
+
<div id="resume-picker-list" class="resume-picker-list hidden"></div>
|
|
378
394
|
</div>
|
|
379
395
|
<div class="confirm-actions">
|
|
380
396
|
<button class="confirm-btn confirm-cancel" id="resume-cancel">Cancel</button>
|
|
381
|
-
<button class="confirm-btn confirm-ok" id="resume-ok">Resume</button>
|
|
382
397
|
</div>
|
|
383
398
|
</div>
|
|
384
399
|
</div>
|
|
@@ -414,6 +429,24 @@
|
|
|
414
429
|
</div>
|
|
415
430
|
</div>
|
|
416
431
|
|
|
432
|
+
<div id="add-project-modal" class="hidden">
|
|
433
|
+
<div class="confirm-backdrop"></div>
|
|
434
|
+
<div class="confirm-dialog add-project-dialog">
|
|
435
|
+
<div class="add-project-title">Add project</div>
|
|
436
|
+
<div class="add-project-body">
|
|
437
|
+
<div class="add-project-input-wrap">
|
|
438
|
+
<input type="text" id="add-project-input" placeholder="/" autocomplete="off" spellcheck="false">
|
|
439
|
+
<div id="add-project-suggestions" class="hidden"></div>
|
|
440
|
+
</div>
|
|
441
|
+
<div id="add-project-error" class="hidden"></div>
|
|
442
|
+
</div>
|
|
443
|
+
<div class="confirm-actions">
|
|
444
|
+
<button class="confirm-btn confirm-cancel" id="add-project-cancel">Cancel</button>
|
|
445
|
+
<button class="confirm-btn confirm-ok" id="add-project-ok">Add</button>
|
|
446
|
+
</div>
|
|
447
|
+
</div>
|
|
448
|
+
</div>
|
|
449
|
+
|
|
417
450
|
<script src="https://cdn.jsdelivr.net/npm/marked@14/marked.min.js"></script>
|
|
418
451
|
<script src="https://cdn.jsdelivr.net/npm/dompurify@3/dist/purify.min.js"></script>
|
|
419
452
|
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11/build/highlight.min.js"></script>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { iconHtml, refreshIcons } from './icons.js';
|
|
2
2
|
import { escapeHtml, copyToClipboard } from './utils.js';
|
|
3
|
-
import { renderMarkdown, highlightCodeBlocks } from './markdown.js';
|
|
3
|
+
import { renderMarkdown, highlightCodeBlocks, renderMermaidBlocks } from './markdown.js';
|
|
4
4
|
import { closeSidebar } from './sidebar.js';
|
|
5
5
|
import { renderUnifiedDiff, renderSplitDiff } from './diff.js';
|
|
6
6
|
|
|
@@ -123,6 +123,7 @@ function renderBody() {
|
|
|
123
123
|
}
|
|
124
124
|
}
|
|
125
125
|
highlightCodeBlocks(bodyEl);
|
|
126
|
+
renderMermaidBlocks(bodyEl);
|
|
126
127
|
renderBtn.classList.add("active");
|
|
127
128
|
renderBtn.title = "Show raw";
|
|
128
129
|
} else {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { copyToClipboard } from './utils.js';
|
|
2
2
|
import { refreshIcons } from './icons.js';
|
|
3
|
+
import { getMermaidThemeVars } from './theme.js';
|
|
3
4
|
|
|
4
5
|
// Initialize markdown parser
|
|
5
6
|
marked.use({ gfm: true, breaks: false });
|
|
@@ -8,18 +9,17 @@ marked.use({ gfm: true, breaks: false });
|
|
|
8
9
|
mermaid.initialize({
|
|
9
10
|
startOnLoad: false,
|
|
10
11
|
theme: "dark",
|
|
11
|
-
themeVariables:
|
|
12
|
-
darkMode: true,
|
|
13
|
-
background: "#1E1D1A",
|
|
14
|
-
primaryColor: "#DA7756",
|
|
15
|
-
primaryTextColor: "#E8E5DE",
|
|
16
|
-
primaryBorderColor: "#3E3C37",
|
|
17
|
-
lineColor: "#908B81",
|
|
18
|
-
secondaryColor: "#35332F",
|
|
19
|
-
tertiaryColor: "#2F2E2B"
|
|
20
|
-
}
|
|
12
|
+
themeVariables: getMermaidThemeVars()
|
|
21
13
|
});
|
|
22
14
|
|
|
15
|
+
export function updateMermaidTheme(vars) {
|
|
16
|
+
mermaid.initialize({
|
|
17
|
+
startOnLoad: false,
|
|
18
|
+
theme: "dark",
|
|
19
|
+
themeVariables: vars
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
23
|
var mermaidIdCounter = 0;
|
|
24
24
|
|
|
25
25
|
export function renderMarkdown(text) {
|
|
@@ -262,6 +262,15 @@ export function initNotifications(_ctx) {
|
|
|
262
262
|
}
|
|
263
263
|
});
|
|
264
264
|
|
|
265
|
+
// --- iOS Safari detection ---
|
|
266
|
+
var isIOSSafari = (function () {
|
|
267
|
+
var ua = navigator.userAgent;
|
|
268
|
+
var isIOS = /iPad|iPhone|iPod/.test(ua) || (navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1);
|
|
269
|
+
var isSafari = isIOS && /Safari/.test(ua) && !/CriOS|FxiOS|OPiOS|EdgiOS/.test(ua);
|
|
270
|
+
return isSafari;
|
|
271
|
+
})();
|
|
272
|
+
var isStandalone = window.matchMedia("(display-mode:standalone)").matches || navigator.standalone;
|
|
273
|
+
|
|
265
274
|
// --- Browser notifications ---
|
|
266
275
|
notifPermission = ("Notification" in window) ? Notification.permission : "denied";
|
|
267
276
|
notifAlertEnabled = localStorage.getItem("notif-alert") !== "0";
|
|
@@ -334,6 +343,7 @@ export function initNotifications(_ctx) {
|
|
|
334
343
|
if (ua.indexOf("Firefox") !== -1) url = "about:preferences#privacy";
|
|
335
344
|
else if (ua.indexOf("Edg/") !== -1) url = "edge://settings/content/notifications";
|
|
336
345
|
else if (ua.indexOf("Arc") !== -1) url = "arc://settings/content/notifications";
|
|
346
|
+
else if (isIOSSafari) url = "Settings > Safari > Notifications";
|
|
337
347
|
notifSettingsUrl.textContent = url;
|
|
338
348
|
})();
|
|
339
349
|
|
|
@@ -375,7 +385,34 @@ export function initNotifications(_ctx) {
|
|
|
375
385
|
var pushAvailable = ("serviceWorker" in navigator) &&
|
|
376
386
|
(location.protocol === "https:" || location.hostname === "localhost");
|
|
377
387
|
|
|
378
|
-
|
|
388
|
+
// On iOS Safari (not in PWA mode), replace the push toggle with an info hint
|
|
389
|
+
if (isIOSSafari && !isStandalone) {
|
|
390
|
+
var infoRow = document.createElement("div");
|
|
391
|
+
infoRow.className = "notif-option notif-ios-info";
|
|
392
|
+
infoRow.style.display = "flex";
|
|
393
|
+
infoRow.innerHTML =
|
|
394
|
+
'<span><i data-lucide="smartphone" style="width:14px;height:14px"></i> Push notifications</span>' +
|
|
395
|
+
'<button class="notif-ios-info-btn" title="Info"><i data-lucide="info" style="width:14px;height:14px"></i></button>';
|
|
396
|
+
notifPushRow.parentNode.replaceChild(infoRow, notifPushRow);
|
|
397
|
+
|
|
398
|
+
var iosHint = document.createElement("div");
|
|
399
|
+
iosHint.id = "notif-ios-hint";
|
|
400
|
+
iosHint.className = "hidden";
|
|
401
|
+
iosHint.innerHTML =
|
|
402
|
+
'To enable push notifications on iOS, tap <strong>Share</strong> ' +
|
|
403
|
+
'<i data-lucide="share" style="width:12px;height:12px;vertical-align:-2px"></i> ' +
|
|
404
|
+
'then <strong>Add to Home Screen</strong>. ' +
|
|
405
|
+
'Push notifications work inside the installed app.';
|
|
406
|
+
infoRow.parentNode.insertBefore(iosHint, infoRow.nextSibling);
|
|
407
|
+
|
|
408
|
+
infoRow.querySelector(".notif-ios-info-btn").addEventListener("click", function (e) {
|
|
409
|
+
e.preventDefault();
|
|
410
|
+
e.stopPropagation();
|
|
411
|
+
iosHint.classList.toggle("hidden");
|
|
412
|
+
refreshIcons();
|
|
413
|
+
});
|
|
414
|
+
refreshIcons();
|
|
415
|
+
} else if (pushAvailable) {
|
|
379
416
|
notifPushRow.style.display = "flex";
|
|
380
417
|
}
|
|
381
418
|
|
|
@@ -240,6 +240,9 @@ export function updatePageTitle() {
|
|
|
240
240
|
var sessionTitle = "";
|
|
241
241
|
var activeItem = ctx.sessionListEl.querySelector(".session-item.active .session-item-text");
|
|
242
242
|
if (activeItem) sessionTitle = activeItem.textContent;
|
|
243
|
+
if (ctx.headerTitleEl) {
|
|
244
|
+
ctx.headerTitleEl.textContent = sessionTitle || ctx.projectName || "Claude Relay";
|
|
245
|
+
}
|
|
243
246
|
if (ctx.projectName && sessionTitle) {
|
|
244
247
|
document.title = sessionTitle + " - " + ctx.projectName;
|
|
245
248
|
} else if (ctx.projectName) {
|
|
@@ -298,6 +301,7 @@ export function initSidebar(_ctx) {
|
|
|
298
301
|
var searchBtn = ctx.$("search-session-btn");
|
|
299
302
|
var searchBox = ctx.$("session-search");
|
|
300
303
|
var searchInput = ctx.$("session-search-input");
|
|
304
|
+
var searchClear = ctx.$("session-search-clear");
|
|
301
305
|
|
|
302
306
|
function openSearch() {
|
|
303
307
|
searchBox.classList.remove("hidden");
|
|
@@ -326,6 +330,12 @@ export function initSidebar(_ctx) {
|
|
|
326
330
|
}
|
|
327
331
|
});
|
|
328
332
|
|
|
333
|
+
if (searchClear) {
|
|
334
|
+
searchClear.addEventListener("click", function () {
|
|
335
|
+
closeSearch();
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
329
339
|
searchInput.addEventListener("input", function () {
|
|
330
340
|
searchQuery = searchInput.value.trim();
|
|
331
341
|
if (searchDebounce) clearTimeout(searchDebounce);
|
|
@@ -349,71 +359,139 @@ export function initSidebar(_ctx) {
|
|
|
349
359
|
}
|
|
350
360
|
});
|
|
351
361
|
|
|
352
|
-
// --- Resume session
|
|
362
|
+
// --- Resume session picker ---
|
|
353
363
|
var resumeModal = ctx.$("resume-modal");
|
|
354
|
-
var resumeInput = ctx.$("resume-session-input");
|
|
355
|
-
var resumeOk = ctx.$("resume-ok");
|
|
356
364
|
var resumeCancel = ctx.$("resume-cancel");
|
|
365
|
+
var pickerLoading = ctx.$("resume-picker-loading");
|
|
366
|
+
var pickerEmpty = ctx.$("resume-picker-empty");
|
|
367
|
+
var pickerList = ctx.$("resume-picker-list");
|
|
357
368
|
|
|
358
369
|
function openResumeModal() {
|
|
359
370
|
resumeModal.classList.remove("hidden");
|
|
360
|
-
|
|
361
|
-
|
|
371
|
+
pickerLoading.classList.remove("hidden");
|
|
372
|
+
pickerEmpty.classList.add("hidden");
|
|
373
|
+
pickerList.classList.add("hidden");
|
|
374
|
+
pickerList.innerHTML = "";
|
|
375
|
+
if (ctx.ws && ctx.connected) {
|
|
376
|
+
ctx.ws.send(JSON.stringify({ type: "list_cli_sessions" }));
|
|
377
|
+
}
|
|
362
378
|
}
|
|
363
379
|
|
|
364
380
|
function closeResumeModal() {
|
|
365
381
|
resumeModal.classList.add("hidden");
|
|
366
|
-
resumeInput.value = "";
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
function submitResume() {
|
|
370
|
-
var val = resumeInput.value.trim();
|
|
371
|
-
if (!val) return;
|
|
372
|
-
if (ctx.ws && ctx.connected) {
|
|
373
|
-
ctx.ws.send(JSON.stringify({ type: "resume_session", cliSessionId: val }));
|
|
374
|
-
}
|
|
375
|
-
closeResumeModal();
|
|
376
|
-
closeSidebar();
|
|
377
382
|
}
|
|
378
383
|
|
|
379
384
|
ctx.resumeSessionBtn.addEventListener("click", openResumeModal);
|
|
380
|
-
resumeOk.addEventListener("click", submitResume);
|
|
381
385
|
resumeCancel.addEventListener("click", closeResumeModal);
|
|
382
386
|
resumeModal.querySelector(".confirm-backdrop").addEventListener("click", closeResumeModal);
|
|
383
387
|
|
|
384
|
-
resumeInput.addEventListener("keydown", function (e) {
|
|
385
|
-
if (e.key === "Enter") {
|
|
386
|
-
e.preventDefault();
|
|
387
|
-
submitResume();
|
|
388
|
-
}
|
|
389
|
-
if (e.key === "Escape") {
|
|
390
|
-
e.preventDefault();
|
|
391
|
-
closeResumeModal();
|
|
392
|
-
}
|
|
393
|
-
});
|
|
394
|
-
|
|
395
388
|
// --- File browser panel switch ---
|
|
396
389
|
var fileBrowserBtn = ctx.$("file-browser-btn");
|
|
397
390
|
var sessionsPanel = ctx.$("sidebar-panel-sessions");
|
|
398
391
|
var filesPanel = ctx.$("sidebar-panel-files");
|
|
399
|
-
var
|
|
392
|
+
var sessionsHeaderContent = ctx.$("sessions-header-content");
|
|
393
|
+
var filesHeaderContent = ctx.$("files-header-content");
|
|
394
|
+
var filePanelClose = ctx.$("file-panel-close");
|
|
400
395
|
|
|
401
396
|
function showFilesPanel() {
|
|
402
397
|
sessionsPanel.classList.add("hidden");
|
|
403
398
|
filesPanel.classList.remove("hidden");
|
|
399
|
+
if (sessionsHeaderContent) sessionsHeaderContent.classList.add("hidden");
|
|
400
|
+
if (filesHeaderContent) filesHeaderContent.classList.remove("hidden");
|
|
404
401
|
if (ctx.onFilesTabOpen) ctx.onFilesTabOpen();
|
|
405
402
|
}
|
|
406
403
|
|
|
407
404
|
function showSessionsPanel() {
|
|
408
405
|
filesPanel.classList.add("hidden");
|
|
409
406
|
sessionsPanel.classList.remove("hidden");
|
|
407
|
+
if (filesHeaderContent) filesHeaderContent.classList.add("hidden");
|
|
408
|
+
if (sessionsHeaderContent) sessionsHeaderContent.classList.remove("hidden");
|
|
410
409
|
}
|
|
411
410
|
|
|
412
411
|
if (fileBrowserBtn) {
|
|
413
412
|
fileBrowserBtn.addEventListener("click", showFilesPanel);
|
|
414
413
|
}
|
|
415
|
-
if (
|
|
416
|
-
|
|
414
|
+
if (filePanelClose) {
|
|
415
|
+
filePanelClose.addEventListener("click", showSessionsPanel);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// --- CLI session picker ---
|
|
420
|
+
function relativeTime(isoString) {
|
|
421
|
+
if (!isoString) return "";
|
|
422
|
+
var ms = Date.now() - new Date(isoString).getTime();
|
|
423
|
+
var sec = Math.floor(ms / 1000);
|
|
424
|
+
if (sec < 60) return "just now";
|
|
425
|
+
var min = Math.floor(sec / 60);
|
|
426
|
+
if (min < 60) return min + "m ago";
|
|
427
|
+
var hr = Math.floor(min / 60);
|
|
428
|
+
if (hr < 24) return hr + "h ago";
|
|
429
|
+
var days = Math.floor(hr / 24);
|
|
430
|
+
if (days < 30) return days + "d ago";
|
|
431
|
+
return new Date(isoString).toLocaleDateString();
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
export function populateCliSessionList(sessions) {
|
|
435
|
+
var pickerLoading = ctx.$("resume-picker-loading");
|
|
436
|
+
var pickerEmpty = ctx.$("resume-picker-empty");
|
|
437
|
+
var pickerList = ctx.$("resume-picker-list");
|
|
438
|
+
if (!pickerLoading || !pickerList) return;
|
|
439
|
+
|
|
440
|
+
pickerLoading.classList.add("hidden");
|
|
441
|
+
|
|
442
|
+
if (!sessions || sessions.length === 0) {
|
|
443
|
+
pickerEmpty.classList.remove("hidden");
|
|
444
|
+
pickerList.classList.add("hidden");
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
pickerEmpty.classList.add("hidden");
|
|
449
|
+
pickerList.classList.remove("hidden");
|
|
450
|
+
pickerList.innerHTML = "";
|
|
451
|
+
|
|
452
|
+
for (var i = 0; i < sessions.length; i++) {
|
|
453
|
+
var s = sessions[i];
|
|
454
|
+
var item = document.createElement("div");
|
|
455
|
+
item.className = "cli-session-item";
|
|
456
|
+
|
|
457
|
+
var title = document.createElement("div");
|
|
458
|
+
title.className = "cli-session-title";
|
|
459
|
+
title.textContent = s.firstPrompt || "Untitled session";
|
|
460
|
+
item.appendChild(title);
|
|
461
|
+
|
|
462
|
+
var meta = document.createElement("div");
|
|
463
|
+
meta.className = "cli-session-meta";
|
|
464
|
+
if (s.lastActivity) {
|
|
465
|
+
var time = document.createElement("span");
|
|
466
|
+
time.textContent = relativeTime(s.lastActivity);
|
|
467
|
+
meta.appendChild(time);
|
|
468
|
+
}
|
|
469
|
+
if (s.model) {
|
|
470
|
+
var model = document.createElement("span");
|
|
471
|
+
model.className = "badge";
|
|
472
|
+
model.textContent = s.model;
|
|
473
|
+
meta.appendChild(model);
|
|
474
|
+
}
|
|
475
|
+
if (s.gitBranch) {
|
|
476
|
+
var branch = document.createElement("span");
|
|
477
|
+
branch.className = "badge";
|
|
478
|
+
branch.textContent = s.gitBranch;
|
|
479
|
+
meta.appendChild(branch);
|
|
480
|
+
}
|
|
481
|
+
item.appendChild(meta);
|
|
482
|
+
|
|
483
|
+
(function (sessionId) {
|
|
484
|
+
item.addEventListener("click", function () {
|
|
485
|
+
if (ctx.ws && ctx.connected) {
|
|
486
|
+
ctx.ws.send(JSON.stringify({ type: "resume_session", cliSessionId: sessionId }));
|
|
487
|
+
}
|
|
488
|
+
var modal = ctx.$("resume-modal");
|
|
489
|
+
if (modal) modal.classList.add("hidden");
|
|
490
|
+
closeSidebar();
|
|
491
|
+
});
|
|
492
|
+
})(s.sessionId);
|
|
493
|
+
|
|
494
|
+
pickerList.appendChild(item);
|
|
417
495
|
}
|
|
418
496
|
}
|
|
419
497
|
|
|
@@ -2,6 +2,7 @@ import { iconHtml, refreshIcons } from './icons.js';
|
|
|
2
2
|
import { closeSidebar } from './sidebar.js';
|
|
3
3
|
import { closeFileViewer } from './filebrowser.js';
|
|
4
4
|
import { copyToClipboard } from './utils.js';
|
|
5
|
+
import { getTerminalTheme } from './theme.js';
|
|
5
6
|
|
|
6
7
|
var ctx;
|
|
7
8
|
var tabs = new Map(); // termId -> { id, title, exited, xterm, fitAddon, bodyEl }
|
|
@@ -197,28 +198,7 @@ function createXtermForTab(tab) {
|
|
|
197
198
|
cursorBlink: true,
|
|
198
199
|
fontSize: 13,
|
|
199
200
|
fontFamily: "'SF Mono', Menlo, Monaco, 'Courier New', monospace",
|
|
200
|
-
theme:
|
|
201
|
-
background: "#1a1a1a",
|
|
202
|
-
foreground: "#d4d4d4",
|
|
203
|
-
cursor: "#d4d4d4",
|
|
204
|
-
selectionBackground: "rgba(255,255,255,0.2)",
|
|
205
|
-
black: "#1a1a1a",
|
|
206
|
-
red: "#f44747",
|
|
207
|
-
green: "#6a9955",
|
|
208
|
-
yellow: "#d7ba7d",
|
|
209
|
-
blue: "#569cd6",
|
|
210
|
-
magenta: "#c586c0",
|
|
211
|
-
cyan: "#4ec9b0",
|
|
212
|
-
white: "#d4d4d4",
|
|
213
|
-
brightBlack: "#808080",
|
|
214
|
-
brightRed: "#f44747",
|
|
215
|
-
brightGreen: "#6a9955",
|
|
216
|
-
brightYellow: "#d7ba7d",
|
|
217
|
-
brightBlue: "#569cd6",
|
|
218
|
-
brightMagenta: "#c586c0",
|
|
219
|
-
brightCyan: "#4ec9b0",
|
|
220
|
-
brightWhite: "#ffffff",
|
|
221
|
-
},
|
|
201
|
+
theme: getTerminalTheme(),
|
|
222
202
|
});
|
|
223
203
|
|
|
224
204
|
var fitAddon = null;
|
|
@@ -326,7 +306,7 @@ function renderTabBar() {
|
|
|
326
306
|
|
|
327
307
|
var closeBtn = document.createElement("button");
|
|
328
308
|
closeBtn.className = "terminal-tab-close";
|
|
329
|
-
closeBtn.innerHTML = '<i data-lucide="
|
|
309
|
+
closeBtn.innerHTML = '<i data-lucide="trash-2" style="width:12px;height:12px"></i>';
|
|
330
310
|
closeBtn.addEventListener("click", function (e) {
|
|
331
311
|
e.stopPropagation();
|
|
332
312
|
closeTab(t.id);
|
|
@@ -534,6 +514,14 @@ export function resetTerminals() {
|
|
|
534
514
|
renderTabBar();
|
|
535
515
|
}
|
|
536
516
|
|
|
517
|
+
export function setTerminalTheme(xtermTheme) {
|
|
518
|
+
for (var tab of tabs.values()) {
|
|
519
|
+
if (tab.xterm) {
|
|
520
|
+
tab.xterm.options.theme = xtermTheme;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
537
525
|
// --- Terminal context menu ---
|
|
538
526
|
function closeTermCtxMenu() {
|
|
539
527
|
if (termCtxMenu) {
|