claude-relay 2.4.2 → 2.5.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.
Files changed (75) hide show
  1. package/bin/cli.js +1 -2350
  2. package/package.json +7 -42
  3. package/LICENSE +0 -21
  4. package/README.md +0 -281
  5. package/lib/cli-sessions.js +0 -270
  6. package/lib/config.js +0 -222
  7. package/lib/daemon.js +0 -423
  8. package/lib/ipc.js +0 -112
  9. package/lib/pages.js +0 -714
  10. package/lib/project.js +0 -1224
  11. package/lib/public/app.js +0 -2157
  12. package/lib/public/apple-touch-icon.png +0 -0
  13. package/lib/public/css/base.css +0 -145
  14. package/lib/public/css/diff.css +0 -128
  15. package/lib/public/css/filebrowser.css +0 -1076
  16. package/lib/public/css/highlight.css +0 -144
  17. package/lib/public/css/input.css +0 -512
  18. package/lib/public/css/menus.css +0 -683
  19. package/lib/public/css/messages.css +0 -1159
  20. package/lib/public/css/overlays.css +0 -731
  21. package/lib/public/css/rewind.css +0 -529
  22. package/lib/public/css/sidebar.css +0 -794
  23. package/lib/public/favicon.svg +0 -26
  24. package/lib/public/icon-192.png +0 -0
  25. package/lib/public/icon-512.png +0 -0
  26. package/lib/public/icon-mono.svg +0 -19
  27. package/lib/public/index.html +0 -460
  28. package/lib/public/manifest.json +0 -27
  29. package/lib/public/modules/diff.js +0 -398
  30. package/lib/public/modules/events.js +0 -21
  31. package/lib/public/modules/filebrowser.js +0 -1375
  32. package/lib/public/modules/fileicons.js +0 -172
  33. package/lib/public/modules/icons.js +0 -54
  34. package/lib/public/modules/input.js +0 -578
  35. package/lib/public/modules/markdown.js +0 -149
  36. package/lib/public/modules/notifications.js +0 -643
  37. package/lib/public/modules/qrcode.js +0 -70
  38. package/lib/public/modules/rewind.js +0 -334
  39. package/lib/public/modules/sidebar.js +0 -628
  40. package/lib/public/modules/state.js +0 -3
  41. package/lib/public/modules/terminal.js +0 -658
  42. package/lib/public/modules/theme.js +0 -622
  43. package/lib/public/modules/tools.js +0 -1410
  44. package/lib/public/modules/utils.js +0 -56
  45. package/lib/public/style.css +0 -10
  46. package/lib/public/sw.js +0 -75
  47. package/lib/push.js +0 -125
  48. package/lib/sdk-bridge.js +0 -771
  49. package/lib/server.js +0 -577
  50. package/lib/sessions.js +0 -402
  51. package/lib/terminal-manager.js +0 -187
  52. package/lib/terminal.js +0 -24
  53. package/lib/themes/ayu-light.json +0 -9
  54. package/lib/themes/catppuccin-latte.json +0 -9
  55. package/lib/themes/catppuccin-mocha.json +0 -9
  56. package/lib/themes/claude-light.json +0 -9
  57. package/lib/themes/claude.json +0 -9
  58. package/lib/themes/dracula.json +0 -9
  59. package/lib/themes/everforest-light.json +0 -9
  60. package/lib/themes/everforest.json +0 -9
  61. package/lib/themes/github-light.json +0 -9
  62. package/lib/themes/gruvbox-dark.json +0 -9
  63. package/lib/themes/gruvbox-light.json +0 -9
  64. package/lib/themes/monokai.json +0 -9
  65. package/lib/themes/nord-light.json +0 -9
  66. package/lib/themes/nord.json +0 -9
  67. package/lib/themes/one-dark.json +0 -9
  68. package/lib/themes/one-light.json +0 -9
  69. package/lib/themes/rose-pine-dawn.json +0 -9
  70. package/lib/themes/rose-pine.json +0 -9
  71. package/lib/themes/solarized-dark.json +0 -9
  72. package/lib/themes/solarized-light.json +0 -9
  73. package/lib/themes/tokyo-night-light.json +0 -9
  74. package/lib/themes/tokyo-night.json +0 -9
  75. package/lib/updater.js +0 -96
@@ -1,658 +0,0 @@
1
- import { iconHtml, refreshIcons } from './icons.js';
2
- import { closeSidebar } from './sidebar.js';
3
- import { closeFileViewer } from './filebrowser.js';
4
- import { copyToClipboard } from './utils.js';
5
- import { getTerminalTheme } from './theme.js';
6
-
7
- var ctx;
8
- var tabs = new Map(); // termId -> { id, title, exited, xterm, fitAddon, bodyEl }
9
- var activeTabId = null;
10
- var isOpen = false;
11
- var ctrlActive = false;
12
- var isTouchDevice = "ontouchstart" in window;
13
- var viewportHandler = null;
14
- var resizeObserver = null;
15
- var toolbarBound = false;
16
- var termCtxMenu = null;
17
-
18
- // --- Init ---
19
- export function initTerminal(_ctx) {
20
- ctx = _ctx;
21
-
22
- // Close panel button
23
- document.getElementById("terminal-close").addEventListener("click", function () {
24
- closeTerminal();
25
- });
26
-
27
- // Header toggle button
28
- var toggleBtn = document.getElementById("terminal-toggle-btn");
29
- if (toggleBtn) {
30
- toggleBtn.addEventListener("click", function () {
31
- if (isOpen && !ctx.terminalContainerEl.classList.contains("hidden")) {
32
- closeTerminal();
33
- } else {
34
- openTerminal();
35
- }
36
- });
37
- }
38
-
39
- // Sidebar terminal button
40
- var sidebarTermBtn = document.getElementById("terminal-sidebar-btn");
41
- if (sidebarTermBtn) {
42
- sidebarTermBtn.addEventListener("click", function () {
43
- closeSidebar();
44
- openTerminal();
45
- });
46
- }
47
-
48
- // New tab button
49
- var newTabBtn = document.getElementById("terminal-new-tab");
50
- if (newTabBtn) {
51
- newTabBtn.addEventListener("click", function () {
52
- createNewTab();
53
- });
54
- }
55
- }
56
-
57
- // --- Open terminal panel ---
58
- export function openTerminal() {
59
- var container = ctx.terminalContainerEl;
60
-
61
- // Hide file viewer if open (also unwatches)
62
- closeFileViewer();
63
-
64
- container.classList.remove("hidden");
65
- isOpen = true;
66
-
67
- // If no tabs exist, create one
68
- if (tabs.size === 0) {
69
- createNewTab();
70
- return; // createNewTab will handle the rest via term_created
71
- }
72
-
73
- // Attach to active tab (or first available)
74
- if (!activeTabId || !tabs.has(activeTabId)) {
75
- activeTabId = tabs.keys().next().value;
76
- }
77
-
78
- activateTab(activeTabId);
79
-
80
- // Mobile: close sidebar
81
- if (window.innerWidth <= 768) {
82
- closeSidebar();
83
- }
84
-
85
- refreshIcons();
86
- }
87
-
88
- // --- Close terminal panel (hide, detach, but keep PTYs alive) ---
89
- export function closeTerminal() {
90
- var container = ctx.terminalContainerEl;
91
- container.classList.add("hidden");
92
-
93
- // Detach from active tab
94
- if (activeTabId && ctx.ws && ctx.connected) {
95
- ctx.ws.send(JSON.stringify({ type: "term_detach", id: activeTabId }));
96
- }
97
-
98
- cleanupListeners();
99
-
100
- // Hide toolbar
101
- var toolbar = document.getElementById("terminal-toolbar");
102
- if (toolbar) {
103
- toolbar.classList.add("hidden");
104
- var ctrlBtn = toolbar.querySelector("[data-key='ctrl']");
105
- if (ctrlBtn) ctrlBtn.classList.remove("active");
106
- }
107
- ctrlActive = false;
108
-
109
- isOpen = false;
110
- }
111
-
112
- // --- Create new tab ---
113
- function createNewTab() {
114
- if (!ctx.ws || !ctx.connected) return;
115
-
116
- // Get current terminal body dimensions for cols/rows
117
- var cols = 80;
118
- var rows = 24;
119
- if (activeTabId && tabs.has(activeTabId)) {
120
- var activeTab = tabs.get(activeTabId);
121
- if (activeTab.xterm) {
122
- cols = activeTab.xterm.cols || 80;
123
- rows = activeTab.xterm.rows || 24;
124
- }
125
- }
126
-
127
- ctx.ws.send(JSON.stringify({ type: "term_create", cols: cols, rows: rows }));
128
- }
129
-
130
- // --- Close a tab (kill PTY) ---
131
- function closeTab(termId) {
132
- if (!ctx.ws || !ctx.connected) return;
133
- ctx.ws.send(JSON.stringify({ type: "term_close", id: termId }));
134
- }
135
-
136
- // --- Activate a tab (show xterm, attach) ---
137
- function activateTab(termId) {
138
- var tab = tabs.get(termId);
139
- if (!tab) return;
140
-
141
- // Detach from old active
142
- if (activeTabId && activeTabId !== termId && ctx.ws && ctx.connected) {
143
- ctx.ws.send(JSON.stringify({ type: "term_detach", id: activeTabId }));
144
- }
145
-
146
- // Hide all tab bodies
147
- for (var t of tabs.values()) {
148
- if (t.bodyEl) t.bodyEl.style.display = "none";
149
- }
150
-
151
- activeTabId = termId;
152
-
153
- // Lazy-create xterm instance
154
- if (!tab.xterm) {
155
- createXtermForTab(tab);
156
- }
157
-
158
- // Show this tab's body
159
- if (tab.bodyEl) tab.bodyEl.style.display = "";
160
-
161
- // Attach to server
162
- if (ctx.ws && ctx.connected) {
163
- ctx.ws.send(JSON.stringify({ type: "term_attach", id: termId }));
164
- }
165
-
166
- // Fit and focus
167
- setupListeners();
168
- fitTerminal();
169
-
170
- if (tab.xterm) {
171
- tab.xterm.focus();
172
- }
173
-
174
- // Show toolbar on touch devices
175
- var toolbar = document.getElementById("terminal-toolbar");
176
- if (toolbar && isTouchDevice) {
177
- toolbar.classList.remove("hidden");
178
- initToolbar(toolbar);
179
- }
180
-
181
- // Mobile viewport handling
182
- if (window.visualViewport && !viewportHandler) {
183
- viewportHandler = function () {
184
- ctx.terminalContainerEl.style.height = window.visualViewport.height + "px";
185
- fitTerminal();
186
- };
187
- window.visualViewport.addEventListener("resize", viewportHandler);
188
- }
189
-
190
- renderTabBar();
191
- }
192
-
193
- // --- Create xterm.js instance for a tab ---
194
- function createXtermForTab(tab) {
195
- if (typeof Terminal === "undefined") return;
196
-
197
- var xterm = new Terminal({
198
- cursorBlink: true,
199
- fontSize: 13,
200
- fontFamily: "'SF Mono', Menlo, Monaco, 'Courier New', monospace",
201
- theme: getTerminalTheme(),
202
- });
203
-
204
- var fitAddon = null;
205
- if (typeof FitAddon !== "undefined") {
206
- fitAddon = new FitAddon.FitAddon();
207
- xterm.loadAddon(fitAddon);
208
- }
209
-
210
- // Create a container div for this tab's terminal
211
- var bodyEl = document.createElement("div");
212
- bodyEl.className = "terminal-tab-body";
213
- ctx.terminalBodyEl.appendChild(bodyEl);
214
-
215
- xterm.open(bodyEl);
216
-
217
- // Route input to server
218
- xterm.onData(function (data) {
219
- if (ctx.ws && ctx.connected) {
220
- ctx.ws.send(JSON.stringify({ type: "term_input", id: tab.id, data: data }));
221
- }
222
- });
223
-
224
- // Right-click context menu
225
- bodyEl.addEventListener("contextmenu", function (e) {
226
- showTermCtxMenu(e, tab);
227
- });
228
-
229
- tab.xterm = xterm;
230
- tab.fitAddon = fitAddon;
231
- tab.bodyEl = bodyEl;
232
- }
233
-
234
- // --- Fit active terminal ---
235
- function fitTerminal() {
236
- if (!activeTabId) return;
237
- var tab = tabs.get(activeTabId);
238
- if (!tab || !tab.fitAddon || !tab.xterm) return;
239
-
240
- try {
241
- tab.fitAddon.fit();
242
- if (ctx.ws && ctx.connected) {
243
- ctx.ws.send(JSON.stringify({
244
- type: "term_resize",
245
- id: activeTabId,
246
- cols: tab.xterm.cols,
247
- rows: tab.xterm.rows,
248
- }));
249
- }
250
- } catch (e) {}
251
- }
252
-
253
- // --- Setup/cleanup resize listeners ---
254
- function setupListeners() {
255
- cleanupListeners();
256
-
257
- window.addEventListener("resize", fitTerminal);
258
-
259
- if (typeof ResizeObserver !== "undefined" && ctx.terminalBodyEl) {
260
- resizeObserver = new ResizeObserver(function () {
261
- fitTerminal();
262
- });
263
- resizeObserver.observe(ctx.terminalBodyEl);
264
- }
265
- }
266
-
267
- function cleanupListeners() {
268
- window.removeEventListener("resize", fitTerminal);
269
-
270
- if (resizeObserver) {
271
- resizeObserver.disconnect();
272
- resizeObserver = null;
273
- }
274
-
275
- if (viewportHandler && window.visualViewport) {
276
- window.visualViewport.removeEventListener("resize", viewportHandler);
277
- viewportHandler = null;
278
- }
279
- ctx.terminalContainerEl.style.height = "";
280
- }
281
-
282
- // --- Render tab bar ---
283
- function renderTabBar() {
284
- var tabsEl = document.getElementById("terminal-tabs");
285
- if (!tabsEl) return;
286
-
287
- tabsEl.innerHTML = "";
288
-
289
- for (var tab of tabs.values()) {
290
- (function (t) {
291
- var el = document.createElement("div");
292
- el.className = "terminal-tab";
293
- if (t.id === activeTabId) el.classList.add("active");
294
- if (t.exited) el.classList.add("exited");
295
-
296
- var label = document.createElement("span");
297
- label.className = "terminal-tab-label";
298
- label.textContent = t.title;
299
- el.appendChild(label);
300
-
301
- // Double-click label to rename
302
- label.addEventListener("dblclick", function (e) {
303
- e.stopPropagation();
304
- startRenameTab(t, label);
305
- });
306
-
307
- var closeBtn = document.createElement("button");
308
- closeBtn.className = "terminal-tab-close";
309
- closeBtn.innerHTML = '<i data-lucide="trash-2" style="width:12px;height:12px"></i>';
310
- closeBtn.addEventListener("click", function (e) {
311
- e.stopPropagation();
312
- closeTab(t.id);
313
- });
314
- el.appendChild(closeBtn);
315
-
316
- el.addEventListener("click", function () {
317
- if (t.id !== activeTabId) {
318
- activateTab(t.id);
319
- }
320
- });
321
-
322
- tabsEl.appendChild(el);
323
- })(tab);
324
- }
325
-
326
- updateTerminalBadge();
327
- refreshIcons();
328
- }
329
-
330
- // --- Rename tab inline ---
331
- function startRenameTab(tab, labelEl) {
332
- var input = document.createElement("input");
333
- input.className = "terminal-tab-rename";
334
- input.value = tab.title;
335
- input.maxLength = 50;
336
-
337
- labelEl.replaceWith(input);
338
- input.focus();
339
- input.select();
340
-
341
- function commit() {
342
- var newTitle = input.value.trim();
343
- if (newTitle && newTitle !== tab.title) {
344
- tab.title = newTitle;
345
- if (ctx.ws && ctx.connected) {
346
- ctx.ws.send(JSON.stringify({ type: "term_rename", id: tab.id, title: newTitle }));
347
- }
348
- }
349
- renderTabBar();
350
- }
351
-
352
- input.addEventListener("blur", commit);
353
- input.addEventListener("keydown", function (e) {
354
- if (e.key === "Enter") { input.blur(); }
355
- if (e.key === "Escape") {
356
- input.value = tab.title; // revert
357
- input.blur();
358
- }
359
- e.stopPropagation();
360
- });
361
- }
362
-
363
- // --- Update terminal count badge ---
364
- function updateTerminalBadge() {
365
- var countEl = document.getElementById("terminal-count");
366
- if (!countEl) return;
367
-
368
- var count = 0;
369
- for (var t of tabs.values()) {
370
- if (!t.exited) count++;
371
- }
372
-
373
- if (count > 0) {
374
- countEl.textContent = count;
375
- countEl.classList.remove("hidden");
376
- } else {
377
- countEl.classList.add("hidden");
378
- }
379
- }
380
-
381
- // --- Handle server messages ---
382
-
383
- export function handleTermList(msg) {
384
- var serverTerminals = msg.terminals || [];
385
- var serverIds = new Set();
386
-
387
- // Add/update tabs from server list
388
- for (var i = 0; i < serverTerminals.length; i++) {
389
- var st = serverTerminals[i];
390
- serverIds.add(st.id);
391
-
392
- if (tabs.has(st.id)) {
393
- var existing = tabs.get(st.id);
394
- existing.title = st.title;
395
- existing.exited = st.exited;
396
- } else {
397
- tabs.set(st.id, {
398
- id: st.id,
399
- title: st.title,
400
- exited: st.exited,
401
- xterm: null,
402
- fitAddon: null,
403
- bodyEl: null,
404
- });
405
- }
406
- }
407
-
408
- // Remove tabs no longer on server
409
- for (var id of tabs.keys()) {
410
- if (!serverIds.has(id)) {
411
- var removed = tabs.get(id);
412
- if (removed.xterm) {
413
- removed.xterm.dispose();
414
- }
415
- if (removed.bodyEl && removed.bodyEl.parentNode) {
416
- removed.bodyEl.parentNode.removeChild(removed.bodyEl);
417
- }
418
- tabs.delete(id);
419
- }
420
- }
421
-
422
- // If active tab was removed, switch to first available
423
- if (activeTabId && !tabs.has(activeTabId)) {
424
- activeTabId = null;
425
- }
426
-
427
- renderTabBar();
428
-
429
- // If panel is open and we have tabs, re-attach
430
- if (isOpen && tabs.size > 0) {
431
- if (!activeTabId) {
432
- activeTabId = tabs.keys().next().value;
433
- }
434
- activateTab(activeTabId);
435
- }
436
-
437
- // If panel is open and all tabs are gone, close panel
438
- if (isOpen && tabs.size === 0) {
439
- closeTerminal();
440
- }
441
- }
442
-
443
- export function handleTermCreated(msg) {
444
- // Switch to the newly created tab
445
- if (msg.id && tabs.has(msg.id)) {
446
- activateTab(msg.id);
447
- }
448
- }
449
-
450
- export function handleTermOutput(msg) {
451
- if (!msg.id) return;
452
- var tab = tabs.get(msg.id);
453
- if (tab && tab.xterm && msg.data) {
454
- tab.xterm.write(msg.data);
455
- }
456
- }
457
-
458
- export function handleTermExited(msg) {
459
- if (!msg.id) return;
460
- var tab = tabs.get(msg.id);
461
- if (tab) {
462
- tab.exited = true;
463
- if (tab.xterm) {
464
- tab.xterm.write("\r\n\x1b[90m[Process exited]\x1b[0m\r\n");
465
- }
466
- renderTabBar();
467
- }
468
- }
469
-
470
- export function handleTermClosed(msg) {
471
- if (!msg.id) return;
472
- var tab = tabs.get(msg.id);
473
- if (tab) {
474
- if (tab.xterm) tab.xterm.dispose();
475
- if (tab.bodyEl && tab.bodyEl.parentNode) {
476
- tab.bodyEl.parentNode.removeChild(tab.bodyEl);
477
- }
478
- tabs.delete(msg.id);
479
-
480
- if (activeTabId === msg.id) {
481
- activeTabId = null;
482
- if (tabs.size > 0) {
483
- activeTabId = tabs.keys().next().value;
484
- activateTab(activeTabId);
485
- }
486
- }
487
-
488
- renderTabBar();
489
-
490
- // Close panel if no tabs left
491
- if (isOpen && tabs.size === 0) {
492
- closeTerminal();
493
- }
494
- }
495
- }
496
-
497
- // --- Reset on reconnect ---
498
- export function resetTerminals() {
499
- // Dispose all xterm instances (server state survives, client re-syncs via term_list)
500
- for (var tab of tabs.values()) {
501
- if (tab.xterm) {
502
- tab.xterm.dispose();
503
- tab.xterm = null;
504
- tab.fitAddon = null;
505
- }
506
- if (tab.bodyEl && tab.bodyEl.parentNode) {
507
- tab.bodyEl.parentNode.removeChild(tab.bodyEl);
508
- tab.bodyEl = null;
509
- }
510
- }
511
- tabs.clear();
512
- activeTabId = null;
513
- cleanupListeners();
514
- renderTabBar();
515
- }
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
-
591
- // --- Mobile toolbar ---
592
- var KEY_MAP = {
593
- tab: "\t",
594
- esc: "\x1b",
595
- up: "\x1b[A",
596
- down: "\x1b[B",
597
- right: "\x1b[C",
598
- left: "\x1b[D",
599
- };
600
-
601
- function initToolbar(toolbar) {
602
- if (!toolbarBound) {
603
- toolbarBound = true;
604
-
605
- toolbar.addEventListener("mousedown", function (e) { e.preventDefault(); });
606
-
607
- toolbar.addEventListener("click", function (e) {
608
- var btn = e.target.closest(".term-key");
609
- if (!btn) return;
610
-
611
- var tab = activeTabId ? tabs.get(activeTabId) : null;
612
- if (!tab || !tab.xterm) return;
613
-
614
- var key = btn.dataset.key;
615
- if (!key) return;
616
-
617
- if (key === "ctrl") {
618
- ctrlActive = !ctrlActive;
619
- btn.classList.toggle("active", ctrlActive);
620
- return;
621
- }
622
-
623
- var seq = KEY_MAP[key];
624
- if (!seq) return;
625
-
626
- if (ctx.ws && ctx.connected) {
627
- ctx.ws.send(JSON.stringify({ type: "term_input", id: activeTabId, data: seq }));
628
- }
629
-
630
- if (ctrlActive) {
631
- ctrlActive = false;
632
- var ctrlBtn = toolbar.querySelector("[data-key='ctrl']");
633
- if (ctrlBtn) ctrlBtn.classList.remove("active");
634
- }
635
- });
636
- }
637
-
638
- // Attach Ctrl handler to active terminal
639
- var tab = activeTabId ? tabs.get(activeTabId) : null;
640
- if (tab && tab.xterm) {
641
- tab.xterm.attachCustomKeyEventHandler(function (ev) {
642
- if (ctrlActive && ev.type === "keydown" && ev.key.length === 1) {
643
- var charCode = ev.key.toUpperCase().charCodeAt(0);
644
- if (charCode >= 65 && charCode <= 90) {
645
- var ctrlChar = String.fromCharCode(charCode - 64);
646
- if (ctx.ws && ctx.connected) {
647
- ctx.ws.send(JSON.stringify({ type: "term_input", id: activeTabId, data: ctrlChar }));
648
- }
649
- ctrlActive = false;
650
- var ctrlBtn = document.querySelector("#terminal-toolbar [data-key='ctrl']");
651
- if (ctrlBtn) ctrlBtn.classList.remove("active");
652
- return false;
653
- }
654
- }
655
- return true;
656
- });
657
- }
658
- }