claude-relay 2.3.0 → 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 +21 -5
- package/bin/cli.js +214 -9
- package/lib/cli-sessions.js +270 -0
- package/lib/config.js +3 -2
- package/lib/daemon.js +45 -1
- package/lib/pages.js +8 -1
- package/lib/project.js +121 -12
- package/lib/public/app.js +411 -87
- package/lib/public/css/base.css +41 -7
- package/lib/public/css/diff.css +6 -6
- package/lib/public/css/filebrowser.css +62 -52
- package/lib/public/css/highlight.css +144 -0
- package/lib/public/css/input.css +11 -9
- package/lib/public/css/menus.css +82 -23
- package/lib/public/css/messages.css +183 -35
- 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 +75 -42
- 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 +84 -23
- package/lib/public/modules/theme.js +622 -0
- package/lib/public/modules/tools.js +247 -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 +45 -3
- 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">
|
|
@@ -139,7 +150,7 @@
|
|
|
139
150
|
</div>
|
|
140
151
|
</div>
|
|
141
152
|
<div id="notif-menu-wrap">
|
|
142
|
-
<button id="notif-btn" title="Notifications"><i data-lucide="
|
|
153
|
+
<button id="notif-btn" title="Notifications"><i data-lucide="bell"></i></button>
|
|
143
154
|
<div id="notif-menu" class="hidden">
|
|
144
155
|
<label class="notif-option" id="notif-push-row">
|
|
145
156
|
<span><i data-lucide="smartphone" style="width:14px;height:14px"></i> Push notifications</span>
|
|
@@ -259,7 +270,7 @@
|
|
|
259
270
|
<span class="context-mini-label" id="context-mini-label">0%</span>
|
|
260
271
|
</div>
|
|
261
272
|
<div id="image-preview-bar"></div>
|
|
262
|
-
<textarea id="input" rows="1" placeholder="Message Claude Code..." enterkeyhint="send"></textarea>
|
|
273
|
+
<textarea id="input" rows="1" placeholder="Message Claude Code..." enterkeyhint="send" dir="auto"></textarea>
|
|
263
274
|
<div id="input-bottom">
|
|
264
275
|
<div id="attach-wrap">
|
|
265
276
|
<button id="attach-btn" type="button" aria-label="Attach"><i data-lucide="plus"></i></button>
|
|
@@ -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
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { iconHtml, refreshIcons } from './icons.js';
|
|
2
2
|
import { closeSidebar } from './sidebar.js';
|
|
3
3
|
import { closeFileViewer } from './filebrowser.js';
|
|
4
|
+
import { copyToClipboard } from './utils.js';
|
|
5
|
+
import { getTerminalTheme } from './theme.js';
|
|
4
6
|
|
|
5
7
|
var ctx;
|
|
6
8
|
var tabs = new Map(); // termId -> { id, title, exited, xterm, fitAddon, bodyEl }
|
|
@@ -11,6 +13,7 @@ var isTouchDevice = "ontouchstart" in window;
|
|
|
11
13
|
var viewportHandler = null;
|
|
12
14
|
var resizeObserver = null;
|
|
13
15
|
var toolbarBound = false;
|
|
16
|
+
var termCtxMenu = null;
|
|
14
17
|
|
|
15
18
|
// --- Init ---
|
|
16
19
|
export function initTerminal(_ctx) {
|
|
@@ -195,28 +198,7 @@ function createXtermForTab(tab) {
|
|
|
195
198
|
cursorBlink: true,
|
|
196
199
|
fontSize: 13,
|
|
197
200
|
fontFamily: "'SF Mono', Menlo, Monaco, 'Courier New', monospace",
|
|
198
|
-
theme:
|
|
199
|
-
background: "#1a1a1a",
|
|
200
|
-
foreground: "#d4d4d4",
|
|
201
|
-
cursor: "#d4d4d4",
|
|
202
|
-
selectionBackground: "rgba(255,255,255,0.2)",
|
|
203
|
-
black: "#1a1a1a",
|
|
204
|
-
red: "#f44747",
|
|
205
|
-
green: "#6a9955",
|
|
206
|
-
yellow: "#d7ba7d",
|
|
207
|
-
blue: "#569cd6",
|
|
208
|
-
magenta: "#c586c0",
|
|
209
|
-
cyan: "#4ec9b0",
|
|
210
|
-
white: "#d4d4d4",
|
|
211
|
-
brightBlack: "#808080",
|
|
212
|
-
brightRed: "#f44747",
|
|
213
|
-
brightGreen: "#6a9955",
|
|
214
|
-
brightYellow: "#d7ba7d",
|
|
215
|
-
brightBlue: "#569cd6",
|
|
216
|
-
brightMagenta: "#c586c0",
|
|
217
|
-
brightCyan: "#4ec9b0",
|
|
218
|
-
brightWhite: "#ffffff",
|
|
219
|
-
},
|
|
201
|
+
theme: getTerminalTheme(),
|
|
220
202
|
});
|
|
221
203
|
|
|
222
204
|
var fitAddon = null;
|
|
@@ -239,6 +221,11 @@ function createXtermForTab(tab) {
|
|
|
239
221
|
}
|
|
240
222
|
});
|
|
241
223
|
|
|
224
|
+
// Right-click context menu
|
|
225
|
+
bodyEl.addEventListener("contextmenu", function (e) {
|
|
226
|
+
showTermCtxMenu(e, tab);
|
|
227
|
+
});
|
|
228
|
+
|
|
242
229
|
tab.xterm = xterm;
|
|
243
230
|
tab.fitAddon = fitAddon;
|
|
244
231
|
tab.bodyEl = bodyEl;
|
|
@@ -319,7 +306,7 @@ function renderTabBar() {
|
|
|
319
306
|
|
|
320
307
|
var closeBtn = document.createElement("button");
|
|
321
308
|
closeBtn.className = "terminal-tab-close";
|
|
322
|
-
closeBtn.innerHTML = '<i data-lucide="
|
|
309
|
+
closeBtn.innerHTML = '<i data-lucide="trash-2" style="width:12px;height:12px"></i>';
|
|
323
310
|
closeBtn.addEventListener("click", function (e) {
|
|
324
311
|
e.stopPropagation();
|
|
325
312
|
closeTab(t.id);
|
|
@@ -527,6 +514,80 @@ export function resetTerminals() {
|
|
|
527
514
|
renderTabBar();
|
|
528
515
|
}
|
|
529
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
|
+
|
|
525
|
+
// --- Terminal context menu ---
|
|
526
|
+
function closeTermCtxMenu() {
|
|
527
|
+
if (termCtxMenu) {
|
|
528
|
+
termCtxMenu.remove();
|
|
529
|
+
termCtxMenu = null;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function showTermCtxMenu(e, tab) {
|
|
534
|
+
e.preventDefault();
|
|
535
|
+
e.stopPropagation();
|
|
536
|
+
closeTermCtxMenu();
|
|
537
|
+
|
|
538
|
+
var menu = document.createElement("div");
|
|
539
|
+
menu.className = "term-ctx-menu";
|
|
540
|
+
|
|
541
|
+
// Copy
|
|
542
|
+
var copyItem = document.createElement("button");
|
|
543
|
+
copyItem.className = "term-ctx-item";
|
|
544
|
+
copyItem.innerHTML = iconHtml("clipboard-copy") + " <span>Copy Terminal</span>";
|
|
545
|
+
copyItem.addEventListener("click", function (ev) {
|
|
546
|
+
ev.stopPropagation();
|
|
547
|
+
closeTermCtxMenu();
|
|
548
|
+
if (!tab.xterm) return;
|
|
549
|
+
tab.xterm.selectAll();
|
|
550
|
+
var text = tab.xterm.getSelection();
|
|
551
|
+
tab.xterm.clearSelection();
|
|
552
|
+
if (text) copyToClipboard(text);
|
|
553
|
+
});
|
|
554
|
+
menu.appendChild(copyItem);
|
|
555
|
+
|
|
556
|
+
// Clear
|
|
557
|
+
var clearItem = document.createElement("button");
|
|
558
|
+
clearItem.className = "term-ctx-item";
|
|
559
|
+
clearItem.innerHTML = iconHtml("trash-2") + " <span>Clear Terminal</span>";
|
|
560
|
+
clearItem.addEventListener("click", function (ev) {
|
|
561
|
+
ev.stopPropagation();
|
|
562
|
+
closeTermCtxMenu();
|
|
563
|
+
if (!tab.xterm) return;
|
|
564
|
+
tab.xterm.clear();
|
|
565
|
+
});
|
|
566
|
+
menu.appendChild(clearItem);
|
|
567
|
+
|
|
568
|
+
// Position at mouse cursor
|
|
569
|
+
menu.style.left = e.clientX + "px";
|
|
570
|
+
menu.style.top = e.clientY + "px";
|
|
571
|
+
document.body.appendChild(menu);
|
|
572
|
+
|
|
573
|
+
// Clamp to viewport
|
|
574
|
+
var rect = menu.getBoundingClientRect();
|
|
575
|
+
if (rect.right > window.innerWidth) {
|
|
576
|
+
menu.style.left = (window.innerWidth - rect.width - 4) + "px";
|
|
577
|
+
}
|
|
578
|
+
if (rect.bottom > window.innerHeight) {
|
|
579
|
+
menu.style.top = (window.innerHeight - rect.height - 4) + "px";
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
termCtxMenu = menu;
|
|
583
|
+
refreshIcons();
|
|
584
|
+
|
|
585
|
+
// Close on outside click (next tick to avoid immediate trigger)
|
|
586
|
+
setTimeout(function () {
|
|
587
|
+
document.addEventListener("click", closeTermCtxMenu, { once: true });
|
|
588
|
+
}, 0);
|
|
589
|
+
}
|
|
590
|
+
|
|
530
591
|
// --- Mobile toolbar ---
|
|
531
592
|
var KEY_MAP = {
|
|
532
593
|
tab: "\t",
|