vg-coder-cli 2.0.22 → 2.0.24

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 (50) hide show
  1. package/.vg/tree-state.json +9 -0
  2. package/package.json +3 -1
  3. package/scripts/build.js +49 -6
  4. package/src/server/api-server.js +46 -0
  5. package/src/server/terminal-manager.js +10 -1
  6. package/src/server/views/css/structure.css +4 -1
  7. package/src/server/views/dashboard.css +24 -1
  8. package/src/server/views/dashboard.html +2 -0
  9. package/src/server/views/js/api.js +24 -0
  10. package/src/server/views/js/features/resize.js +57 -0
  11. package/src/server/views/js/features/structure.js +109 -16
  12. package/src/server/views/js/main.js +5 -0
  13. package/src/server/views/vg-coder/background.js +48201 -2
  14. package/src/server/views/vg-coder/controller.js +496 -1
  15. package/src/server/views/vg-coder/manifest.json +13 -5
  16. package/src/server/views/vg-coder/{options.css → sidepanel.css} +34 -32
  17. package/src/server/views/vg-coder/{options.html → sidepanel.html} +2 -2
  18. package/src/server/views/vg-coder/sidepanel.js +347 -0
  19. package/vetgo-auto/README.md +3 -0
  20. package/vetgo-auto/chrome/CSP_IMPROVEMENTS.md +147 -0
  21. package/vetgo-auto/chrome/MANIFEST_V3_MIGRATION.md +123 -0
  22. package/vetgo-auto/chrome/assets/icon128.png +0 -0
  23. package/vetgo-auto/chrome/assets/icon16.png +0 -0
  24. package/vetgo-auto/chrome/assets/icon48.png +0 -0
  25. package/vetgo-auto/chrome/environments/environment.ts +13 -0
  26. package/vetgo-auto/chrome/manifest.json +66 -0
  27. package/vetgo-auto/chrome/rules.json +23 -0
  28. package/vetgo-auto/chrome/src/background.ts +200 -0
  29. package/vetgo-auto/chrome/src/controller.ts +98 -0
  30. package/vetgo-auto/chrome/src/controllers/common.firebase.ts +31 -0
  31. package/vetgo-auto/chrome/src/controllers/firebase-crud.ts +147 -0
  32. package/vetgo-auto/chrome/src/controllers/load-common-fuc.controller.ts +24 -0
  33. package/vetgo-auto/chrome/src/controllers/load-script.controller.ts +23 -0
  34. package/vetgo-auto/chrome/src/script-injector.ts +305 -0
  35. package/vetgo-auto/chrome/src/sidepanel.css +166 -0
  36. package/vetgo-auto/chrome/src/sidepanel.html +48 -0
  37. package/vetgo-auto/chrome/src/sidepanel.ts +127 -0
  38. package/vetgo-auto/chrome/src/utils/db-utils.ts +2 -0
  39. package/vetgo-auto/chrome/src/utils/environment-storage.service.ts +85 -0
  40. package/vetgo-auto/chrome/webpack.config.js +53 -0
  41. package/vetgo-auto/chrome/webpack.config.prod.js +54 -0
  42. package/vetgo-auto/package.json +30 -0
  43. package/vetgo-auto/tsconfig.json +27 -0
  44. package/vg-coder-cli-2.0.23.tgz +0 -0
  45. package/vg-coder-cli-2.0.24.tgz +0 -0
  46. package/src/server/views/vg-coder/background.js.LICENSE.txt +0 -118
  47. package/src/server/views/vg-coder/options.js +0 -1
  48. package/vg-coder-cli-2.0.21.tgz +0 -0
  49. package/vg-coder-cli-2.0.22.tgz +0 -0
  50. package/vg-coder.zip +0 -0
@@ -0,0 +1,9 @@
1
+ {
2
+ "excludedPaths": [
3
+ "scripts/build.js",
4
+ "src/detectors/project-detector.js",
5
+ "src/exporter/html-exporter.js",
6
+ "src/ignore",
7
+ "src/ignore/ignore-manager.js"
8
+ ]
9
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vg-coder-cli",
3
- "version": "2.0.22",
3
+ "version": "2.0.24",
4
4
  "description": "🚀 CLI tool to analyze projects, concatenate source files, count tokens, and export HTML with syntax highlighting and copy functionality",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -13,6 +13,8 @@
13
13
  "test": "jest",
14
14
  "test:watch": "jest --watch",
15
15
  "dev": "nodemon src/index.js",
16
+ "build:extension": "cd vetgo-auto && npm run build",
17
+ "build:copy": "node scripts/build.js",
16
18
  "build": "node scripts/build.js",
17
19
  "push": "npm run build && npm pack && npm publish"
18
20
  },
package/scripts/build.js CHANGED
@@ -1,29 +1,72 @@
1
1
  const AdmZip = require('adm-zip');
2
2
  const path = require('path');
3
3
  const fs = require('fs-extra');
4
+ const { execSync } = require('child_process');
4
5
 
5
6
  async function build() {
6
7
  try {
7
8
  const rootDir = path.resolve(__dirname, '..');
9
+ const extensionSourceDir = path.join(rootDir, 'vetgo-auto', 'chrome', 'dist');
10
+ const extensionWorkspaceDir = path.join(rootDir, 'vetgo-auto');
8
11
  const zipPath = path.join(rootDir, 'vg-coder.zip');
9
12
  const targetDir = path.join(rootDir, 'src', 'server', 'views', 'vg-coder');
10
13
 
11
14
  console.log('🏗️ Starting build process...');
12
15
 
13
- // 1. Check if zip exists
16
+ // Strategy 1: Try to build and copy from vetgo-auto/chrome/dist
17
+ if (fs.existsSync(extensionWorkspaceDir)) {
18
+ console.log('📦 Found vetgo-auto workspace. Building extension...');
19
+
20
+ try {
21
+ // Build the extension
22
+ console.log('🔨 Running: cd vetgo-auto && npm run build');
23
+ execSync('npm run build', {
24
+ cwd: extensionWorkspaceDir,
25
+ stdio: 'inherit'
26
+ });
27
+
28
+ // Check if dist directory exists
29
+ if (fs.existsSync(extensionSourceDir)) {
30
+ console.log('✅ Extension built successfully at:', extensionSourceDir);
31
+
32
+ // Clean old directory
33
+ if (fs.existsSync(targetDir)) {
34
+ console.log('🧹 Cleaning old extension directory...');
35
+ fs.removeSync(targetDir);
36
+ }
37
+
38
+ // Copy built extension
39
+ console.log('📋 Copying extension to:', targetDir);
40
+ fs.copySync(extensionSourceDir, targetDir);
41
+
42
+ console.log('✅ Extension copied successfully!');
43
+ console.log('🚀 Build completed successfully!');
44
+ return;
45
+ } else {
46
+ console.warn('⚠️ Extension dist directory not found. Falling back to zip extraction...');
47
+ }
48
+ } catch (buildError) {
49
+ console.warn('⚠️ Extension build failed:', buildError.message);
50
+ console.warn('⚠️ Falling back to zip extraction...');
51
+ }
52
+ } else {
53
+ console.log('ℹ️ vetgo-auto workspace not found. Using zip file...');
54
+ }
55
+
56
+ // Strategy 2: Fallback to zip extraction (for npm package users)
14
57
  if (!fs.existsSync(zipPath)) {
15
- console.error('⚠️ vg-coder.zip not found at root. Skipping extension extraction.');
16
- // We don't exit 1 here to allow build to continue if zip is missing in dev
17
- return;
58
+ console.error('vg-coder.zip not found and extension build failed.');
59
+ console.error(' Cannot proceed with build.');
60
+ process.exit(1);
18
61
  }
19
62
 
20
- // 2. Clean old directory
63
+ // Clean old directory
21
64
  if (fs.existsSync(targetDir)) {
22
65
  console.log('🧹 Cleaning old extension directory...');
23
66
  fs.removeSync(targetDir);
24
67
  }
25
68
 
26
- // 3. Unzip
69
+ // Unzip
27
70
  console.log('📦 Unzipping vg-coder.zip...');
28
71
  const zip = new AdmZip(zipPath);
29
72
  zip.extractAllTo(targetDir, true);
@@ -274,6 +274,52 @@ class ApiServer {
274
274
  }
275
275
  });
276
276
 
277
+ // --- TREE STATE API ---
278
+
279
+ // Save tree state (excluded paths)
280
+ this.app.post('/api/tree-state/save', async (req, res) => {
281
+ try {
282
+ const { excludedPaths } = req.body;
283
+ if (!Array.isArray(excludedPaths)) {
284
+ return res.status(400).json({ error: 'excludedPaths must be an array' });
285
+ }
286
+
287
+ // Create .vg directory if it doesn't exist
288
+ const vgDir = path.join(this.workingDir, '.vg');
289
+ await fs.ensureDir(vgDir);
290
+
291
+ // Save state to .vg/tree-state.json
292
+ const stateFile = path.join(vgDir, 'tree-state.json');
293
+ await fs.writeJson(stateFile, { excludedPaths }, { spaces: 2 });
294
+
295
+ console.log(chalk.green(`✓ Saved tree state: ${excludedPaths.length} excluded items`));
296
+ res.json({ success: true, count: excludedPaths.length });
297
+ } catch (error) {
298
+ console.error(chalk.red('❌ [TREE STATE SAVE] Error:'), error.message);
299
+ res.status(500).json({ error: error.message });
300
+ }
301
+ });
302
+
303
+ // Load tree state
304
+ this.app.get('/api/tree-state/load', async (req, res) => {
305
+ try {
306
+ const stateFile = path.join(this.workingDir, '.vg', 'tree-state.json');
307
+
308
+ // Check if state file exists
309
+ if (!await fs.pathExists(stateFile)) {
310
+ return res.json({ excludedPaths: [] });
311
+ }
312
+
313
+ // Read and return state
314
+ const state = await fs.readJson(stateFile);
315
+ res.json({ excludedPaths: state.excludedPaths || [] });
316
+ } catch (error) {
317
+ console.error(chalk.red('❌ [TREE STATE LOAD] Error:'), error.message);
318
+ // Return empty state on error instead of failing
319
+ res.json({ excludedPaths: [] });
320
+ }
321
+ });
322
+
277
323
  // --- GENERAL API ---
278
324
 
279
325
  this.app.post('/api/analyze', async (req, res) => {
@@ -5,7 +5,16 @@ class TerminalManager {
5
5
  constructor() {
6
6
  // Map: termId -> { process: pty, socketId: string }
7
7
  this.sessions = new Map();
8
- this.shell = os.platform() === 'win32' ? 'powershell.exe' : 'bash';
8
+ // Use full path to shell to avoid posix_spawnp errors
9
+ if (os.platform() === 'win32') {
10
+ this.shell = 'powershell.exe';
11
+ } else if (os.platform() === 'darwin') {
12
+ // macOS - use zsh (default shell since Catalina)
13
+ this.shell = process.env.SHELL || '/bin/zsh';
14
+ } else {
15
+ // Linux and others
16
+ this.shell = process.env.SHELL || '/bin/bash';
17
+ }
9
18
  }
10
19
 
11
20
  createTerminal(socket, termId, cols = 80, rows = 24, cwd) {
@@ -25,8 +25,9 @@
25
25
  font-size: 11px;
26
26
  /* Smaller tree font */
27
27
  overflow-x: auto;
28
- max-height: 400px;
28
+ max-height: 600px;
29
29
  overflow-y: auto;
30
+ width: 100%; /* Take full width */
30
31
  }
31
32
 
32
33
  .tree-ul {
@@ -58,6 +59,7 @@
58
59
  cursor: pointer;
59
60
  transition: background 0.1s;
60
61
  line-height: 1.2;
62
+ width: 100%; /* Ensure row takes full width */
61
63
  }
62
64
 
63
65
  .tree-item-row:hover {
@@ -99,6 +101,7 @@
99
101
  font-weight: 600;
100
102
  min-width: 30px;
101
103
  text-align: center;
104
+ flex-shrink: 0; /* Always keep badge visible */
102
105
  }
103
106
 
104
107
  .token-low {
@@ -61,14 +61,37 @@ body {
61
61
  height: 100%;
62
62
  overflow-y: auto;
63
63
  background: var(--ios-bg);
64
- border-right: 1px solid var(--ios-separator);
65
64
  position: relative;
66
65
  z-index: 10;
66
+ /* allow resize override */
67
+ flex: 0 0 450px;
68
+ }
69
+
70
+ /* Resize Handler */
71
+ .resize-handler {
72
+ width: 16px;
73
+ cursor: col-resize;
74
+ background: transparent;
75
+ z-index: 100;
76
+ margin-left: -3px; /* Overlap border */
77
+ position: relative;
78
+ }
79
+
80
+ .resize-handler:hover,
81
+ body.resizing .resize-handler {
82
+ background: var(--ios-blue);
83
+ opacity: 0.5;
84
+ }
85
+
86
+ body.resizing {
87
+ cursor: col-resize;
88
+ user-select: none;
67
89
  }
68
90
 
69
91
  .container {
70
92
  padding: 15px;
71
93
  padding-top: 15px;
94
+ padding-right: 0px;
72
95
  }
73
96
 
74
97
  /* --- COMPONENT STYLES --- */
@@ -186,6 +186,8 @@
186
186
  <div style="height: 50px;"></div>
187
187
  </div>
188
188
  </div>
189
+ <!-- Resize Handler -->
190
+ <div id="resize-handler" class="resize-handler"></div>
189
191
 
190
192
  <!-- CỘT PHẢI: AI Iframe & Editor -->
191
193
  <div class="right-panel">
@@ -100,6 +100,30 @@ export async function commitChanges(message) {
100
100
  return true;
101
101
  }
102
102
 
103
+ // --- Tree State API ---
104
+
105
+ export async function saveTreeState(excludedPaths) {
106
+ const res = await fetch(`${API_BASE}/api/tree-state/save`, {
107
+ method: 'POST',
108
+ headers: { 'Content-Type': 'application/json' },
109
+ body: JSON.stringify({ excludedPaths })
110
+ });
111
+ if (!res.ok) {
112
+ const data = await res.json();
113
+ throw new Error(data.error || 'Failed to save tree state');
114
+ }
115
+ return await res.json();
116
+ }
117
+
118
+ export async function loadTreeState() {
119
+ const res = await fetch(`${API_BASE}/api/tree-state/load`);
120
+ if (!res.ok) {
121
+ const data = await res.json();
122
+ throw new Error(data.error || 'Failed to load tree state');
123
+ }
124
+ return await res.json();
125
+ }
126
+
103
127
  // ------------------------
104
128
 
105
129
  export async function copyAsFile(filename, content) {
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Feature: Left Panel Resize
3
+ * Allows the user to drag the right edge of the left panel to resize it.
4
+ */
5
+
6
+ export function initResizeHandler() {
7
+ const leftPanel = document.querySelector('.left-panel');
8
+ const handle = document.getElementById('resize-handler'); // Match the ID we will add in HTML
9
+ const splitLayout = document.querySelector('.split-layout');
10
+
11
+ if (!leftPanel || !handle) {
12
+ console.warn('Resize elements not found');
13
+ return;
14
+ }
15
+
16
+ let isResizing = false;
17
+ let startX = 0;
18
+ let startWidth = 0;
19
+
20
+ // Mouse Down
21
+ handle.addEventListener('mousedown', (e) => {
22
+ isResizing = true;
23
+ startX = e.clientX;
24
+ startWidth = leftPanel.getBoundingClientRect().width;
25
+
26
+ // Add resizing class for styling/cursor
27
+ document.body.classList.add('resizing');
28
+
29
+ // Disable text selection during drag
30
+ e.preventDefault();
31
+ });
32
+
33
+ // Mouse Move
34
+ document.addEventListener('mousemove', (e) => {
35
+ if (!isResizing) return;
36
+
37
+ requestAnimationFrame(() => {
38
+ const currentX = e.clientX;
39
+ const diffX = currentX - startX;
40
+ const newWidth = Math.max(250, startWidth + diffX); // Min width 250px
41
+ const maxWidth = window.innerWidth - 300; // Leave space for right panel
42
+
43
+ if (newWidth < maxWidth) {
44
+ leftPanel.style.flex = `0 0 ${newWidth}px`;
45
+ leftPanel.style.width = `${newWidth}px`;
46
+ }
47
+ });
48
+ });
49
+
50
+ // Mouse Up
51
+ document.addEventListener('mouseup', () => {
52
+ if (isResizing) {
53
+ isResizing = false;
54
+ document.body.classList.remove('resizing');
55
+ }
56
+ });
57
+ }
@@ -1,9 +1,13 @@
1
- import { getStructure, analyzeProject, copyToClipboard } from '../api.js';
1
+ import { getStructure, analyzeProject, copyToClipboard, saveTreeState as apiSaveTreeState, loadTreeState as apiLoadTreeState } from '../api.js';
2
2
  import { showToast, showLoading, resetButton, showResponse, formatNumber, showCopiedState } from '../utils.js';
3
3
 
4
4
  // Global variable to store current structure data
5
5
  let currentStructureData = null;
6
6
 
7
+ // Tree state management
8
+ let excludedPaths = new Set(); // Paths that are unchecked
9
+ let saveStateTimeout = null; // Debounce timer for auto-save
10
+
7
11
  /**
8
12
  * Handle Structure button click logic
9
13
  */
@@ -24,6 +28,9 @@ export async function handleStructureView(event) {
24
28
  const data = await getStructure(path);
25
29
  currentStructureData = data.structure;
26
30
 
31
+ // Load saved state before rendering
32
+ await loadTreeState();
33
+
27
34
  // Render Tree HTML using recursive function
28
35
  treeContent.innerHTML = generateTreeHtml(data.structure);
29
36
 
@@ -62,18 +69,35 @@ export function handleCheckboxChange(event) {
62
69
  event.stopPropagation();
63
70
  const checkbox = event.target;
64
71
  const isChecked = checkbox.checked;
72
+ const path = checkbox.dataset.path;
73
+
74
+ // 1. Update excluded paths set
75
+ if (isChecked) {
76
+ excludedPaths.delete(path);
77
+ } else {
78
+ excludedPaths.add(path);
79
+ }
65
80
 
66
- // 1. Sync Children: If this is a folder, update all children checkboxes
81
+ // 2. Sync Children: If this is a folder, update all children checkboxes
67
82
  const li = checkbox.closest('.tree-li');
68
83
  if (li) {
69
84
  const childrenCheckboxes = li.querySelectorAll('.tree-checkbox');
70
85
  childrenCheckboxes.forEach(child => {
71
86
  child.checked = isChecked;
87
+ const childPath = child.dataset.path;
88
+ if (isChecked) {
89
+ excludedPaths.delete(childPath);
90
+ } else {
91
+ excludedPaths.add(childPath);
92
+ }
72
93
  });
73
94
  }
74
95
 
75
- // 2. Recalculate Tokens
96
+ // 3. Recalculate Tokens
76
97
  updateTotalTokens();
98
+
99
+ // 4. Auto-save state (debounced)
100
+ debouncedSaveState();
77
101
  }
78
102
 
79
103
  /**
@@ -169,11 +193,35 @@ export async function handleCopySelected(event) {
169
193
  function generateTreeHtml(node) {
170
194
  if (!node) return '';
171
195
 
172
- const isDir = node.type === 'directory';
173
- const hasChildren = isDir && node.children && node.children.length > 0;
196
+ // --- COMPACT FOLDER LOGIC ---
197
+ let currentNode = node;
198
+ let displayName = node.name;
199
+ let isCompact = false;
200
+
201
+ // Only compact if it's a directory
202
+ if (currentNode.type === 'directory') {
203
+ // While current node has EXACTLY one child and that child is a directory
204
+ while (
205
+ currentNode.children &&
206
+ currentNode.children.length === 1 &&
207
+ currentNode.children[0].type === 'directory'
208
+ ) {
209
+ const child = currentNode.children[0];
210
+ displayName += '/' + child.name;
211
+ currentNode = child; // Advance to child
212
+ isCompact = true;
213
+ }
214
+ }
215
+
216
+ // Now uses 'currentNode' for properties (it's the deepest folder in the chain)
217
+ // But we use 'displayName' for the UI
218
+ // 'tokens' should be from the root node of this chain (usually same as leaf for folders)
174
219
 
175
- // Determine Token Color
176
- const tokens = node.tokens || 0;
220
+ const isDir = currentNode.type === 'directory';
221
+ const hasChildren = isDir && currentNode.children && currentNode.children.length > 0;
222
+
223
+ // Determine Token Color (Use original node's tokens or deep node's tokens - they should be identical)
224
+ const tokens = currentNode.tokens || 0;
177
225
  let tokenClass = 'token-low';
178
226
  if (tokens > 5000) tokenClass = 'token-high';
179
227
  else if (tokens > 2000) tokenClass = 'token-med';
@@ -184,7 +232,6 @@ function generateTreeHtml(node) {
184
232
  const liClass = `tree-li ${hasChildren ? 'has-children' : ''}`;
185
233
 
186
234
  // Actions
187
- // On click: Toggle if folder, Open Tab if file
188
235
  let clickAction = '';
189
236
  let cursorStyle = '';
190
237
 
@@ -196,34 +243,38 @@ function generateTreeHtml(node) {
196
243
  } else {
197
244
  // File click -> Open in Editor
198
245
  // Escape backslashes for Windows paths
199
- const safePath = (node.relativePath || node.path).replace(/\\/g, '\\\\');
200
- clickAction = `onclick="window.openFileTab('${safePath}', '${node.name}')"`;
246
+ const safePath = (currentNode.relativePath || currentNode.path).replace(/\\/g, '\\\\');
247
+ clickAction = `onclick="window.openFileTab('${safePath}', '${currentNode.name}')"`;
201
248
  cursorStyle = 'cursor: pointer; color: var(--text-primary);';
202
249
  }
203
250
 
204
251
  // Build HTML
205
252
  let html = `<li class="${liClass}">`;
206
253
 
254
+ // Check if this path should be excluded (unchecked)
255
+ const nodePath = currentNode.relativePath || currentNode.path;
256
+ const isExcluded = excludedPaths.has(nodePath);
257
+
207
258
  html += `
208
259
  <div class="tree-item-row" ${isDir ? clickAction : ''}>
209
260
  <span class="arrow">${arrow}</span>
210
261
  <input type="checkbox" class="tree-checkbox"
211
- data-path="${node.relativePath || node.path}"
262
+ data-path="${nodePath}"
212
263
  data-tokens="${tokens}"
213
- data-type="${node.type}"
214
- checked
264
+ data-type="${currentNode.type}"
265
+ ${isExcluded ? '' : 'checked'}
215
266
  onclick="handleCheckboxChange(event)">
216
267
  <span class="tree-icon">${icon}</span>
217
- <span class="tree-name" style="${cursorStyle}" ${!isDir ? clickAction : ''}>${node.name}</span>
268
+ <span class="tree-name" style="${cursorStyle}" ${!isDir ? clickAction : ''} title="${displayName}">${displayName}</span>
218
269
  <span class="token-badge ${tokenClass}">${formatNumber(tokens)}</span>
219
270
  </div>
220
271
  `;
221
272
 
222
- // Children recursion
273
+ // Children recursion (using deepest node's children)
223
274
  if (hasChildren) {
224
275
  html += '<ul class="tree-ul">';
225
276
  // Sort: Folders first, then files
226
- node.children.forEach(child => {
277
+ currentNode.children.forEach(child => {
227
278
  html += generateTreeHtml(child);
228
279
  });
229
280
  html += '</ul>';
@@ -233,3 +284,45 @@ function generateTreeHtml(node) {
233
284
 
234
285
  return isDir ? `<ul class="tree-ul">${html}</ul>` : html;
235
286
  }
287
+
288
+ /**
289
+ * Load tree state from backend
290
+ */
291
+ async function loadTreeState() {
292
+ try {
293
+ const data = await apiLoadTreeState();
294
+ excludedPaths = new Set(data.excludedPaths || []);
295
+ console.log(`Loaded tree state: ${excludedPaths.size} excluded items`);
296
+ } catch (err) {
297
+ console.error('Failed to load tree state:', err);
298
+ excludedPaths = new Set(); // Reset to empty on error
299
+ }
300
+ }
301
+
302
+ /**
303
+ * Debounced save - waits 500ms after last change before saving
304
+ */
305
+ function debouncedSaveState() {
306
+ // Clear existing timeout
307
+ if (saveStateTimeout) {
308
+ clearTimeout(saveStateTimeout);
309
+ }
310
+
311
+ // Set new timeout
312
+ saveStateTimeout = setTimeout(() => {
313
+ saveTreeState();
314
+ }, 500);
315
+ }
316
+
317
+ /**
318
+ * Save current tree state to backend
319
+ */
320
+ async function saveTreeState() {
321
+ try {
322
+ const excludedArray = Array.from(excludedPaths);
323
+ await apiSaveTreeState(excludedArray);
324
+ console.log(`Saved tree state: ${excludedArray.length} excluded items`);
325
+ } catch (err) {
326
+ console.error('Failed to save tree state:', err);
327
+ }
328
+ }
@@ -8,6 +8,7 @@ import { initGitView } from './features/git-view.js';
8
8
  import { initTerminal, createNewTerminal } from './features/terminal.js';
9
9
  import { initEditorTabs } from './features/editor-tabs.js';
10
10
  import { initMonaco, updateMonacoTheme } from './features/monaco-manager.js';
11
+ import { initResizeHandler } from './features/resize.js';
11
12
 
12
13
  document.addEventListener('DOMContentLoaded', async () => {
13
14
  // Load system prompt text
@@ -34,9 +35,13 @@ document.addEventListener('DOMContentLoaded', async () => {
34
35
  // Initialize Terminal System
35
36
  initTerminal();
36
37
 
38
+ // Initialize Monaco & Tabs
37
39
  // Initialize Monaco & Tabs
38
40
  initMonaco();
39
41
  initEditorTabs();
42
+
43
+ // Initialize Resize Handler
44
+ initResizeHandler();
40
45
 
41
46
  // Set default tab to AI Assistant
42
47
  if (window.switchTab) {