vg-coder-cli 2.0.31 → 2.0.32

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 (40) hide show
  1. package/ARCHITECTURE.md +255 -0
  2. package/README.md +0 -11
  3. package/change.sh +0 -0
  4. package/dist/vg-coder-bundle.js +42 -0
  5. package/gulpfile.js +111 -0
  6. package/package.json +19 -11
  7. package/src/index.js +28 -220
  8. package/src/server/api-server.js +120 -428
  9. package/src/server/views/css/bubble.css +81 -0
  10. package/src/server/views/css/code-viewer.css +58 -0
  11. package/src/server/views/css/terminal.css +59 -155
  12. package/src/server/views/dashboard.css +78 -678
  13. package/src/server/views/dashboard.html +39 -278
  14. package/src/server/views/js/api.js +2 -22
  15. package/src/server/views/js/config.js +27 -15
  16. package/src/server/views/js/event-protocol.js +263 -0
  17. package/src/server/views/js/features/bubble-features/index.js +125 -0
  18. package/src/server/views/js/features/bubble-features/paste-run-feature.js +16 -0
  19. package/src/server/views/js/features/bubble-features/terminal-feature.js +16 -0
  20. package/src/server/views/js/features/bubble.js +175 -0
  21. package/src/server/views/js/features/code-viewer.js +90 -0
  22. package/src/server/views/js/features/commands.js +34 -81
  23. package/src/server/views/js/features/editor-tabs.js +19 -46
  24. package/src/server/views/js/features/git-view.js +63 -81
  25. package/src/server/views/js/features/iframe-manager.js +3 -97
  26. package/src/server/views/js/features/monaco-manager.js +19 -39
  27. package/src/server/views/js/features/project-switcher.js +7 -63
  28. package/src/server/views/js/features/resize.js +5 -16
  29. package/src/server/views/js/features/structure.js +38 -106
  30. package/src/server/views/js/features/terminal.js +102 -418
  31. package/src/server/views/js/handlers.js +60 -43
  32. package/src/server/views/js/main.js +75 -179
  33. package/src/server/views/js/shadow-entry.js +21 -0
  34. package/src/server/views/js/utils.js +48 -28
  35. package/src/server/views/vg-coder/_metadata/generated_indexed_rulesets/_ruleset1 +0 -0
  36. package/src/server/views/vg-coder/controller.js +33 -258
  37. package/vetgo-auto/chrome/src/utils/injector-script.ts +33 -258
  38. package/vetgo-auto/vg-coder.zip +0 -0
  39. package/src/server/views/dashboard.js +0 -457
  40. package/test-pty.js +0 -31
@@ -1,26 +1,19 @@
1
- // Terminal Logic: Multi-instance & Floating
1
+ import { io } from 'socket.io-client';
2
+ import { Terminal } from 'xterm';
3
+ import { FitAddon } from 'xterm-addon-fit';
4
+ import { getById, showToast } from '../utils.js';
2
5
 
3
6
  let socket;
4
- const activeTerminals = new Map(); // Map<termId, { term, fitAddon, element, prevSize }>
5
-
6
- // Z-Index Management
7
+ const activeTerminals = new Map();
7
8
  let maxZIndex = 10001;
8
9
 
9
10
  export function initTerminal() {
10
- if (typeof io === 'undefined' || typeof Terminal === 'undefined') {
11
- console.error('Libraries missing for Terminal');
12
- return;
13
- }
14
-
15
- // 1. Init Socket
16
- socket = io();
11
+ // Force connect to localhost
12
+ socket = io('http://localhost:6868');
17
13
 
18
- // 2. Global Event Listeners
19
14
  socket.on('terminal:data', ({ termId, data }) => {
20
15
  const session = activeTerminals.get(termId);
21
- if (session) {
22
- session.term.write(data);
23
- }
16
+ if (session) session.term.write(data);
24
17
  });
25
18
 
26
19
  socket.on('terminal:exit', ({ termId }) => {
@@ -28,497 +21,188 @@ export function initTerminal() {
28
21
  });
29
22
  }
30
23
 
31
- /**
32
- * Tạo một cửa sổ terminal mới
33
- */
34
24
  export function createNewTerminal() {
35
25
  const termId = 'term_' + Date.now();
36
- const layer = document.getElementById('floating-terminals-layer');
26
+ const layer = getById('floating-terminals-layer');
37
27
 
38
- // 1. Create DOM Elements
28
+ if (!layer) {
29
+ console.error('Terminal layer not found');
30
+ return;
31
+ }
32
+
39
33
  const wrapper = document.createElement('div');
40
34
  wrapper.className = 'floating-terminal';
41
35
  wrapper.id = `wrapper-${termId}`;
42
36
 
43
- // Offset vị trí một chút nếu mở nhiều cái
37
+ // Position offset
44
38
  const offset = (activeTerminals.size % 10) * 30;
45
39
  wrapper.style.top = `${100 + offset}px`;
46
40
  wrapper.style.left = `${400 + offset}px`;
47
41
  wrapper.style.zIndex = ++maxZIndex;
48
42
 
49
- // HTML Template - Đã thêm Copy Buttons
43
+ // --- HTML STRUCTURE: Header + Body + Separate Input ---
50
44
  wrapper.innerHTML = `
51
- <div class="terminal-header" id="header-${termId}" ondblclick="window.toggleMinimize('${termId}')">
52
- <div class="terminal-title-group">
53
- <span>>_</span> Terminal (${activeTerminals.size + 1})
54
- </div>
55
-
56
- <!-- Copy Button Group -->
57
- <div class="terminal-copy-group" id="copy-group-${termId}">
58
- <button class="copy-btn copy-smart" onclick="window.copyTerminalLog('${termId}', 'smart')" title="Smart Copy (optimized for 3000 tokens)">
59
- 🧠 <span class="token-badge" id="badge-smart-${termId}">0</span>
60
- </button>
61
- <button class="copy-btn copy-errors" onclick="window.copyTerminalLog('${termId}', 'errors')" title="Errors Only (with context)">
62
- ⚠️ <span class="token-badge" id="badge-errors-${termId}">0</span>
63
- </button>
64
- <button class="copy-btn copy-recent" onclick="window.copyTerminalLog('${termId}', 'recent')" title="Recent 200 lines">
65
- 📄 <span class="token-badge" id="badge-recent-${termId}">0</span>
66
- </button>
67
- <button class="copy-btn copy-all" onclick="window.copyTerminalLog('${termId}', 'all')" title="Copy All">
68
- 📦 <span class="token-badge" id="badge-all-${termId}">0</span>
69
- </button>
70
- </div>
71
-
72
- <!-- Clear Button -->
73
- <button class="term-btn-clear" onclick="window.clearTerminal('${termId}')" title="Clear Terminal">
74
- 🗑️
75
- </button>
76
-
45
+ <div class="terminal-header" id="header-${termId}">
46
+ <div class="terminal-title-group"><span>>_</span> Terminal (${activeTerminals.size + 1})</div>
77
47
  <div class="terminal-controls">
78
- <button class="term-btn minimize" onclick="window.toggleMinimize('${termId}')" title="Minimize/Restore">-</button>
79
- <button class="term-btn maximize" onclick="window.toggleMaximize('${termId}')" title="Maximize">+</button>
80
- <button class="term-btn close" onclick="window.closeTerminal('${termId}')" title="Close">x</button>
48
+ <button class="term-btn minimize" onclick="window.toggleMinimize('${termId}')">-</button>
49
+ <button class="term-btn maximize" onclick="window.toggleMaximize('${termId}')">+</button>
50
+ <button class="term-btn close" onclick="window.closeTerminal('${termId}')">x</button>
81
51
  </div>
82
52
  </div>
83
53
  <div class="terminal-body" id="body-${termId}"></div>
54
+ <div class="terminal-input-row">
55
+ <span class="terminal-prompt">➜</span>
56
+ <input type="text" class="terminal-input" id="input-${termId}" placeholder="Type command here..." autocomplete="off" spellcheck="false" />
57
+ </div>
84
58
  `;
85
59
 
86
60
  layer.appendChild(wrapper);
87
61
 
88
- // 2. Init xterm.js
62
+ // --- XTERM CONFIG: Disable Direct Input ---
89
63
  const term = new Terminal({
90
64
  cursorBlink: true,
91
65
  fontSize: 13,
92
66
  fontFamily: 'Menlo, Monaco, "Courier New", monospace',
93
- theme: {
94
- background: '#1e1e1e',
95
- foreground: '#f0f0f0'
96
- }
67
+ theme: { background: '#1e1e1e', foreground: '#f0f0f0' },
68
+ disableStdin: true, // CRITICAL: Stop typing directly into xterm
69
+ rows: 18 // Slightly less to make room for input row
97
70
  });
98
71
 
99
- const fitAddon = new FitAddon.FitAddon();
72
+ const fitAddon = new FitAddon();
100
73
  term.loadAddon(fitAddon);
101
74
 
102
- term.open(document.getElementById(`body-${termId}`));
103
- fitAddon.fit();
104
-
105
- // 3. Register Events
75
+ const bodyEl = wrapper.querySelector(`#body-${termId}`);
76
+ term.open(bodyEl);
106
77
 
107
- // Bring to front on click
108
- wrapper.addEventListener('mousedown', () => {
109
- wrapper.style.zIndex = ++maxZIndex;
110
- });
111
-
112
- // Send input
113
- term.onData(data => {
114
- socket.emit('terminal:input', { termId, data });
115
- });
116
-
117
- // Resize Observer to refit terminal when window is resized
118
- const resizeObserver = new ResizeObserver(() => {
119
- // Chỉ fit lại nếu không bị minimized
120
- if (!wrapper.classList.contains('minimized')) {
121
- try {
122
- fitAddon.fit();
123
- socket.emit('terminal:resize', {
124
- termId,
125
- cols: term.cols,
126
- rows: term.rows
127
- });
128
- } catch (e) {}
78
+ // Fit Logic
79
+ setTimeout(() => {
80
+ try {
81
+ fitAddon.fit();
82
+ const cols = term.cols || 80;
83
+ const rows = term.rows || 18;
84
+
85
+ fetch('http://localhost:6868/api/projects')
86
+ .then(res => res.json())
87
+ .then(data => {
88
+ socket.emit('terminal:init', { termId, cols, rows, projectId: data.activeProjectId });
89
+ })
90
+ .catch(() => {
91
+ socket.emit('terminal:init', { termId, cols, rows });
92
+ });
93
+ } catch(e) {}
94
+ }, 100);
95
+
96
+ // --- INPUT HANDLING ---
97
+ const inputEl = wrapper.querySelector(`#input-${termId}`);
98
+
99
+ inputEl.addEventListener('keydown', (e) => {
100
+ // Handle ENTER: Send command
101
+ if (e.key === 'Enter') {
102
+ const command = inputEl.value;
103
+ // Send to server with carriage return
104
+ socket.emit('terminal:input', { termId, data: command + '\r' });
105
+
106
+ // Clear input
107
+ inputEl.value = '';
108
+
109
+ // Scroll xterm to bottom to see result
110
+ term.scrollToBottom();
129
111
  }
130
- });
131
- resizeObserver.observe(document.getElementById(`body-${termId}`));
132
-
133
- // 4. Make Draggable
134
- makeDraggable(wrapper, document.getElementById(`header-${termId}`));
135
-
136
- // 5. Store Session with log buffer
137
- activeTerminals.set(termId, {
138
- term,
139
- fitAddon,
140
- element: wrapper,
141
- logBuffer: [], // Local buffer for quick access
142
- partialLine: '' // Buffer for incomplete lines
143
- });
144
-
145
- // 6. Setup log buffer capture and token count updates
146
- // Listen to terminal's onData event (this fires for user input)
147
- // We need to listen to the actual output from the backend
148
- socket.on('terminal:data', ({ termId: dataTermId, data }) => {
149
- if (dataTermId !== termId) return;
150
-
151
- const session = activeTerminals.get(termId);
152
- if (!session) return;
153
-
154
- // Strip ANSI codes
155
- const cleanData = data.replace(
156
- // eslint-disable-next-line no-control-regex
157
- /\x1b\[[0-9;]*[a-zA-Z]|\x1b\][^\x07]*\x07|\x1b\][^\x1b]*\x1b\\/g,
158
- ''
159
- );
160
-
161
- // Accumulate partial line
162
- session.partialLine += cleanData;
163
-
164
- // Split by newlines and process complete lines
165
- const lines = session.partialLine.split(/\r?\n/);
166
112
 
167
- // Keep the last incomplete line in partialLine
168
- session.partialLine = lines.pop() || '';
169
-
170
- // Add complete lines to buffer
171
- lines.forEach(line => {
172
- if (line.trim().length > 0) {
173
- session.logBuffer.push(line);
174
- // Maintain max 10000 lines
175
- if (session.logBuffer.length > 10000) {
176
- session.logBuffer.shift();
177
- }
178
- }
179
- });
113
+ // Handle CTRL+C: Send interrupt signal
114
+ if (e.ctrlKey && e.key === 'c') {
115
+ socket.emit('terminal:input', { termId, data: '\x03' }); // ASCII ETX
116
+ e.preventDefault();
117
+ }
180
118
 
181
- // Update token counts (debounced)
182
- if (lines.length > 0) {
183
- updateTokenCounts(termId);
119
+ // Handle Arrow Up/Down for history could be implemented here locally if needed,
120
+ // but typically shells handle history. Since we send raw chars, remote shell history works
121
+ // IF we were typing in xterm. With separate input, we lose shell history navigation
122
+ // unless we implement a local history buffer here.
123
+ // For now, let's keep it simple.
124
+ });
125
+
126
+ // Auto-focus input when clicking anywhere on the wrapper
127
+ wrapper.addEventListener('mousedown', (e) => {
128
+ wrapper.style.zIndex = ++maxZIndex;
129
+ // Don't focus if clicking buttons
130
+ if (e.target.tagName !== 'BUTTON') {
131
+ // Delay focus slightly to ensure drag didn't start
132
+ setTimeout(() => inputEl.focus(), 10);
184
133
  }
185
134
  });
186
135
 
187
- // 7. Get current project ID from API
188
- let currentProjectId = null;
189
- fetch('/api/projects')
190
- .then(res => res.json())
191
- .then(data => {
192
- currentProjectId = data.activeProjectId;
193
-
194
- // Store in session
195
- activeTerminals.get(termId).projectId = currentProjectId;
196
-
197
- // Init Backend Process with projectId
198
- socket.emit('terminal:init', {
199
- termId,
200
- cols: term.cols,
201
- rows: term.rows,
202
- projectId: currentProjectId
203
- });
204
- })
205
- .catch(err => {
206
- console.error('Failed to get project info:', err);
207
- // Fallback without projectId
208
- socket.emit('terminal:init', {
209
- termId,
210
- cols: term.cols,
211
- rows: term.rows
212
- });
213
- });
214
-
215
- // 8. Initial token count update
216
- setTimeout(() => updateTokenCounts(termId), 100);
136
+ // Window Controls
137
+ const header = wrapper.querySelector(`#header-${termId}`);
138
+ header.addEventListener('dblclick', () => window.toggleMinimize(termId));
139
+ makeDraggable(wrapper, header);
140
+
141
+ activeTerminals.set(termId, { term, fitAddon, element: wrapper });
217
142
 
218
- // Return termId so caller can use it
143
+ // Focus input immediately
144
+ inputEl.focus();
145
+
219
146
  return termId;
220
147
  }
221
148
 
222
- /**
223
- * Đóng terminal UI & Process
224
- */
225
149
  export function closeTerminalUI(termId) {
226
150
  const session = activeTerminals.get(termId);
227
151
  if (session) {
228
- // Remove from DOM
229
152
  session.element.remove();
230
- // Clean map
231
153
  activeTerminals.delete(termId);
232
- // Tell server to kill
233
154
  socket.emit('terminal:kill', { termId });
234
155
  }
235
156
  }
236
157
 
237
- /**
238
- * Toggle Minimize/Restore
239
- */
240
158
  export function toggleMinimize(termId) {
241
159
  const session = activeTerminals.get(termId);
242
- if (!session) return;
243
-
244
- const el = session.element;
245
- const isMinimized = el.classList.contains('minimized');
246
-
247
- if (isMinimized) {
248
- // Restore
249
- el.classList.remove('minimized');
250
-
251
- // Restore size logic if needed, but CSS transition handles visualization
252
- // Need to refit xterm after transition
253
- setTimeout(() => {
254
- try { session.fitAddon.fit(); } catch(e){}
255
- }, 250);
256
- } else {
257
- // Minimize
258
- // Store current width/height if we want to restore exact pixel values later
259
- // (CSS handles the visual hiding)
260
- el.classList.add('minimized');
261
- }
160
+ if (session) session.element.classList.toggle('minimized');
262
161
  }
263
162
 
264
- /**
265
- * Toggle Maximize (Simple Fullscreen simulation)
266
- */
267
163
  export function toggleMaximize(termId) {
268
164
  const session = activeTerminals.get(termId);
269
- if (!session) return;
270
-
271
- const el = session.element;
272
- const isMaximized = el.classList.contains('maximized');
273
-
274
- if (isMaximized) {
275
- // Restore normal size
276
- el.classList.remove('maximized');
277
- el.style.width = session.prevSize?.width || '600px';
278
- el.style.height = session.prevSize?.height || '400px';
279
- el.style.top = session.prevSize?.top || '100px';
280
- el.style.left = session.prevSize?.left || '400px';
281
- } else {
282
- // Save current state
283
- session.prevSize = {
284
- width: el.style.width,
285
- height: el.style.height,
286
- top: el.style.top,
287
- left: el.style.left
288
- };
289
-
290
- // Go full "floating" screen
291
- el.classList.add('maximized');
292
- el.style.width = '90vw';
293
- el.style.height = '80vh';
294
- el.style.top = '10vh';
295
- el.style.left = '5vw';
296
- el.classList.remove('minimized'); // Ensure not minimized
165
+ if (session) {
166
+ session.element.classList.toggle('maximized');
167
+ setTimeout(() => {
168
+ try {
169
+ session.fitAddon.fit();
170
+ socket.emit('terminal:resize', { termId, cols: session.term.cols, rows: session.term.rows });
171
+ } catch(e){}
172
+ }, 250);
297
173
  }
298
-
299
- setTimeout(() => {
300
- try { session.fitAddon.fit(); } catch(e){}
301
- }, 250);
302
174
  }
303
175
 
304
-
305
- /**
306
- * Logic Drag & Drop
307
- */
308
176
  function makeDraggable(element, handle) {
309
177
  let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
310
-
311
178
  handle.onmousedown = dragMouseDown;
312
179
 
313
180
  function dragMouseDown(e) {
314
- // Don't drag if clicking buttons
315
181
  if(e.target.tagName === 'BUTTON') return;
316
-
317
182
  e.preventDefault();
318
- // Get mouse cursor position at startup
319
183
  pos3 = e.clientX;
320
184
  pos4 = e.clientY;
321
185
  document.onmouseup = closeDragElement;
322
- // Call function whenever cursor moves
323
186
  document.onmousemove = elementDrag;
324
187
  }
325
188
 
326
189
  function elementDrag(e) {
327
190
  e.preventDefault();
328
- // Calculate new cursor position
329
191
  pos1 = pos3 - e.clientX;
330
192
  pos2 = pos4 - e.clientY;
331
193
  pos3 = e.clientX;
332
194
  pos4 = e.clientY;
333
- // Set element's new position
334
195
  element.style.top = (element.offsetTop - pos2) + "px";
335
196
  element.style.left = (element.offsetLeft - pos1) + "px";
336
197
  }
337
198
 
338
199
  function closeDragElement() {
339
- // Stop moving when mouse button is released
340
200
  document.onmouseup = null;
341
201
  document.onmousemove = null;
342
202
  }
343
203
  }
344
204
 
345
- /**
346
- * Copy terminal log with specified strategy
347
- * @param {string} termId - Terminal ID
348
- * @param {string} strategy - 'smart' | 'errors' | 'recent' | 'all'
349
- */
350
- async function copyTerminalLog(termId, strategy) {
351
- const session = activeTerminals.get(termId);
352
- if (!session || !session.logBuffer || session.logBuffer.length === 0) {
353
- showToast('⚠️ No logs to copy', 'warning');
354
- return;
355
- }
356
-
357
- try {
358
- // Use SmartCopyEngine to generate content
359
- let result;
360
- const lines = session.logBuffer;
361
-
362
- switch (strategy) {
363
- case 'smart':
364
- result = window.SmartCopyEngine.generateSmartCopy(lines, 3000);
365
- break;
366
- case 'errors':
367
- result = window.SmartCopyEngine.generateErrorsOnly(lines);
368
- break;
369
- case 'recent':
370
- result = window.SmartCopyEngine.generateRecent(lines, 200);
371
- break;
372
- case 'all':
373
- result = window.SmartCopyEngine.generateCopyAll(lines);
374
- break;
375
- default:
376
- result = window.SmartCopyEngine.generateSmartCopy(lines, 3000);
377
- }
378
-
379
- // Copy to clipboard
380
- await navigator.clipboard.writeText(result.content);
381
-
382
- // Show success toast with details
383
- const strategyNames = {
384
- smart: '🧠 Smart',
385
- errors: '⚠️ Errors',
386
- recent: '📄 Recent',
387
- all: '📦 All'
388
- };
389
-
390
- let message = `✅ Copied ${result.tokens} tokens (${strategyNames[strategy]})`;
391
-
392
- if (result.stats && result.stats.message) {
393
- message += `\n${result.stats.message}`;
394
- }
395
-
396
- if (result.warning) {
397
- message += `\n${result.warning}`;
398
- }
399
-
400
- showToast(message, 'success');
401
-
402
- } catch (error) {
403
- console.error('Copy failed:', error);
404
- showToast('❌ Failed to copy logs', 'error');
405
- }
406
- }
407
-
408
- /**
409
- * Update token count badges for a terminal
410
- * Debounced to avoid excessive updates
411
- */
412
- let updateTokenCountsTimeout = null;
413
- function updateTokenCounts(termId) {
414
- // Debounce updates
415
- clearTimeout(updateTokenCountsTimeout);
416
- updateTokenCountsTimeout = setTimeout(() => {
417
- const session = activeTerminals.get(termId);
418
- if (!session || !session.logBuffer) return;
419
-
420
- const analysis = window.SmartCopyEngine.analyzeLogBuffer(session.logBuffer);
421
-
422
- // Update badges
423
- updateBadge(`badge-smart-${termId}`, analysis.smart.tokens);
424
- updateBadge(`badge-errors-${termId}`, analysis.errors.tokens);
425
- updateBadge(`badge-recent-${termId}`, analysis.recent.tokens);
426
- updateBadge(`badge-all-${termId}`, analysis.all.tokens);
427
- }, 500); // 500ms debounce
428
- }
429
-
430
- /**
431
- * Update a single badge element
432
- */
433
- function updateBadge(badgeId, tokens) {
434
- const badge = document.getElementById(badgeId);
435
- if (badge) {
436
- badge.textContent = formatTokenCount(tokens);
437
- }
438
- }
439
-
440
- /**
441
- * Format token count for display
442
- */
443
- function formatTokenCount(tokens) {
444
- if (tokens === 0) return '0';
445
- if (tokens < 1000) return tokens.toString();
446
- if (tokens < 10000) return (tokens / 1000).toFixed(1) + 'k';
447
- return Math.floor(tokens / 1000) + 'k';
448
- }
449
-
450
- /**
451
- * Show toast notification
452
- */
453
- function showToast(message, type = 'info') {
454
- const toast = document.getElementById('toast');
455
- if (!toast) return;
456
-
457
- toast.textContent = message;
458
- toast.className = 'toast show';
459
-
460
- if (type === 'success') {
461
- toast.style.background = '#28a745';
462
- } else if (type === 'error') {
463
- toast.style.background = '#dc3545';
464
- } else if (type === 'warning') {
465
- toast.style.background = '#ffc107';
466
- toast.style.color = '#000';
467
- } else {
468
- toast.style.background = '#007bff';
469
- }
470
-
471
- setTimeout(() => {
472
- toast.classList.remove('show');
473
- }, 3000);
474
- }
475
-
476
- /**
477
- * Clear terminal display and log buffer
478
- * @param {string} termId - Terminal ID
479
- */
480
- function clearTerminal(termId) {
481
- const session = activeTerminals.get(termId);
482
- if (!session) return;
483
-
484
- // Clear the xterm display
485
- session.term.clear();
486
-
487
- // Clear the log buffer
488
- session.logBuffer = [];
489
- session.partialLine = '';
490
-
491
- // Reset token counts to 0
492
- updateBadge(`badge-smart-${termId}`, 0);
493
- updateBadge(`badge-errors-${termId}`, 0);
494
- updateBadge(`badge-recent-${termId}`, 0);
495
- updateBadge(`badge-all-${termId}`, 0);
496
-
497
- // Show toast notification
498
- showToast('🗑️ Terminal cleared', 'info');
499
- }
500
-
501
- /**
502
- * Update terminal visibility based on active project
503
- * @param {string} activeProjectId - Active project ID
504
- */
505
- function updateTerminalVisibility(activeProjectId) {
506
- activeTerminals.forEach((session, termId) => {
507
- const shouldShow = !session.projectId || session.projectId === activeProjectId;
508
- session.element.style.display = shouldShow ? 'block' : 'none';
509
- });
510
-
511
- const visibleCount = Array.from(activeTerminals.values())
512
- .filter(s => s.element.style.display !== 'none').length;
513
-
514
- console.log(`Updated terminal visibility: ${visibleCount} visible for project ${activeProjectId}`);
515
- }
516
-
517
- // Global Exports for HTML onclick
518
205
  window.createNewTerminal = createNewTerminal;
519
206
  window.closeTerminal = closeTerminalUI;
520
207
  window.toggleMinimize = toggleMinimize;
521
208
  window.toggleMaximize = toggleMaximize;
522
- window.copyTerminalLog = copyTerminalLog;
523
- window.clearTerminal = clearTerminal;
524
- window.updateTerminalVisibility = updateTerminalVisibility;