vg-coder-cli 2.0.24 ā 2.0.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.vg/tree-state.json +1 -7
- package/package.json +1 -1
- package/src/index.js +36 -0
- package/src/server/api-server.js +241 -29
- package/src/server/project-manager.js +353 -0
- package/src/server/terminal-manager.js +130 -3
- package/src/server/views/css/iframe.css +1 -0
- package/src/server/views/css/terminal.css +88 -0
- package/src/server/views/dashboard.css +391 -16
- package/src/server/views/dashboard.html +150 -19
- package/src/server/views/js/features/commands.js +230 -0
- package/src/server/views/js/features/iframe-manager.js +6 -2
- package/src/server/views/js/features/project-switcher.js +153 -0
- package/src/server/views/js/features/terminal.js +280 -8
- package/src/server/views/js/main.js +65 -0
- package/src/server/views/js/utils/log-utils.js +164 -0
- package/src/server/views/js/utils/smart-copy-engine.js +283 -0
- package/src/server/views/vg-coder/controller.js +376 -2
- package/vetgo-auto/chrome/src/controller.ts +75 -1
- package/vetgo-auto/chrome/src/utils/ai-domains.ts +33 -0
- package/vetgo-auto/chrome/src/utils/injector-script.ts +251 -0
- package/vetgo-auto/vg-coder.zip +0 -0
- package/vg-coder-cli-2.0.26.tgz +0 -0
- package/vg-coder-cli-2.0.23.tgz +0 -0
- package/vg-coder-cli-2.0.24.tgz +0 -0
package/.vg/tree-state.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vg-coder-cli",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.26",
|
|
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": {
|
package/src/index.js
CHANGED
|
@@ -372,11 +372,43 @@ class VGCoderCLI {
|
|
|
372
372
|
*/
|
|
373
373
|
async handleStart(options) {
|
|
374
374
|
try {
|
|
375
|
+
const projectPath = process.cwd();
|
|
375
376
|
const initialPort = parseInt(options.port);
|
|
377
|
+
const projectManager = require('./server/project-manager');
|
|
378
|
+
|
|
379
|
+
// Check if leader exists
|
|
380
|
+
const leaderInfo = await projectManager.checkLeader();
|
|
381
|
+
|
|
382
|
+
if (leaderInfo) {
|
|
383
|
+
// Leader exists - join as follower
|
|
384
|
+
console.log(chalk.blue(`\nš Found existing server at port ${leaderInfo.port}`));
|
|
385
|
+
const joined = await projectManager.joinLeader(leaderInfo, projectPath);
|
|
386
|
+
|
|
387
|
+
if (joined) {
|
|
388
|
+
// Successfully joined, no need to start new server
|
|
389
|
+
return;
|
|
390
|
+
} else {
|
|
391
|
+
// Failed to join, fallback to starting new server
|
|
392
|
+
console.log(chalk.yellow('ā ļø Failed to join leader, starting new server...'));
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// No leader or failed to join - become leader
|
|
397
|
+
console.log(chalk.blue('\nš No existing server found, becoming leader...'));
|
|
398
|
+
|
|
376
399
|
const server = new ApiServer(initialPort);
|
|
377
400
|
|
|
401
|
+
// Try to acquire lock before starting
|
|
402
|
+
const lockAcquired = await projectManager.acquireLock(initialPort);
|
|
403
|
+
if (!lockAcquired) {
|
|
404
|
+
throw new Error('Failed to acquire leader lock');
|
|
405
|
+
}
|
|
406
|
+
|
|
378
407
|
await server.start();
|
|
379
408
|
|
|
409
|
+
// Register current project
|
|
410
|
+
server.projectManager.registerProject(projectPath);
|
|
411
|
+
|
|
380
412
|
// Auto-open browser to dashboard using actual port from server
|
|
381
413
|
const dashboardUrl = `http://localhost:${server.port}`;
|
|
382
414
|
const { exec } = require('child_process');
|
|
@@ -402,6 +434,10 @@ class VGCoderCLI {
|
|
|
402
434
|
// Handle graceful shutdown
|
|
403
435
|
const shutdown = async () => {
|
|
404
436
|
console.log(chalk.yellow('\n\nShutting down server...'));
|
|
437
|
+
|
|
438
|
+
// Release lock
|
|
439
|
+
await projectManager.releaseLock();
|
|
440
|
+
|
|
405
441
|
await server.stop();
|
|
406
442
|
process.exit(0);
|
|
407
443
|
};
|
package/src/server/api-server.js
CHANGED
|
@@ -16,6 +16,7 @@ const FileScanner = require('../scanner/file-scanner');
|
|
|
16
16
|
const TokenManager = require('../tokenizer/token-manager');
|
|
17
17
|
const BashExecutor = require('../utils/bash-executor');
|
|
18
18
|
const terminalManager = require('./terminal-manager');
|
|
19
|
+
const projectManager = require('./project-manager');
|
|
19
20
|
|
|
20
21
|
class ApiServer {
|
|
21
22
|
constructor(port = 6868) {
|
|
@@ -29,7 +30,8 @@ class ApiServer {
|
|
|
29
30
|
});
|
|
30
31
|
|
|
31
32
|
this.server = null;
|
|
32
|
-
this.workingDir = process.cwd();
|
|
33
|
+
this.workingDir = process.cwd(); // Fallback for backward compatibility
|
|
34
|
+
this.projectManager = projectManager; // Reference to singleton
|
|
33
35
|
this.setupMiddleware();
|
|
34
36
|
this.setupRoutes();
|
|
35
37
|
this.setupSocketIO();
|
|
@@ -41,6 +43,14 @@ class ApiServer {
|
|
|
41
43
|
this.app.use(bodyParser.urlencoded({ extended: true, limit: '50mb' }));
|
|
42
44
|
this.app.use(express.static(path.join(__dirname, 'views')));
|
|
43
45
|
|
|
46
|
+
// Project context middleware
|
|
47
|
+
this.app.use((req, res, next) => {
|
|
48
|
+
const activeProject = this.projectManager.getActiveProject();
|
|
49
|
+
req.projectContext = activeProject;
|
|
50
|
+
req.workingDir = activeProject ? activeProject.workingDir : this.workingDir;
|
|
51
|
+
next();
|
|
52
|
+
});
|
|
53
|
+
|
|
44
54
|
this.app.use((req, res, next) => {
|
|
45
55
|
if (!req.path.includes('.')) {
|
|
46
56
|
console.log(chalk.blue(`[REQ] ${req.method} ${req.path}`));
|
|
@@ -53,8 +63,22 @@ class ApiServer {
|
|
|
53
63
|
this.io.on('connection', (socket) => {
|
|
54
64
|
socket.on('terminal:init', (data) => {
|
|
55
65
|
if (!data || !data.termId) return;
|
|
56
|
-
const { termId, cols, rows } = data;
|
|
57
|
-
|
|
66
|
+
const { termId, cols, rows, projectId } = data;
|
|
67
|
+
|
|
68
|
+
// Get working directory from project context
|
|
69
|
+
let cwd;
|
|
70
|
+
if (projectId) {
|
|
71
|
+
// Use specific project
|
|
72
|
+
const projects = this.projectManager.getAllProjects();
|
|
73
|
+
const project = projects.find(p => p.id === projectId);
|
|
74
|
+
cwd = project ? project.workingDir : this.workingDir;
|
|
75
|
+
} else {
|
|
76
|
+
// Use active project
|
|
77
|
+
const activeProject = this.projectManager.getActiveProject();
|
|
78
|
+
cwd = activeProject ? activeProject.workingDir : this.workingDir;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
terminalManager.createTerminal(socket, termId, cols, rows, cwd, projectId);
|
|
58
82
|
});
|
|
59
83
|
|
|
60
84
|
socket.on('terminal:input', (data) => {
|
|
@@ -94,6 +118,91 @@ class ApiServer {
|
|
|
94
118
|
}
|
|
95
119
|
});
|
|
96
120
|
|
|
121
|
+
// --- MULTI-PROJECT MANAGEMENT API ---
|
|
122
|
+
|
|
123
|
+
// List all projects
|
|
124
|
+
this.app.get('/api/projects', (req, res) => {
|
|
125
|
+
try {
|
|
126
|
+
const projects = this.projectManager.getAllProjects();
|
|
127
|
+
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
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Register new project (for follower join)
|
|
139
|
+
this.app.post('/api/projects/register', (req, res) => {
|
|
140
|
+
try {
|
|
141
|
+
const { workingDir, name } = req.body;
|
|
142
|
+
if (!workingDir) {
|
|
143
|
+
return res.status(400).json({ error: 'Missing workingDir' });
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const projectId = this.projectManager.registerProject(workingDir);
|
|
147
|
+
const projects = this.projectManager.getAllProjects();
|
|
148
|
+
|
|
149
|
+
// Emit socket event to notify all clients about new project
|
|
150
|
+
this.io.emit('project:registered', { projectId, name });
|
|
151
|
+
|
|
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
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Switch active project
|
|
163
|
+
this.app.post('/api/projects/switch', (req, res) => {
|
|
164
|
+
try {
|
|
165
|
+
const { projectId } = req.body;
|
|
166
|
+
if (!projectId) {
|
|
167
|
+
return res.status(400).json({ error: 'Missing projectId' });
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const success = this.projectManager.switchProject(projectId);
|
|
171
|
+
|
|
172
|
+
if (success) {
|
|
173
|
+
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
|
+
|
|
181
|
+
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
|
+
}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// Remove project
|
|
191
|
+
this.app.delete('/api/projects/:id', (req, res) => {
|
|
192
|
+
try {
|
|
193
|
+
const projectId = req.params.id;
|
|
194
|
+
this.projectManager.removeProject(projectId);
|
|
195
|
+
|
|
196
|
+
// Emit socket event
|
|
197
|
+
this.io.emit('project:removed', { projectId });
|
|
198
|
+
|
|
199
|
+
res.json({ success: true });
|
|
200
|
+
} catch (error) {
|
|
201
|
+
res.status(500).json({ error: error.message });
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
|
|
97
206
|
// --- FILE OPERATIONS (NEW) ---
|
|
98
207
|
|
|
99
208
|
// Read raw file content
|
|
@@ -103,8 +212,8 @@ class ApiServer {
|
|
|
103
212
|
if (!filePath) return res.status(400).json({ error: 'Missing path' });
|
|
104
213
|
|
|
105
214
|
// Prevent directory traversal (basic check)
|
|
106
|
-
const resolvedPath = path.resolve(
|
|
107
|
-
if (!resolvedPath.startsWith(
|
|
215
|
+
const resolvedPath = path.resolve(req.workingDir, filePath);
|
|
216
|
+
if (!resolvedPath.startsWith(req.workingDir)) {
|
|
108
217
|
// Allow reading but log warning - in dev tool we might want flexibility
|
|
109
218
|
// For strict mode: return res.status(403).json({ error: 'Access denied' });
|
|
110
219
|
}
|
|
@@ -126,10 +235,10 @@ class ApiServer {
|
|
|
126
235
|
const { path: filePath, content } = req.body;
|
|
127
236
|
if (!filePath || content === undefined) return res.status(400).json({ error: 'Missing data' });
|
|
128
237
|
|
|
129
|
-
const resolvedPath = path.resolve(
|
|
238
|
+
const resolvedPath = path.resolve(req.workingDir, filePath);
|
|
130
239
|
|
|
131
240
|
// Security check
|
|
132
|
-
if (!resolvedPath.startsWith(
|
|
241
|
+
if (!resolvedPath.startsWith(req.workingDir)) {
|
|
133
242
|
// For strict mode: return res.status(403).json({ error: 'Access denied' });
|
|
134
243
|
}
|
|
135
244
|
|
|
@@ -145,7 +254,7 @@ class ApiServer {
|
|
|
145
254
|
// Get Git Status
|
|
146
255
|
this.app.get('/api/git/status', async (req, res) => {
|
|
147
256
|
try {
|
|
148
|
-
const { stdout } = await execAsync('git status --porcelain -u', { cwd:
|
|
257
|
+
const { stdout } = await execAsync('git status --porcelain -u', { cwd: req.workingDir });
|
|
149
258
|
|
|
150
259
|
const staged = [];
|
|
151
260
|
const unstaged = [];
|
|
@@ -184,7 +293,7 @@ class ApiServer {
|
|
|
184
293
|
try {
|
|
185
294
|
const { files } = req.body;
|
|
186
295
|
const target = files.includes('*') ? '.' : files.map(f => `"${f}"`).join(' ');
|
|
187
|
-
await execAsync(`git add ${target}`, { cwd:
|
|
296
|
+
await execAsync(`git add ${target}`, { cwd: req.workingDir });
|
|
188
297
|
res.json({ success: true });
|
|
189
298
|
} catch (error) {
|
|
190
299
|
res.status(500).json({ error: error.message });
|
|
@@ -196,7 +305,7 @@ class ApiServer {
|
|
|
196
305
|
try {
|
|
197
306
|
const { files } = req.body;
|
|
198
307
|
const target = files.includes('*') ? '' : files.map(f => `"${f}"`).join(' ');
|
|
199
|
-
await execAsync(`git reset HEAD ${target}`, { cwd:
|
|
308
|
+
await execAsync(`git reset HEAD ${target}`, { cwd: req.workingDir });
|
|
200
309
|
res.json({ success: true });
|
|
201
310
|
} catch (error) {
|
|
202
311
|
res.status(500).json({ error: error.message });
|
|
@@ -208,12 +317,12 @@ class ApiServer {
|
|
|
208
317
|
try {
|
|
209
318
|
const { files } = req.body;
|
|
210
319
|
if (files.includes('*')) {
|
|
211
|
-
try { await execAsync('git restore .', { cwd:
|
|
212
|
-
try { await execAsync('git clean -fd', { cwd:
|
|
320
|
+
try { await execAsync('git restore .', { cwd: req.workingDir }); } catch (e) {}
|
|
321
|
+
try { await execAsync('git clean -fd', { cwd: req.workingDir }); } catch (e) {}
|
|
213
322
|
} else {
|
|
214
323
|
for (const file of files) {
|
|
215
|
-
try { await execAsync(`git restore "${file}"`, { cwd:
|
|
216
|
-
try { await execAsync(`git clean -f "${file}"`, { cwd:
|
|
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) {}
|
|
217
326
|
}
|
|
218
327
|
}
|
|
219
328
|
res.json({ success: true });
|
|
@@ -229,7 +338,7 @@ class ApiServer {
|
|
|
229
338
|
const { message } = req.body;
|
|
230
339
|
if (!message) throw new Error('Commit message is required');
|
|
231
340
|
const safeMessage = message.replace(/"/g, '\\"');
|
|
232
|
-
await execAsync(`git commit -m "${safeMessage}"`, { cwd:
|
|
341
|
+
await execAsync(`git commit -m "${safeMessage}"`, { cwd: req.workingDir });
|
|
233
342
|
res.json({ success: true });
|
|
234
343
|
} catch (error) {
|
|
235
344
|
res.status(500).json({ error: error.message });
|
|
@@ -246,17 +355,17 @@ class ApiServer {
|
|
|
246
355
|
if (type === 'staged') {
|
|
247
356
|
cmd = file ? `git diff --cached -- "${file}"` : `git diff --cached`;
|
|
248
357
|
} else {
|
|
249
|
-
|
|
358
|
+
if (file) {
|
|
250
359
|
try {
|
|
251
|
-
const filePath = path.join(
|
|
360
|
+
const filePath = path.join(req.workingDir, file);
|
|
252
361
|
if (await fs.pathExists(filePath)) {
|
|
253
362
|
const stat = await fs.stat(filePath);
|
|
254
363
|
if (stat.isDirectory()) return res.json({ diff: '' });
|
|
255
364
|
}
|
|
256
365
|
} catch (e) {}
|
|
257
|
-
const { stdout: isUntracked } = await execAsync(`git ls-files --others --exclude-standard "${file}"`, { cwd:
|
|
366
|
+
const { stdout: isUntracked } = await execAsync(`git ls-files --others --exclude-standard "${file}"`, { cwd: req.workingDir });
|
|
258
367
|
if (isUntracked.trim()) {
|
|
259
|
-
const content = await fs.readFile(path.join(
|
|
368
|
+
const content = await fs.readFile(path.join(req.workingDir, file), 'utf8');
|
|
260
369
|
let fakeDiff = `diff --git a/${file} b/${file}\nnew file mode 100644\n--- /dev/null\n+++ b/${file}\n@@ -0,0 +1,${content.split('\n').length} @@\n`;
|
|
261
370
|
content.split('\n').forEach(l => fakeDiff += `+${l}\n`);
|
|
262
371
|
return res.json({ diff: fakeDiff });
|
|
@@ -266,7 +375,7 @@ class ApiServer {
|
|
|
266
375
|
cmd = `git diff`;
|
|
267
376
|
}
|
|
268
377
|
}
|
|
269
|
-
const { stdout } = await execAsync(cmd, { cwd:
|
|
378
|
+
const { stdout } = await execAsync(cmd, { cwd: req.workingDir, maxBuffer: 20 * 1024 * 1024 });
|
|
270
379
|
res.json({ diff: stdout });
|
|
271
380
|
} catch (error) {
|
|
272
381
|
console.error(chalk.red('ā [GIT DIFF] Error:'), error.message);
|
|
@@ -274,6 +383,82 @@ class ApiServer {
|
|
|
274
383
|
}
|
|
275
384
|
});
|
|
276
385
|
|
|
386
|
+
// --- TERMINAL LOG API ---
|
|
387
|
+
|
|
388
|
+
// Get terminal logs
|
|
389
|
+
this.app.get('/api/terminal/:termId/logs', (req, res) => {
|
|
390
|
+
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
|
+
}
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
// Analyze terminal logs
|
|
406
|
+
this.app.post('/api/terminal/:termId/analyze', (req, res) => {
|
|
407
|
+
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
|
+
}
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
// --- SAVED COMMANDS API ---
|
|
422
|
+
|
|
423
|
+
// Load saved commands
|
|
424
|
+
this.app.get('/api/commands/load', async (req, res) => {
|
|
425
|
+
try {
|
|
426
|
+
const commandsFile = path.join(req.workingDir, '.vg', 'commands.json');
|
|
427
|
+
|
|
428
|
+
if (!await fs.pathExists(commandsFile)) {
|
|
429
|
+
return res.json({ commands: [] });
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const data = await fs.readJson(commandsFile);
|
|
433
|
+
res.json({ commands: data.commands || [] });
|
|
434
|
+
} catch (error) {
|
|
435
|
+
console.error(chalk.red('ā [COMMANDS LOAD] Error:'), error.message);
|
|
436
|
+
res.json({ commands: [] });
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
// Save commands
|
|
441
|
+
this.app.post('/api/commands/save', async (req, res) => {
|
|
442
|
+
try {
|
|
443
|
+
const { commands } = req.body;
|
|
444
|
+
if (!Array.isArray(commands)) {
|
|
445
|
+
return res.status(400).json({ error: 'commands must be an array' });
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const vgDir = path.join(req.workingDir, '.vg');
|
|
449
|
+
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`));
|
|
455
|
+
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
|
+
|
|
277
462
|
// --- TREE STATE API ---
|
|
278
463
|
|
|
279
464
|
// Save tree state (excluded paths)
|
|
@@ -285,7 +470,7 @@ class ApiServer {
|
|
|
285
470
|
}
|
|
286
471
|
|
|
287
472
|
// Create .vg directory if it doesn't exist
|
|
288
|
-
const vgDir = path.join(
|
|
473
|
+
const vgDir = path.join(req.workingDir, '.vg');
|
|
289
474
|
await fs.ensureDir(vgDir);
|
|
290
475
|
|
|
291
476
|
// Save state to .vg/tree-state.json
|
|
@@ -303,7 +488,7 @@ class ApiServer {
|
|
|
303
488
|
// Load tree state
|
|
304
489
|
this.app.get('/api/tree-state/load', async (req, res) => {
|
|
305
490
|
try {
|
|
306
|
-
const stateFile = path.join(
|
|
491
|
+
const stateFile = path.join(req.workingDir, '.vg', 'tree-state.json');
|
|
307
492
|
|
|
308
493
|
// Check if state file exists
|
|
309
494
|
if (!await fs.pathExists(stateFile)) {
|
|
@@ -325,7 +510,7 @@ class ApiServer {
|
|
|
325
510
|
this.app.post('/api/analyze', async (req, res) => {
|
|
326
511
|
const { path: projectPath, options = {}, specificFiles } = req.body;
|
|
327
512
|
if (!projectPath) return res.status(400).json({ error: 'Missing path' });
|
|
328
|
-
const resolvedPath = path.resolve(projectPath);
|
|
513
|
+
const resolvedPath = path.resolve(req.workingDir, projectPath);
|
|
329
514
|
if (!await fs.pathExists(resolvedPath)) return res.status(404).json({ error: 'Path not found' });
|
|
330
515
|
const scanner = new FileScanner(resolvedPath, {
|
|
331
516
|
extensions: options.extensions ? options.extensions.split(',') : undefined,
|
|
@@ -339,9 +524,8 @@ class ApiServer {
|
|
|
339
524
|
});
|
|
340
525
|
|
|
341
526
|
this.app.get('/api/info', async (req, res) => {
|
|
342
|
-
const projectPath = req.query.path;
|
|
343
|
-
|
|
344
|
-
const resolvedPath = path.resolve(projectPath);
|
|
527
|
+
const projectPath = req.query.path || '.';
|
|
528
|
+
const resolvedPath = path.resolve(req.workingDir, projectPath);
|
|
345
529
|
const detector = new ProjectDetector(resolvedPath);
|
|
346
530
|
const projectInfo = await detector.detectAll();
|
|
347
531
|
const scanner = new FileScanner(resolvedPath);
|
|
@@ -351,7 +535,7 @@ class ApiServer {
|
|
|
351
535
|
|
|
352
536
|
this.app.get('/api/structure', async (req, res) => {
|
|
353
537
|
const projectPath = req.query.path || '.';
|
|
354
|
-
const resolvedPath = path.resolve(projectPath);
|
|
538
|
+
const resolvedPath = path.resolve(req.workingDir, projectPath);
|
|
355
539
|
const scanner = new FileScanner(resolvedPath);
|
|
356
540
|
const scanResult = await scanner.scanProject();
|
|
357
541
|
const tokenManager = new TokenManager();
|
|
@@ -361,7 +545,7 @@ class ApiServer {
|
|
|
361
545
|
|
|
362
546
|
this.app.post('/api/execute', async (req, res) => {
|
|
363
547
|
const { bash } = req.body;
|
|
364
|
-
const executor = new BashExecutor(
|
|
548
|
+
const executor = new BashExecutor(req.workingDir);
|
|
365
549
|
const result = await executor.execute(bash);
|
|
366
550
|
res.status(result.success ? 200 : 400).json(result);
|
|
367
551
|
});
|
|
@@ -370,6 +554,24 @@ class ApiServer {
|
|
|
370
554
|
await fs.remove(path.resolve(req.body.output));
|
|
371
555
|
res.json({ success: true });
|
|
372
556
|
});
|
|
557
|
+
|
|
558
|
+
// Shutdown server endpoint
|
|
559
|
+
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
|
+
}
|
|
574
|
+
});
|
|
373
575
|
}
|
|
374
576
|
|
|
375
577
|
async start() {
|
|
@@ -414,7 +616,17 @@ class ApiServer {
|
|
|
414
616
|
}
|
|
415
617
|
|
|
416
618
|
async stop() {
|
|
417
|
-
|
|
619
|
+
console.log(chalk.yellow('Stopping server...'));
|
|
620
|
+
|
|
621
|
+
// Release leader lock
|
|
622
|
+
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'));
|
|
418
630
|
}
|
|
419
631
|
}
|
|
420
632
|
|