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.
- package/ARCHITECTURE.md +255 -0
- package/README.md +0 -11
- package/change.sh +0 -0
- package/dist/vg-coder-bundle.js +42 -0
- package/gulpfile.js +111 -0
- package/package.json +19 -11
- package/src/index.js +28 -220
- package/src/server/api-server.js +120 -428
- package/src/server/views/css/bubble.css +81 -0
- package/src/server/views/css/code-viewer.css +58 -0
- package/src/server/views/css/terminal.css +59 -155
- package/src/server/views/dashboard.css +78 -678
- package/src/server/views/dashboard.html +39 -278
- package/src/server/views/js/api.js +2 -22
- package/src/server/views/js/config.js +27 -15
- package/src/server/views/js/event-protocol.js +263 -0
- package/src/server/views/js/features/bubble-features/index.js +125 -0
- package/src/server/views/js/features/bubble-features/paste-run-feature.js +16 -0
- package/src/server/views/js/features/bubble-features/terminal-feature.js +16 -0
- package/src/server/views/js/features/bubble.js +175 -0
- package/src/server/views/js/features/code-viewer.js +90 -0
- package/src/server/views/js/features/commands.js +34 -81
- package/src/server/views/js/features/editor-tabs.js +19 -46
- package/src/server/views/js/features/git-view.js +63 -81
- package/src/server/views/js/features/iframe-manager.js +3 -97
- package/src/server/views/js/features/monaco-manager.js +19 -39
- package/src/server/views/js/features/project-switcher.js +7 -63
- package/src/server/views/js/features/resize.js +5 -16
- package/src/server/views/js/features/structure.js +38 -106
- package/src/server/views/js/features/terminal.js +102 -418
- package/src/server/views/js/handlers.js +60 -43
- package/src/server/views/js/main.js +75 -179
- package/src/server/views/js/shadow-entry.js +21 -0
- package/src/server/views/js/utils.js +48 -28
- package/src/server/views/vg-coder/_metadata/generated_indexed_rulesets/_ruleset1 +0 -0
- package/src/server/views/vg-coder/controller.js +33 -258
- package/vetgo-auto/chrome/src/utils/injector-script.ts +33 -258
- package/vetgo-auto/vg-coder.zip +0 -0
- package/src/server/views/dashboard.js +0 -457
- package/test-pty.js +0 -31
package/src/server/api-server.js
CHANGED
|
@@ -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
|
-
|
|
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:
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
//
|
|
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 {
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
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
|
-
|
|
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
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
res.json({
|
|
412
|
-
|
|
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
|
-
//
|
|
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.
|
|
547
|
-
|
|
548
|
-
const
|
|
549
|
-
|
|
550
|
-
|
|
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
|
-
|
|
561
|
-
|
|
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
|
-
|
|
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
|
|