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
@@ -22,16 +22,10 @@ class ApiServer {
22
22
  constructor(port = 6868) {
23
23
  this.port = port;
24
24
  this.app = express();
25
-
26
- // Create HTTP server for Socket.IO
27
25
  this.httpServer = http.createServer(this.app);
28
- this.io = new Server(this.httpServer, {
29
- cors: { origin: "*" }
30
- });
31
-
32
- this.server = null;
33
- this.workingDir = process.cwd(); // Fallback for backward compatibility
34
- this.projectManager = projectManager; // Reference to singleton
26
+ this.io = new Server(this.httpServer, { cors: { origin: "*" } });
27
+ this.workingDir = process.cwd();
28
+ this.projectManager = projectManager;
35
29
  this.setupMiddleware();
36
30
  this.setupRoutes();
37
31
  this.setupSocketIO();
@@ -42,21 +36,14 @@ class ApiServer {
42
36
  this.app.use(bodyParser.json({ limit: '50mb' }));
43
37
  this.app.use(bodyParser.urlencoded({ extended: true, limit: '50mb' }));
44
38
  this.app.use(express.static(path.join(__dirname, 'views')));
39
+ this.app.use('/dist', express.static(path.join(__dirname, '../../dist')));
45
40
 
46
- // Project context middleware
47
41
  this.app.use((req, res, next) => {
48
42
  const activeProject = this.projectManager.getActiveProject();
49
43
  req.projectContext = activeProject;
50
44
  req.workingDir = activeProject ? activeProject.workingDir : this.workingDir;
51
45
  next();
52
46
  });
53
-
54
- this.app.use((req, res, next) => {
55
- if (!req.path.includes('.')) {
56
- console.log(chalk.blue(`[REQ] ${req.method} ${req.path}`));
57
- }
58
- next();
59
- });
60
47
  }
61
48
 
62
49
  setupSocketIO() {
@@ -64,44 +51,21 @@ class ApiServer {
64
51
  socket.on('terminal:init', (data) => {
65
52
  if (!data || !data.termId) return;
66
53
  const { termId, cols, rows, projectId } = data;
67
-
68
- // Get working directory from project context
69
54
  let cwd;
70
55
  if (projectId) {
71
- // Use specific project
72
56
  const projects = this.projectManager.getAllProjects();
73
57
  const project = projects.find(p => p.id === projectId);
74
58
  cwd = project ? project.workingDir : this.workingDir;
75
59
  } else {
76
- // Use active project
77
60
  const activeProject = this.projectManager.getActiveProject();
78
61
  cwd = activeProject ? activeProject.workingDir : this.workingDir;
79
62
  }
80
-
81
63
  terminalManager.createTerminal(socket, termId, cols, rows, cwd, projectId);
82
64
  });
83
-
84
- socket.on('terminal:input', (data) => {
85
- if (data && data.termId) {
86
- terminalManager.write(data.termId, data.data);
87
- }
88
- });
89
-
90
- socket.on('terminal:resize', (data) => {
91
- if (data && data.termId) {
92
- terminalManager.resize(data.termId, data.cols, data.rows);
93
- }
94
- });
95
-
96
- socket.on('terminal:kill', (data) => {
97
- if (data && data.termId) {
98
- terminalManager.kill(data.termId);
99
- }
100
- });
101
-
102
- socket.on('disconnect', () => {
103
- terminalManager.cleanupSocket(socket.id);
104
- });
65
+ socket.on('terminal:input', (data) => { if (data && data.termId) terminalManager.write(data.termId, data.data); });
66
+ socket.on('terminal:resize', (data) => { if (data && data.termId) terminalManager.resize(data.termId, data.cols, data.rows); });
67
+ socket.on('terminal:kill', (data) => { if (data && data.termId) terminalManager.kill(data.termId); });
68
+ socket.on('disconnect', () => { terminalManager.cleanupSocket(socket.id); });
105
69
  });
106
70
  }
107
71
 
@@ -109,82 +73,106 @@ class ApiServer {
109
73
  this.app.get('/', (req, res) => res.sendFile(path.join(__dirname, 'views', 'dashboard.html')));
110
74
  this.app.get('/health', (req, res) => res.json({ status: 'ok', version: packageJson.version }));
111
75
 
112
- this.app.get('/api/extension-path', (req, res) => {
113
- try {
114
- const extensionPath = path.join(__dirname, 'views', 'vg-coder');
115
- res.json({ path: extensionPath, exists: fs.existsSync(extensionPath) });
116
- } catch (error) {
117
- res.status(500).json({ error: error.message });
118
- }
76
+ // File Ops
77
+ this.app.get('/api/read-file', async (req, res) => {
78
+ try {
79
+ const filePath = req.query.path;
80
+ const resolvedPath = path.resolve(req.workingDir, filePath);
81
+ if (!await fs.pathExists(resolvedPath)) return res.status(404).json({ error: 'File not found' });
82
+ const content = await fs.readFile(resolvedPath, 'utf8');
83
+ res.json({ content, path: filePath });
84
+ } catch (error) { res.status(500).json({ error: error.message }); }
85
+ });
86
+
87
+ this.app.post('/api/save-file', async (req, res) => {
88
+ try {
89
+ const { path: filePath, content } = req.body;
90
+ const resolvedPath = path.resolve(req.workingDir, filePath);
91
+ await fs.writeFile(resolvedPath, content, 'utf8');
92
+ res.json({ success: true });
93
+ } catch (error) { res.status(500).json({ error: error.message }); }
94
+ });
95
+
96
+ // Bash Execute
97
+ this.app.post('/api/execute', async (req, res) => {
98
+ const { bash } = req.body;
99
+ const executor = new BashExecutor(req.workingDir);
100
+ const result = await executor.execute(bash);
101
+ res.status(result.success ? 200 : 400).json(result);
119
102
  });
120
103
 
121
- // --- MULTI-PROJECT MANAGEMENT API ---
104
+ // Structure API
105
+ this.app.get('/api/structure', async (req, res) => {
106
+ const projectPath = req.query.path || '.';
107
+ const resolvedPath = path.resolve(req.workingDir, projectPath);
108
+ const scanner = new FileScanner(resolvedPath);
109
+ const scanResult = await scanner.scanProject();
110
+ const tokenManager = new TokenManager();
111
+ const enrichedTree = tokenManager.analyzeTree(scanResult.tree, scanResult.files);
112
+ res.json({ path: resolvedPath, structure: enrichedTree });
113
+ });
114
+
115
+ // Info API
116
+ this.app.get('/api/info', async (req, res) => {
117
+ const projectPath = req.query.path || '.';
118
+ const resolvedPath = path.resolve(req.workingDir, projectPath);
119
+ const detector = new ProjectDetector(resolvedPath);
120
+ const projectInfo = await detector.detectAll();
121
+ res.json({ path: resolvedPath, primaryType: projectInfo.primary });
122
+ });
123
+
124
+ // Analyze API
125
+ this.app.post('/api/analyze', async (req, res) => {
126
+ const { path: projectPath, options = {}, specificFiles } = req.body;
127
+ const resolvedPath = path.resolve(req.workingDir, projectPath);
128
+ const scanner = new FileScanner(resolvedPath, {
129
+ extensions: options.extensions ? options.extensions.split(',') : undefined,
130
+ includeHidden: options.includeHidden
131
+ });
132
+ let scanResult = await scanner.scanProject();
133
+ let filesToProcess = scanResult.files;
134
+ if (specificFiles?.length) filesToProcess = filesToProcess.filter(f => specificFiles.includes(f.relativePath));
135
+ const content = await scanner.createCombinedContentForAI(filesToProcess, { includeStats: true, preserveLineNumbers: true });
136
+ res.send(content);
137
+ });
122
138
 
139
+ // --- PROJECT MANAGEMENT APIS (FIXED) ---
140
+
123
141
  // List all projects
124
142
  this.app.get('/api/projects', (req, res) => {
125
143
  try {
126
144
  const projects = this.projectManager.getAllProjects();
127
145
  const activeProject = this.projectManager.getActiveProject();
128
- res.json({
129
- projects,
130
- activeProjectId: activeProject ? activeProject.id : null,
131
- totalProjects: projects.length
132
- });
133
- } catch (error) {
134
- res.status(500).json({ error: error.message });
135
- }
146
+ res.json({ projects, activeProjectId: activeProject ? activeProject.id : null, totalProjects: projects.length });
147
+ } catch (error) { res.status(500).json({ error: error.message }); }
136
148
  });
137
149
 
138
- // Register new project (for follower join)
150
+ // Register new project (Required for join)
139
151
  this.app.post('/api/projects/register', (req, res) => {
140
152
  try {
141
153
  const { workingDir, name } = req.body;
142
- if (!workingDir) {
143
- return res.status(400).json({ error: 'Missing workingDir' });
144
- }
154
+ if (!workingDir) return res.status(400).json({ error: 'Missing workingDir' });
145
155
 
146
156
  const projectId = this.projectManager.registerProject(workingDir);
147
157
  const projects = this.projectManager.getAllProjects();
148
158
 
149
- // Emit socket event to notify all clients about new project
150
- this.io.emit('project:registered', { projectId, name });
159
+ this.io.emit('project:registered', { projectId, name: name || path.basename(workingDir) });
151
160
 
152
- res.json({
153
- success: true,
154
- projectId,
155
- totalProjects: projects.length
156
- });
157
- } catch (error) {
158
- res.status(500).json({ error: error.message });
159
- }
161
+ res.json({ success: true, projectId, totalProjects: projects.length });
162
+ } catch (error) { res.status(500).json({ error: error.message }); }
160
163
  });
161
164
 
162
165
  // Switch active project
163
166
  this.app.post('/api/projects/switch', (req, res) => {
164
167
  try {
165
168
  const { projectId } = req.body;
166
- if (!projectId) {
167
- return res.status(400).json({ error: 'Missing projectId' });
168
- }
169
-
170
169
  const success = this.projectManager.switchProject(projectId);
171
-
172
170
  if (success) {
173
171
  const activeProject = this.projectManager.getActiveProject();
174
-
175
- // Emit socket event to notify all clients
176
- this.io.emit('project:switched', {
177
- projectId,
178
- projectName: activeProject.name
179
- });
180
-
172
+ this.io.emit('project:switched', { projectId, projectName: activeProject.name });
181
173
  res.json({ success: true, project: activeProject });
182
- } else {
183
- res.status(404).json({ error: 'Project not found' });
184
- }
185
- } catch (error) {
186
- res.status(500).json({ error: error.message });
187
- }
174
+ } else res.status(404).json({ error: 'Project not found' });
175
+ } catch (error) { res.status(500).json({ error: error.message }); }
188
176
  });
189
177
 
190
178
  // Remove project
@@ -192,160 +180,26 @@ class ApiServer {
192
180
  try {
193
181
  const projectId = req.params.id;
194
182
  this.projectManager.removeProject(projectId);
195
-
196
- // Emit socket event
197
183
  this.io.emit('project:removed', { projectId });
198
-
199
184
  res.json({ success: true });
200
- } catch (error) {
201
- res.status(500).json({ error: error.message });
202
- }
203
- });
204
-
205
-
206
- // --- FILE OPERATIONS (NEW) ---
207
-
208
- // Read raw file content
209
- this.app.get('/api/read-file', async (req, res) => {
210
- try {
211
- const filePath = req.query.path;
212
- if (!filePath) return res.status(400).json({ error: 'Missing path' });
213
-
214
- // Prevent directory traversal (basic check)
215
- const resolvedPath = path.resolve(req.workingDir, filePath);
216
- if (!resolvedPath.startsWith(req.workingDir)) {
217
- // Allow reading but log warning - in dev tool we might want flexibility
218
- // For strict mode: return res.status(403).json({ error: 'Access denied' });
219
- }
220
-
221
- if (!await fs.pathExists(resolvedPath)) {
222
- return res.status(404).json({ error: 'File not found' });
223
- }
224
-
225
- const content = await fs.readFile(resolvedPath, 'utf8');
226
- res.json({ content, path: filePath });
227
- } catch (error) {
228
- res.status(500).json({ error: error.message });
229
- }
185
+ } catch (error) { res.status(500).json({ error: error.message }); }
230
186
  });
231
187
 
232
- // Save file content
233
- this.app.post('/api/save-file', async (req, res) => {
234
- try {
235
- const { path: filePath, content } = req.body;
236
- if (!filePath || content === undefined) return res.status(400).json({ error: 'Missing data' });
237
-
238
- const resolvedPath = path.resolve(req.workingDir, filePath);
239
-
240
- // Security check
241
- if (!resolvedPath.startsWith(req.workingDir)) {
242
- // For strict mode: return res.status(403).json({ error: 'Access denied' });
243
- }
244
-
245
- await fs.writeFile(resolvedPath, content, 'utf8');
246
- res.json({ success: true });
247
- } catch (error) {
248
- res.status(500).json({ error: error.message });
249
- }
250
- });
251
-
252
- // --- GIT API START ---
253
-
254
- // Get Git Status
188
+ // Git
255
189
  this.app.get('/api/git/status', async (req, res) => {
256
190
  try {
257
191
  const { stdout } = await execAsync('git status --porcelain -u', { cwd: req.workingDir });
258
-
259
- const staged = [];
260
- const unstaged = [];
261
- const untracked = [];
262
-
263
- const lines = stdout.split('\n').filter(l => l.trim());
264
-
265
- lines.forEach(line => {
266
- const x = line[0];
267
- const y = line[1];
268
- const path = line.substring(3);
269
-
270
- if (x !== ' ' && x !== '?') {
271
- staged.push({ path, status: x });
272
- }
273
-
274
- if (y !== ' ') {
275
- if (x === '?' && y === '?') {
276
- untracked.push({ path, status: 'U' });
277
- } else {
278
- unstaged.push({ path, status: y });
279
- }
280
- }
192
+ const staged = [], unstaged = [], untracked = [];
193
+ stdout.split('\n').filter(l=>l).forEach(line => {
194
+ const x = line[0], y = line[1], path = line.substring(3);
195
+ if (x !== ' ' && x !== '?') staged.push({ path, status: x });
196
+ if (y !== ' ') (x === '?' && y === '?') ? untracked.push({ path, status: 'U' }) : unstaged.push({ path, status: y });
281
197
  });
282
-
283
- const changes = [...unstaged, ...untracked];
284
- res.json({ staged, changes });
285
- } catch (error) {
286
- console.error(chalk.red('āŒ [GIT STATUS] Error:'), error.message);
287
- res.status(500).json({ error: error.message });
288
- }
289
- });
290
-
291
- // Git Stage
292
- this.app.post('/api/git/stage', async (req, res) => {
293
- try {
294
- const { files } = req.body;
295
- const target = files.includes('*') ? '.' : files.map(f => `"${f}"`).join(' ');
296
- await execAsync(`git add ${target}`, { cwd: req.workingDir });
297
- res.json({ success: true });
298
- } catch (error) {
299
- res.status(500).json({ error: error.message });
300
- }
301
- });
302
-
303
- // Git Unstage
304
- this.app.post('/api/git/unstage', async (req, res) => {
305
- try {
306
- const { files } = req.body;
307
- const target = files.includes('*') ? '' : files.map(f => `"${f}"`).join(' ');
308
- await execAsync(`git reset HEAD ${target}`, { cwd: req.workingDir });
309
- res.json({ success: true });
310
- } catch (error) {
311
- res.status(500).json({ error: error.message });
312
- }
198
+ res.json({ staged, changes: [...unstaged, ...untracked] });
199
+ } catch (error) { res.status(500).json({ error: error.message }); }
313
200
  });
314
201
 
315
- // Git Discard
316
- this.app.post('/api/git/discard', async (req, res) => {
317
- try {
318
- const { files } = req.body;
319
- if (files.includes('*')) {
320
- try { await execAsync('git restore .', { cwd: req.workingDir }); } catch (e) {}
321
- try { await execAsync('git clean -fd', { cwd: req.workingDir }); } catch (e) {}
322
- } else {
323
- for (const file of files) {
324
- try { await execAsync(`git restore "${file}"`, { cwd: req.workingDir }); } catch (e) {}
325
- try { await execAsync(`git clean -f "${file}"`, { cwd: req.workingDir }); } catch (e) {}
326
- }
327
- }
328
- res.json({ success: true });
329
- } catch (error) {
330
- console.error('Discard error:', error);
331
- res.status(500).json({ error: error.message });
332
- }
333
- });
334
-
335
- // Git Commit
336
- this.app.post('/api/git/commit', async (req, res) => {
337
- try {
338
- const { message } = req.body;
339
- if (!message) throw new Error('Commit message is required');
340
- const safeMessage = message.replace(/"/g, '\\"');
341
- await execAsync(`git commit -m "${safeMessage}"`, { cwd: req.workingDir });
342
- res.json({ success: true });
343
- } catch (error) {
344
- res.status(500).json({ error: error.message });
345
- }
346
- });
347
-
348
- // Get Diff
202
+ // Git Diff
349
203
  this.app.get('/api/git/diff', async (req, res) => {
350
204
  try {
351
205
  const file = req.query.file;
@@ -363,6 +217,7 @@ class ApiServer {
363
217
  if (stat.isDirectory()) return res.json({ diff: '' });
364
218
  }
365
219
  } catch (e) {}
220
+
366
221
  const { stdout: isUntracked } = await execAsync(`git ls-files --others --exclude-standard "${file}"`, { cwd: req.workingDir });
367
222
  if (isUntracked.trim()) {
368
223
  const content = await fs.readFile(path.join(req.workingDir, file), 'utf8');
@@ -383,194 +238,56 @@ class ApiServer {
383
238
  }
384
239
  });
385
240
 
386
- // --- TERMINAL LOG API ---
387
-
388
- // Get terminal logs
389
- this.app.get('/api/terminal/:termId/logs', (req, res) => {
241
+ // Tree State API
242
+ this.app.post('/api/tree-state/save', async (req, res) => {
390
243
  try {
391
- const { termId } = req.params;
392
- const logs = terminalManager.getLogBuffer(termId);
393
-
394
- res.json({
395
- termId,
396
- logs,
397
- totalLines: logs.length
398
- });
399
- } catch (error) {
400
- console.error(chalk.red('āŒ [TERMINAL LOGS] Error:'), error.message);
401
- res.status(500).json({ error: error.message });
402
- }
244
+ const { excludedPaths } = req.body;
245
+ const vgDir = path.join(req.workingDir, '.vg');
246
+ await fs.ensureDir(vgDir);
247
+ await fs.writeJson(path.join(vgDir, 'tree-state.json'), { excludedPaths }, { spaces: 2 });
248
+ res.json({ success: true, count: excludedPaths.length });
249
+ } catch (error) { res.status(500).json({ error: error.message }); }
403
250
  });
404
251
 
405
- // Analyze terminal logs
406
- this.app.post('/api/terminal/:termId/analyze', (req, res) => {
252
+ this.app.get('/api/tree-state/load', async (req, res) => {
407
253
  try {
408
- const { termId } = req.params;
409
- const analysis = terminalManager.analyzeLogBuffer(termId);
410
-
411
- res.json({
412
- termId,
413
- ...analysis
414
- });
415
- } catch (error) {
416
- console.error(chalk.red('āŒ [TERMINAL ANALYZE] Error:'), error.message);
417
- res.status(500).json({ error: error.message });
418
- }
254
+ const stateFile = path.join(req.workingDir, '.vg', 'tree-state.json');
255
+ if (!await fs.pathExists(stateFile)) return res.json({ excludedPaths: [] });
256
+ const data = await fs.readJson(stateFile);
257
+ res.json({ excludedPaths: data.excludedPaths || [] });
258
+ } catch (error) { res.status(500).json({ error: error.message }); }
419
259
  });
420
260
 
421
- // --- SAVED COMMANDS API ---
422
-
423
- // Load saved commands
261
+ // Commands
424
262
  this.app.get('/api/commands/load', async (req, res) => {
425
263
  try {
426
264
  const commandsFile = path.join(req.workingDir, '.vg', 'commands.json');
427
-
428
- if (!await fs.pathExists(commandsFile)) {
429
- return res.json({ commands: [] });
430
- }
431
-
265
+ if (!await fs.pathExists(commandsFile)) return res.json({ commands: [] });
432
266
  const data = await fs.readJson(commandsFile);
433
267
  res.json({ commands: data.commands || [] });
434
- } catch (error) {
435
- console.error(chalk.red('āŒ [COMMANDS LOAD] Error:'), error.message);
436
- res.json({ commands: [] });
437
- }
268
+ } catch (error) { res.status(500).json({ error: error.message }); }
438
269
  });
439
270
 
440
- // Save commands
441
271
  this.app.post('/api/commands/save', async (req, res) => {
442
272
  try {
443
273
  const { commands } = req.body;
444
- if (!Array.isArray(commands)) {
445
- return res.status(400).json({ error: 'commands must be an array' });
446
- }
447
-
448
274
  const vgDir = path.join(req.workingDir, '.vg');
449
275
  await fs.ensureDir(vgDir);
450
-
451
- const commandsFile = path.join(vgDir, 'commands.json');
452
- await fs.writeJson(commandsFile, { commands }, { spaces: 2 });
453
-
454
- console.log(chalk.green(`āœ“ Saved ${commands.length} commands`));
276
+ await fs.writeJson(path.join(vgDir, 'commands.json'), { commands }, { spaces: 2 });
455
277
  res.json({ success: true, count: commands.length });
456
- } catch (error) {
457
- console.error(chalk.red('āŒ [COMMANDS SAVE] Error:'), error.message);
458
- res.status(500).json({ error: error.message });
459
- }
460
- });
461
-
462
- // --- TREE STATE API ---
463
-
464
- // Save tree state (excluded paths)
465
- this.app.post('/api/tree-state/save', async (req, res) => {
466
- try {
467
- const { excludedPaths } = req.body;
468
- if (!Array.isArray(excludedPaths)) {
469
- return res.status(400).json({ error: 'excludedPaths must be an array' });
470
- }
471
-
472
- // Create .vg directory if it doesn't exist
473
- const vgDir = path.join(req.workingDir, '.vg');
474
- await fs.ensureDir(vgDir);
475
-
476
- // Save state to .vg/tree-state.json
477
- const stateFile = path.join(vgDir, 'tree-state.json');
478
- await fs.writeJson(stateFile, { excludedPaths }, { spaces: 2 });
479
-
480
- console.log(chalk.green(`āœ“ Saved tree state: ${excludedPaths.length} excluded items`));
481
- res.json({ success: true, count: excludedPaths.length });
482
- } catch (error) {
483
- console.error(chalk.red('āŒ [TREE STATE SAVE] Error:'), error.message);
484
- res.status(500).json({ error: error.message });
485
- }
486
- });
487
-
488
- // Load tree state
489
- this.app.get('/api/tree-state/load', async (req, res) => {
490
- try {
491
- const stateFile = path.join(req.workingDir, '.vg', 'tree-state.json');
492
-
493
- // Check if state file exists
494
- if (!await fs.pathExists(stateFile)) {
495
- return res.json({ excludedPaths: [] });
496
- }
497
-
498
- // Read and return state
499
- const state = await fs.readJson(stateFile);
500
- res.json({ excludedPaths: state.excludedPaths || [] });
501
- } catch (error) {
502
- console.error(chalk.red('āŒ [TREE STATE LOAD] Error:'), error.message);
503
- // Return empty state on error instead of failing
504
- res.json({ excludedPaths: [] });
505
- }
506
- });
507
-
508
- // --- GENERAL API ---
509
-
510
- this.app.post('/api/analyze', async (req, res) => {
511
- const { path: projectPath, options = {}, specificFiles } = req.body;
512
- if (!projectPath) return res.status(400).json({ error: 'Missing path' });
513
- const resolvedPath = path.resolve(req.workingDir, projectPath);
514
- if (!await fs.pathExists(resolvedPath)) return res.status(404).json({ error: 'Path not found' });
515
- const scanner = new FileScanner(resolvedPath, {
516
- extensions: options.extensions ? options.extensions.split(',') : undefined,
517
- includeHidden: options.includeHidden
518
- });
519
- let scanResult = await scanner.scanProject();
520
- let filesToProcess = scanResult.files;
521
- if (specificFiles?.length) filesToProcess = filesToProcess.filter(f => specificFiles.includes(f.relativePath));
522
- const content = await scanner.createCombinedContentForAI(filesToProcess, { includeStats: true, preserveLineNumbers: true });
523
- res.send(content);
524
- });
525
-
526
- this.app.get('/api/info', async (req, res) => {
527
- const projectPath = req.query.path || '.';
528
- const resolvedPath = path.resolve(req.workingDir, projectPath);
529
- const detector = new ProjectDetector(resolvedPath);
530
- const projectInfo = await detector.detectAll();
531
- const scanner = new FileScanner(resolvedPath);
532
- const scanResult = await scanner.scanProject();
533
- res.json({ path: resolvedPath, primaryType: projectInfo.primary, stats: { totalFiles: scanResult.files.length } });
534
- });
535
-
536
- this.app.get('/api/structure', async (req, res) => {
537
- const projectPath = req.query.path || '.';
538
- const resolvedPath = path.resolve(req.workingDir, projectPath);
539
- const scanner = new FileScanner(resolvedPath);
540
- const scanResult = await scanner.scanProject();
541
- const tokenManager = new TokenManager();
542
- const enrichedTree = tokenManager.analyzeTree(scanResult.tree, scanResult.files);
543
- res.json({ path: resolvedPath, structure: enrichedTree });
278
+ } catch (error) { res.status(500).json({ error: error.message }); }
544
279
  });
545
280
 
546
- this.app.post('/api/execute', async (req, res) => {
547
- const { bash } = req.body;
548
- const executor = new BashExecutor(req.workingDir);
549
- const result = await executor.execute(bash);
550
- res.status(result.success ? 200 : 400).json(result);
281
+ this.app.get('/api/extension-path', (req, res) => {
282
+ try {
283
+ const extensionPath = path.join(__dirname, 'views', 'vg-coder');
284
+ res.json({ path: extensionPath, exists: fs.existsSync(extensionPath) });
285
+ } catch (error) { res.status(500).json({ error: error.message }); }
551
286
  });
552
287
 
553
- this.app.delete('/api/clean', async (req, res) => {
554
- await fs.remove(path.resolve(req.body.output));
555
- res.json({ success: true });
556
- });
557
-
558
- // Shutdown server endpoint
559
288
  this.app.post('/api/shutdown', async (req, res) => {
560
- try {
561
- console.log(chalk.yellow('\nšŸ›‘ Shutdown requested via API...'));
562
-
563
- // Send response first
564
- res.json({ success: true, message: 'Server shutting down...' });
565
-
566
- // Give time for response to be sent
567
- setTimeout(async () => {
568
- await this.stop();
569
- process.exit(0);
570
- }, 500);
571
- } catch (error) {
572
- res.status(500).json({ error: error.message });
573
- }
289
+ res.json({ success: true });
290
+ setTimeout(async () => { await this.stop(); process.exit(0); }, 500);
574
291
  });
575
292
  }
576
293
 
@@ -579,7 +296,6 @@ class ApiServer {
579
296
  const tryPort = (port) => {
580
297
  const onError = (e) => {
581
298
  if (e.code === 'EADDRINUSE') {
582
- console.log(chalk.yellow(`āš ļø Port ${port} is busy, trying ${port + 1}...`));
583
299
  this.httpServer.close();
584
300
  tryPort(port + 1);
585
301
  } else {
@@ -587,46 +303,22 @@ class ApiServer {
587
303
  reject(e);
588
304
  }
589
305
  };
590
-
591
306
  this.httpServer.once('error', onError);
592
-
593
307
  this.server = this.httpServer.listen(port, () => {
594
308
  this.httpServer.removeListener('error', onError);
595
-
596
- // Update actual port
597
309
  this.port = this.server.address().port;
598
-
599
- const projectName = path.basename(this.workingDir);
600
- const startTime = new Date().toLocaleString();
601
-
602
- console.log(chalk.green('\n──────────────────────────────────────────────────'));
603
- console.log(`šŸš€ ${chalk.bold('VG Coder Server')} ${chalk.green('ā— Online')}`);
604
- console.log(chalk.gray('──────────────────────────────────────────────────'));
605
- console.log(`šŸ“ Project: ${chalk.cyan(projectName)}`);
606
- console.log(`ā° Started: ${chalk.yellow(startTime)}`);
607
- console.log(`šŸ“” URL: ${chalk.blue(`http://localhost:${this.port}`)}`);
608
- console.log(chalk.green('──────────────────────────────────────────────────\n'));
609
-
310
+ console.log(chalk.green(`šŸš€ Server Online: http://localhost:${this.port}`));
311
+ console.log(chalk.blue(`šŸ“¦ Dist served at: http://localhost:${this.port}/dist`));
610
312
  resolve();
611
313
  });
612
314
  };
613
-
614
315
  tryPort(this.port);
615
316
  });
616
317
  }
617
318
 
618
319
  async stop() {
619
- console.log(chalk.yellow('Stopping server...'));
620
-
621
- // Release leader lock
622
320
  await this.projectManager.releaseLock();
623
-
624
- // Close server
625
- if (this.server) {
626
- this.server.close();
627
- }
628
-
629
- console.log(chalk.green('āœ“ Server stopped'));
321
+ if (this.server) this.server.close();
630
322
  }
631
323
  }
632
324