vg-coder-cli 1.0.10 → 1.0.11

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.
@@ -0,0 +1,320 @@
1
+ const express = require('express');
2
+ const cors = require('cors');
3
+ const bodyParser = require('body-parser');
4
+ const path = require('path');
5
+ const fs = require('fs-extra');
6
+ const chalk = require('chalk');
7
+ const packageJson = require('../../package.json');
8
+
9
+ const ProjectDetector = require('../detectors/project-detector');
10
+ const FileScanner = require('../scanner/file-scanner');
11
+ const TokenManager = require('../tokenizer/token-manager');
12
+ const BashExecutor = require('../utils/bash-executor');
13
+
14
+ /**
15
+ * API Server for VG Coder CLI
16
+ */
17
+ class ApiServer {
18
+ constructor(port = 6868) {
19
+ this.port = port;
20
+ this.app = express();
21
+ this.server = null;
22
+ this.workingDir = process.cwd(); // Track working directory
23
+ this.setupMiddleware();
24
+ this.setupRoutes();
25
+ }
26
+
27
+ /**
28
+ * Setup Express middleware
29
+ */
30
+ setupMiddleware() {
31
+ this.app.use(cors());
32
+ this.app.use(bodyParser.json());
33
+ this.app.use(bodyParser.urlencoded({ extended: true }));
34
+
35
+ // Request logging
36
+ this.app.use((req, res, next) => {
37
+ console.log(chalk.blue(`[${new Date().toISOString()}] ${req.method} ${req.path}`));
38
+ next();
39
+ });
40
+ }
41
+
42
+ /**
43
+ * Setup API routes
44
+ */
45
+ setupRoutes() {
46
+ // Dashboard - serve HTML interface
47
+ this.app.get('/', (req, res) => {
48
+ res.sendFile(path.join(__dirname, 'views', 'dashboard.html'));
49
+ });
50
+
51
+ // Health check
52
+ this.app.get('/health', (req, res) => {
53
+ res.json({
54
+ status: 'ok',
55
+ version: packageJson.version,
56
+ timestamp: new Date().toISOString()
57
+ });
58
+ });
59
+
60
+ // Analyze endpoint - returns project.txt file
61
+ this.app.post('/api/analyze', async (req, res) => {
62
+ try {
63
+ const { path: projectPath, options = {} } = req.body;
64
+
65
+ if (!projectPath) {
66
+ return res.status(400).json({
67
+ error: 'Missing required field: path'
68
+ });
69
+ }
70
+
71
+ const resolvedPath = path.resolve(projectPath);
72
+
73
+ // Validate project path
74
+ if (!await fs.pathExists(resolvedPath)) {
75
+ return res.status(404).json({
76
+ error: `Project path does not exist: ${projectPath}`
77
+ });
78
+ }
79
+
80
+ console.log(chalk.yellow(`Analyzing project: ${resolvedPath}`));
81
+
82
+ // Detect project type
83
+ const detector = new ProjectDetector(resolvedPath);
84
+ const projectInfo = await detector.detectAll();
85
+
86
+ // Scan files
87
+ const scannerOptions = {
88
+ maxTokens: parseInt(options.maxTokens || 8000),
89
+ extensions: options.extensions ? options.extensions.split(',').map(ext => ext.trim()) : undefined,
90
+ includeHidden: options.includeHidden || false
91
+ };
92
+
93
+ const scanner = new FileScanner(resolvedPath, scannerOptions);
94
+ const scanResult = await scanner.scanProject();
95
+
96
+ // Create AI-friendly content
97
+ const aiContent = await scanner.createCombinedContentForAI(scanResult.files, {
98
+ includeStats: true,
99
+ includeTree: true,
100
+ preserveLineNumbers: true
101
+ });
102
+
103
+ // Set response headers for file download
104
+ res.setHeader('Content-Type', 'text/plain; charset=utf-8');
105
+ res.setHeader('Content-Disposition', 'attachment; filename="project.txt"');
106
+ res.send(aiContent);
107
+
108
+ console.log(chalk.green(`✓ Analysis completed: ${scanResult.files.length} files`));
109
+
110
+ } catch (error) {
111
+ console.error(chalk.red('Error during analysis:'), error);
112
+ res.status(500).json({
113
+ error: 'Analysis failed',
114
+ message: error.message
115
+ });
116
+ }
117
+ });
118
+
119
+ // Info endpoint
120
+ this.app.get('/api/info', async (req, res) => {
121
+ try {
122
+ const projectPath = req.query.path;
123
+
124
+ if (!projectPath) {
125
+ return res.status(400).json({
126
+ error: 'Missing required query parameter: path'
127
+ });
128
+ }
129
+
130
+ const resolvedPath = path.resolve(projectPath);
131
+
132
+ if (!await fs.pathExists(resolvedPath)) {
133
+ return res.status(404).json({
134
+ error: `Project path does not exist: ${projectPath}`
135
+ });
136
+ }
137
+
138
+ // Detect project
139
+ const detector = new ProjectDetector(resolvedPath);
140
+ const projectInfo = await detector.detectAll();
141
+
142
+ // Quick scan
143
+ const scanner = new FileScanner(resolvedPath);
144
+ const scanResult = await scanner.scanProject();
145
+
146
+ // Token analysis
147
+ const tokenManager = new TokenManager();
148
+ const tokenAnalysis = tokenManager.analyzeFiles(scanResult.files);
149
+ tokenManager.cleanup();
150
+
151
+ const extensions = [...new Set(scanResult.files.map(f => f.extension))].filter(Boolean);
152
+
153
+ res.json({
154
+ path: resolvedPath,
155
+ primaryType: projectInfo.primary,
156
+ detectedTechnologies: projectInfo.detected,
157
+ stats: {
158
+ totalFiles: scanResult.stats.processedFiles,
159
+ totalSize: scanResult.files.reduce((sum, f) => sum + f.size, 0),
160
+ totalLines: scanResult.files.reduce((sum, f) => sum + f.lines, 0),
161
+ extensions: extensions
162
+ },
163
+ tokens: {
164
+ total: tokenAnalysis.summary.totalTokens,
165
+ averagePerFile: tokenAnalysis.summary.averageTokensPerFile,
166
+ filesExceedingLimit: tokenAnalysis.summary.filesExceedingLimit,
167
+ estimatedChunks: tokenAnalysis.summary.estimatedChunks
168
+ }
169
+ });
170
+
171
+ console.log(chalk.green(`✓ Info retrieved for: ${resolvedPath}`));
172
+
173
+ } catch (error) {
174
+ console.error(chalk.red('Error getting info:'), error);
175
+ res.status(500).json({
176
+ error: 'Failed to get project info',
177
+ message: error.message
178
+ });
179
+ }
180
+ });
181
+
182
+ // Clean endpoint
183
+ this.app.delete('/api/clean', async (req, res) => {
184
+ try {
185
+ const { output } = req.body;
186
+
187
+ if (!output) {
188
+ return res.status(400).json({
189
+ error: 'Missing required field: output'
190
+ });
191
+ }
192
+
193
+ const outputPath = path.resolve(output);
194
+
195
+ if (await fs.pathExists(outputPath)) {
196
+ await fs.remove(outputPath);
197
+ res.json({
198
+ success: true,
199
+ message: `Cleaned: ${outputPath}`
200
+ });
201
+ console.log(chalk.green(`✓ Cleaned: ${outputPath}`));
202
+ } else {
203
+ res.json({
204
+ success: true,
205
+ message: 'Output directory does not exist'
206
+ });
207
+ }
208
+
209
+ } catch (error) {
210
+ console.error(chalk.red('Error cleaning:'), error);
211
+ res.status(500).json({
212
+ error: 'Failed to clean',
213
+ message: error.message
214
+ });
215
+ }
216
+ });
217
+
218
+ // Execute bash script endpoint
219
+ this.app.post('/api/execute', async (req, res) => {
220
+ try {
221
+ const { bash } = req.body;
222
+
223
+ if (!bash) {
224
+ return res.status(400).json({
225
+ error: 'Missing required field: bash'
226
+ });
227
+ }
228
+
229
+ console.log(chalk.yellow(`Executing bash script (${bash.length} chars)...`));
230
+
231
+ // Create executor with working directory
232
+ const executor = new BashExecutor(this.workingDir);
233
+
234
+ // Execute script (validates syntax first, then executes)
235
+ const result = await executor.execute(bash);
236
+
237
+ if (result.success) {
238
+ console.log(chalk.green(`✓ Bash execution completed in ${result.executionTime}ms`));
239
+ res.json(result);
240
+ } else {
241
+ console.log(chalk.red(`✗ Bash execution failed: ${result.error || 'Exit code ' + result.exitCode}`));
242
+ res.status(400).json(result);
243
+ }
244
+
245
+ } catch (error) {
246
+ console.error(chalk.red('Error executing bash:'), error);
247
+ res.status(500).json({
248
+ error: 'Execution failed',
249
+ message: error.message
250
+ });
251
+ }
252
+ });
253
+
254
+ // 404 handler
255
+ this.app.use((req, res) => {
256
+ res.status(404).json({
257
+ error: 'Not found',
258
+ message: `Route ${req.method} ${req.path} not found`
259
+ });
260
+ });
261
+
262
+ // Error handler
263
+ this.app.use((err, req, res, next) => {
264
+ console.error(chalk.red('Server error:'), err);
265
+ res.status(500).json({
266
+ error: 'Internal server error',
267
+ message: err.message
268
+ });
269
+ });
270
+ }
271
+
272
+ /**
273
+ * Start the server
274
+ */
275
+ async start() {
276
+ return new Promise((resolve, reject) => {
277
+ this.server = this.app.listen(this.port, () => {
278
+ console.log(chalk.green(`\n🚀 VG Coder API Server started!`));
279
+ console.log(chalk.blue(`📡 Listening on: http://localhost:${this.port}`));
280
+ console.log(chalk.cyan(`\n🎨 Dashboard: http://localhost:${this.port}`));
281
+ console.log(chalk.yellow(`\n📚 Available endpoints:`));
282
+ console.log(` GET /health - Health check`);
283
+ console.log(` POST /api/analyze - Analyze project (returns project.txt)`);
284
+ console.log(` GET /api/info?path=. - Get project info`);
285
+ console.log(` DELETE /api/clean - Clean output directory`);
286
+ console.log(` POST /api/execute - Execute bash script`);
287
+ console.log(chalk.gray(`\n💡 Press Ctrl+C to stop the server\n`));
288
+ resolve();
289
+ });
290
+
291
+ this.server.on('error', (err) => {
292
+ if (err.code === 'EADDRINUSE') {
293
+ console.error(chalk.red(`\n❌ Port ${this.port} is already in use!`));
294
+ console.log(chalk.yellow(`Try using a different port with: vg start -p <port>\n`));
295
+ } else {
296
+ console.error(chalk.red('\n❌ Server error:'), err.message);
297
+ }
298
+ reject(err);
299
+ });
300
+ });
301
+ }
302
+
303
+ /**
304
+ * Stop the server
305
+ */
306
+ async stop() {
307
+ return new Promise((resolve) => {
308
+ if (this.server) {
309
+ this.server.close(() => {
310
+ console.log(chalk.yellow('\n👋 Server stopped\n'));
311
+ resolve();
312
+ });
313
+ } else {
314
+ resolve();
315
+ }
316
+ });
317
+ }
318
+ }
319
+
320
+ module.exports = ApiServer;