vg-coder-cli 2.0.25 ā 2.0.27
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 +167 -31
- package/src/server/project-manager.js +353 -0
- package/src/server/terminal-manager.js +31 -3
- package/src/server/views/css/iframe.css +1 -0
- package/src/server/views/dashboard.css +138 -30
- package/src/server/views/dashboard.html +94 -11
- package/src/server/views/js/features/commands.js +3 -1
- 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 +44 -6
- package/src/server/views/js/main.js +61 -0
- package/src/server/views/vg-coder/controller.js +397 -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 +272 -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.27.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-coder-cli-2.0.25.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.27",
|
|
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);
|
|
@@ -314,7 +423,7 @@ class ApiServer {
|
|
|
314
423
|
// Load saved commands
|
|
315
424
|
this.app.get('/api/commands/load', async (req, res) => {
|
|
316
425
|
try {
|
|
317
|
-
const commandsFile = path.join(
|
|
426
|
+
const commandsFile = path.join(req.workingDir, '.vg', 'commands.json');
|
|
318
427
|
|
|
319
428
|
if (!await fs.pathExists(commandsFile)) {
|
|
320
429
|
return res.json({ commands: [] });
|
|
@@ -336,7 +445,7 @@ class ApiServer {
|
|
|
336
445
|
return res.status(400).json({ error: 'commands must be an array' });
|
|
337
446
|
}
|
|
338
447
|
|
|
339
|
-
const vgDir = path.join(
|
|
448
|
+
const vgDir = path.join(req.workingDir, '.vg');
|
|
340
449
|
await fs.ensureDir(vgDir);
|
|
341
450
|
|
|
342
451
|
const commandsFile = path.join(vgDir, 'commands.json');
|
|
@@ -361,7 +470,7 @@ class ApiServer {
|
|
|
361
470
|
}
|
|
362
471
|
|
|
363
472
|
// Create .vg directory if it doesn't exist
|
|
364
|
-
const vgDir = path.join(
|
|
473
|
+
const vgDir = path.join(req.workingDir, '.vg');
|
|
365
474
|
await fs.ensureDir(vgDir);
|
|
366
475
|
|
|
367
476
|
// Save state to .vg/tree-state.json
|
|
@@ -379,7 +488,7 @@ class ApiServer {
|
|
|
379
488
|
// Load tree state
|
|
380
489
|
this.app.get('/api/tree-state/load', async (req, res) => {
|
|
381
490
|
try {
|
|
382
|
-
const stateFile = path.join(
|
|
491
|
+
const stateFile = path.join(req.workingDir, '.vg', 'tree-state.json');
|
|
383
492
|
|
|
384
493
|
// Check if state file exists
|
|
385
494
|
if (!await fs.pathExists(stateFile)) {
|
|
@@ -401,7 +510,7 @@ class ApiServer {
|
|
|
401
510
|
this.app.post('/api/analyze', async (req, res) => {
|
|
402
511
|
const { path: projectPath, options = {}, specificFiles } = req.body;
|
|
403
512
|
if (!projectPath) return res.status(400).json({ error: 'Missing path' });
|
|
404
|
-
const resolvedPath = path.resolve(projectPath);
|
|
513
|
+
const resolvedPath = path.resolve(req.workingDir, projectPath);
|
|
405
514
|
if (!await fs.pathExists(resolvedPath)) return res.status(404).json({ error: 'Path not found' });
|
|
406
515
|
const scanner = new FileScanner(resolvedPath, {
|
|
407
516
|
extensions: options.extensions ? options.extensions.split(',') : undefined,
|
|
@@ -415,9 +524,8 @@ class ApiServer {
|
|
|
415
524
|
});
|
|
416
525
|
|
|
417
526
|
this.app.get('/api/info', async (req, res) => {
|
|
418
|
-
const projectPath = req.query.path;
|
|
419
|
-
|
|
420
|
-
const resolvedPath = path.resolve(projectPath);
|
|
527
|
+
const projectPath = req.query.path || '.';
|
|
528
|
+
const resolvedPath = path.resolve(req.workingDir, projectPath);
|
|
421
529
|
const detector = new ProjectDetector(resolvedPath);
|
|
422
530
|
const projectInfo = await detector.detectAll();
|
|
423
531
|
const scanner = new FileScanner(resolvedPath);
|
|
@@ -427,7 +535,7 @@ class ApiServer {
|
|
|
427
535
|
|
|
428
536
|
this.app.get('/api/structure', async (req, res) => {
|
|
429
537
|
const projectPath = req.query.path || '.';
|
|
430
|
-
const resolvedPath = path.resolve(projectPath);
|
|
538
|
+
const resolvedPath = path.resolve(req.workingDir, projectPath);
|
|
431
539
|
const scanner = new FileScanner(resolvedPath);
|
|
432
540
|
const scanResult = await scanner.scanProject();
|
|
433
541
|
const tokenManager = new TokenManager();
|
|
@@ -437,7 +545,7 @@ class ApiServer {
|
|
|
437
545
|
|
|
438
546
|
this.app.post('/api/execute', async (req, res) => {
|
|
439
547
|
const { bash } = req.body;
|
|
440
|
-
const executor = new BashExecutor(
|
|
548
|
+
const executor = new BashExecutor(req.workingDir);
|
|
441
549
|
const result = await executor.execute(bash);
|
|
442
550
|
res.status(result.success ? 200 : 400).json(result);
|
|
443
551
|
});
|
|
@@ -446,6 +554,24 @@ class ApiServer {
|
|
|
446
554
|
await fs.remove(path.resolve(req.body.output));
|
|
447
555
|
res.json({ success: true });
|
|
448
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
|
+
});
|
|
449
575
|
}
|
|
450
576
|
|
|
451
577
|
async start() {
|
|
@@ -490,7 +616,17 @@ class ApiServer {
|
|
|
490
616
|
}
|
|
491
617
|
|
|
492
618
|
async stop() {
|
|
493
|
-
|
|
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'));
|
|
494
630
|
}
|
|
495
631
|
}
|
|
496
632
|
|