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,421 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>VG Coder API Dashboard</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
16
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
17
+ min-height: 100vh;
18
+ padding: 20px;
19
+ }
20
+
21
+ .container {
22
+ max-width: 1200px;
23
+ margin: 0 auto;
24
+ }
25
+
26
+ .header {
27
+ background: white;
28
+ border-radius: 12px;
29
+ padding: 30px;
30
+ margin-bottom: 30px;
31
+ box-shadow: 0 10px 30px rgba(0,0,0,0.2);
32
+ }
33
+
34
+ .header h1 {
35
+ color: #667eea;
36
+ font-size: 2.5em;
37
+ margin-bottom: 10px;
38
+ }
39
+
40
+ .header p {
41
+ color: #666;
42
+ font-size: 1.1em;
43
+ }
44
+
45
+ .status {
46
+ display: inline-block;
47
+ padding: 8px 16px;
48
+ background: #10b981;
49
+ color: white;
50
+ border-radius: 20px;
51
+ font-size: 0.9em;
52
+ margin-top: 10px;
53
+ }
54
+
55
+ .endpoints {
56
+ display: grid;
57
+ grid-template-columns: repeat(auto-fit, minmax(500px, 1fr));
58
+ gap: 20px;
59
+ }
60
+
61
+ .endpoint-card {
62
+ background: white;
63
+ border-radius: 12px;
64
+ padding: 25px;
65
+ box-shadow: 0 10px 30px rgba(0,0,0,0.2);
66
+ transition: transform 0.3s ease;
67
+ }
68
+
69
+ .endpoint-card:hover {
70
+ transform: translateY(-5px);
71
+ }
72
+
73
+ .endpoint-header {
74
+ display: flex;
75
+ align-items: center;
76
+ gap: 15px;
77
+ margin-bottom: 15px;
78
+ }
79
+
80
+ .method {
81
+ padding: 6px 12px;
82
+ border-radius: 6px;
83
+ font-weight: bold;
84
+ font-size: 0.85em;
85
+ }
86
+
87
+ .method.get { background: #3b82f6; color: white; }
88
+ .method.post { background: #10b981; color: white; }
89
+ .method.delete { background: #ef4444; color: white; }
90
+
91
+ .endpoint-path {
92
+ font-family: 'Courier New', monospace;
93
+ color: #333;
94
+ font-size: 1.1em;
95
+ }
96
+
97
+ .endpoint-desc {
98
+ color: #666;
99
+ margin-bottom: 15px;
100
+ line-height: 1.6;
101
+ }
102
+
103
+ .form-group {
104
+ margin-bottom: 15px;
105
+ }
106
+
107
+ .form-group label {
108
+ display: block;
109
+ margin-bottom: 5px;
110
+ color: #333;
111
+ font-weight: 500;
112
+ }
113
+
114
+ .form-group input,
115
+ .form-group textarea {
116
+ width: 100%;
117
+ padding: 10px;
118
+ border: 2px solid #e5e7eb;
119
+ border-radius: 6px;
120
+ font-size: 0.95em;
121
+ font-family: 'Courier New', monospace;
122
+ }
123
+
124
+ .form-group textarea {
125
+ min-height: 120px;
126
+ resize: vertical;
127
+ }
128
+
129
+ .btn {
130
+ background: #667eea;
131
+ color: white;
132
+ border: none;
133
+ padding: 12px 24px;
134
+ border-radius: 6px;
135
+ cursor: pointer;
136
+ font-size: 1em;
137
+ font-weight: 600;
138
+ transition: background 0.3s ease;
139
+ }
140
+
141
+ .btn:hover {
142
+ background: #5568d3;
143
+ }
144
+
145
+ .btn:disabled {
146
+ background: #9ca3af;
147
+ cursor: not-allowed;
148
+ }
149
+
150
+ .response {
151
+ margin-top: 15px;
152
+ padding: 15px;
153
+ border-radius: 6px;
154
+ background: #f9fafb;
155
+ border-left: 4px solid #667eea;
156
+ display: none;
157
+ }
158
+
159
+ .response.show {
160
+ display: block;
161
+ }
162
+
163
+ .response.success {
164
+ border-left-color: #10b981;
165
+ background: #f0fdf4;
166
+ }
167
+
168
+ .response.error {
169
+ border-left-color: #ef4444;
170
+ background: #fef2f2;
171
+ }
172
+
173
+ .response pre {
174
+ margin: 0;
175
+ font-family: 'Courier New', monospace;
176
+ font-size: 0.9em;
177
+ white-space: pre-wrap;
178
+ word-wrap: break-word;
179
+ }
180
+
181
+ .loading {
182
+ display: inline-block;
183
+ width: 16px;
184
+ height: 16px;
185
+ border: 3px solid rgba(255,255,255,.3);
186
+ border-radius: 50%;
187
+ border-top-color: white;
188
+ animation: spin 1s ease-in-out infinite;
189
+ }
190
+
191
+ @keyframes spin {
192
+ to { transform: rotate(360deg); }
193
+ }
194
+ </style>
195
+ </head>
196
+ <body>
197
+ <div class="container">
198
+ <div class="header">
199
+ <h1>🚀 VG Coder API Dashboard</h1>
200
+ <p>Test your API endpoints directly from the browser</p>
201
+ <span class="status" id="status">● Server Running</span>
202
+ </div>
203
+
204
+ <div class="endpoints">
205
+ <!-- Health Check -->
206
+ <div class="endpoint-card">
207
+ <div class="endpoint-header">
208
+ <span class="method get">GET</span>
209
+ <span class="endpoint-path">/health</span>
210
+ </div>
211
+ <p class="endpoint-desc">Check server health status</p>
212
+ <button class="btn" onclick="testHealth()">Test Health Check</button>
213
+ <div class="response" id="health-response"></div>
214
+ </div>
215
+
216
+ <!-- Analyze -->
217
+ <div class="endpoint-card">
218
+ <div class="endpoint-header">
219
+ <span class="method post">POST</span>
220
+ <span class="endpoint-path">/api/analyze</span>
221
+ </div>
222
+ <p class="endpoint-desc">Analyze project and download project.txt</p>
223
+ <div class="form-group">
224
+ <label>Project Path:</label>
225
+ <input type="text" id="analyze-path" value="." placeholder=".">
226
+ </div>
227
+ <div class="form-group">
228
+ <label>Max Tokens:</label>
229
+ <input type="number" id="analyze-tokens" value="8000" placeholder="8000">
230
+ </div>
231
+ <button class="btn" onclick="testAnalyze()">Analyze & Download</button>
232
+ <div class="response" id="analyze-response"></div>
233
+ </div>
234
+
235
+ <!-- Info -->
236
+ <div class="endpoint-card">
237
+ <div class="endpoint-header">
238
+ <span class="method get">GET</span>
239
+ <span class="endpoint-path">/api/info</span>
240
+ </div>
241
+ <p class="endpoint-desc">Get project information and statistics</p>
242
+ <div class="form-group">
243
+ <label>Project Path:</label>
244
+ <input type="text" id="info-path" value="." placeholder=".">
245
+ </div>
246
+ <button class="btn" onclick="testInfo()">Get Info</button>
247
+ <div class="response" id="info-response"></div>
248
+ </div>
249
+
250
+ <!-- Execute Bash -->
251
+ <div class="endpoint-card">
252
+ <div class="endpoint-header">
253
+ <span class="method post">POST</span>
254
+ <span class="endpoint-path">/api/execute</span>
255
+ </div>
256
+ <p class="endpoint-desc">Execute bash script with syntax validation</p>
257
+ <div class="form-group">
258
+ <label>Bash Script:</label>
259
+ <textarea id="execute-bash" placeholder="echo 'Hello World'&#10;date"></textarea>
260
+ </div>
261
+ <button class="btn" onclick="testExecute()">Execute Script</button>
262
+ <div class="response" id="execute-response"></div>
263
+ </div>
264
+
265
+ <!-- Clean -->
266
+ <div class="endpoint-card">
267
+ <div class="endpoint-header">
268
+ <span class="method delete">DELETE</span>
269
+ <span class="endpoint-path">/api/clean</span>
270
+ </div>
271
+ <p class="endpoint-desc">Clean output directory</p>
272
+ <div class="form-group">
273
+ <label>Output Path:</label>
274
+ <input type="text" id="clean-output" value="./vg-output" placeholder="./vg-output">
275
+ </div>
276
+ <button class="btn" onclick="testClean()">Clean Directory</button>
277
+ <div class="response" id="clean-response"></div>
278
+ </div>
279
+ </div>
280
+ </div>
281
+
282
+ <script>
283
+ const API_BASE = window.location.origin;
284
+
285
+ function showResponse(elementId, data, isError = false) {
286
+ const el = document.getElementById(elementId);
287
+ el.className = 'response show ' + (isError ? 'error' : 'success');
288
+ el.innerHTML = '<pre>' + JSON.stringify(data, null, 2) + '</pre>';
289
+ }
290
+
291
+ function showLoading(button) {
292
+ button.disabled = true;
293
+ button.innerHTML = '<span class="loading"></span> Loading...';
294
+ }
295
+
296
+ function resetButton(button, text) {
297
+ button.disabled = false;
298
+ button.textContent = text;
299
+ }
300
+
301
+ async function testHealth() {
302
+ const btn = event.target;
303
+ showLoading(btn);
304
+ try {
305
+ const res = await fetch(`${API_BASE}/health`);
306
+ const data = await res.json();
307
+ showResponse('health-response', data);
308
+ } catch (err) {
309
+ showResponse('health-response', { error: err.message }, true);
310
+ }
311
+ resetButton(btn, 'Test Health Check');
312
+ }
313
+
314
+ async function testAnalyze() {
315
+ const btn = event.target;
316
+ const path = document.getElementById('analyze-path').value;
317
+ const maxTokens = document.getElementById('analyze-tokens').value;
318
+
319
+ showLoading(btn);
320
+ try {
321
+ const res = await fetch(`${API_BASE}/api/analyze`, {
322
+ method: 'POST',
323
+ headers: { 'Content-Type': 'application/json' },
324
+ body: JSON.stringify({
325
+ path,
326
+ options: { maxTokens: parseInt(maxTokens) }
327
+ })
328
+ });
329
+
330
+ if (res.ok) {
331
+ const blob = await res.blob();
332
+ const url = window.URL.createObjectURL(blob);
333
+ const a = document.createElement('a');
334
+ a.href = url;
335
+ a.download = 'project.txt';
336
+ a.click();
337
+ showResponse('analyze-response', { success: true, message: 'File downloaded!' });
338
+ } else {
339
+ const data = await res.json();
340
+ showResponse('analyze-response', data, true);
341
+ }
342
+ } catch (err) {
343
+ showResponse('analyze-response', { error: err.message }, true);
344
+ }
345
+ resetButton(btn, 'Analyze & Download');
346
+ }
347
+
348
+ async function testInfo() {
349
+ const btn = event.target;
350
+ const path = document.getElementById('info-path').value;
351
+
352
+ showLoading(btn);
353
+ try {
354
+ const res = await fetch(`${API_BASE}/api/info?path=${encodeURIComponent(path)}`);
355
+ const data = await res.json();
356
+ showResponse('info-response', data, !res.ok);
357
+ } catch (err) {
358
+ showResponse('info-response', { error: err.message }, true);
359
+ }
360
+ resetButton(btn, 'Get Info');
361
+ }
362
+
363
+ async function testExecute() {
364
+ const btn = event.target;
365
+ const bash = document.getElementById('execute-bash').value;
366
+
367
+ if (!bash.trim()) {
368
+ showResponse('execute-response', { error: 'Bash script is required' }, true);
369
+ return;
370
+ }
371
+
372
+ showLoading(btn);
373
+ try {
374
+ const res = await fetch(`${API_BASE}/api/execute`, {
375
+ method: 'POST',
376
+ headers: { 'Content-Type': 'application/json' },
377
+ body: JSON.stringify({ bash })
378
+ });
379
+ const data = await res.json();
380
+ showResponse('execute-response', data, !res.ok || !data.success);
381
+ } catch (err) {
382
+ showResponse('execute-response', { error: err.message }, true);
383
+ }
384
+ resetButton(btn, 'Execute Script');
385
+ }
386
+
387
+ async function testClean() {
388
+ const btn = event.target;
389
+ const output = document.getElementById('clean-output').value;
390
+
391
+ showLoading(btn);
392
+ try {
393
+ const res = await fetch(`${API_BASE}/api/clean`, {
394
+ method: 'DELETE',
395
+ headers: { 'Content-Type': 'application/json' },
396
+ body: JSON.stringify({ output })
397
+ });
398
+ const data = await res.json();
399
+ showResponse('clean-response', data, !res.ok);
400
+ } catch (err) {
401
+ showResponse('clean-response', { error: err.message }, true);
402
+ }
403
+ resetButton(btn, 'Clean Directory');
404
+ }
405
+
406
+ // Check server status periodically
407
+ setInterval(async () => {
408
+ try {
409
+ const res = await fetch(`${API_BASE}/health`);
410
+ if (res.ok) {
411
+ document.getElementById('status').textContent = '● Server Running';
412
+ document.getElementById('status').style.background = '#10b981';
413
+ }
414
+ } catch {
415
+ document.getElementById('status').textContent = '● Server Offline';
416
+ document.getElementById('status').style.background = '#ef4444';
417
+ }
418
+ }, 5000);
419
+ </script>
420
+ </body>
421
+ </html>
@@ -0,0 +1,130 @@
1
+ const { exec } = require('child_process');
2
+ const path = require('path');
3
+ const fs = require('fs-extra');
4
+ const { promisify } = require('util');
5
+
6
+ const execAsync = promisify(exec);
7
+
8
+ /**
9
+ * Bash Script Executor
10
+ * Validates and executes bash scripts safely
11
+ */
12
+ class BashExecutor {
13
+ constructor(workingDir) {
14
+ this.workingDir = workingDir || process.cwd();
15
+ this.tempDir = path.join(this.workingDir, '.vg', 'temp-execute');
16
+ }
17
+
18
+ /**
19
+ * Ensure temp directory exists
20
+ */
21
+ async ensureTempDir() {
22
+ await fs.ensureDir(this.tempDir);
23
+ }
24
+
25
+ /**
26
+ * Cleanup temp directory
27
+ */
28
+ async cleanup() {
29
+ try {
30
+ if (await fs.pathExists(this.tempDir)) {
31
+ await fs.remove(this.tempDir);
32
+ }
33
+ } catch (error) {
34
+ console.error('Cleanup error:', error.message);
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Validate bash script syntax
40
+ * @param {string} bashScript - The bash script to validate
41
+ * @returns {Promise<{valid: boolean, error: string|null}>}
42
+ */
43
+ async validateSyntax(bashScript) {
44
+ await this.ensureTempDir();
45
+
46
+ const scriptPath = path.join(this.tempDir, 'validate.sh');
47
+
48
+ try {
49
+ // Write script to temp file
50
+ await fs.writeFile(scriptPath, bashScript, 'utf8');
51
+
52
+ // Validate syntax using bash -n
53
+ await execAsync(`bash -n "${scriptPath}"`);
54
+
55
+ return { valid: true, error: null };
56
+
57
+ } catch (error) {
58
+ return {
59
+ valid: false,
60
+ error: error.stderr || error.message
61
+ };
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Execute bash script in working directory
67
+ * @param {string} bashScript - The bash script to execute
68
+ * @returns {Promise<{success: boolean, stdout: string, stderr: string, exitCode: number}>}
69
+ */
70
+ async execute(bashScript) {
71
+ const startTime = Date.now();
72
+
73
+ try {
74
+ // First validate syntax
75
+ const validation = await this.validateSyntax(bashScript);
76
+
77
+ if (!validation.valid) {
78
+ return {
79
+ success: false,
80
+ error: 'Syntax validation failed',
81
+ details: validation.error,
82
+ executionTime: Date.now() - startTime
83
+ };
84
+ }
85
+
86
+ // Execute script in working directory
87
+ const result = await execAsync(bashScript, {
88
+ cwd: this.workingDir,
89
+ maxBuffer: 10 * 1024 * 1024, // 10MB buffer
90
+ shell: '/bin/bash'
91
+ });
92
+
93
+ // Cleanup temp directory
94
+ await this.cleanup();
95
+
96
+ return {
97
+ success: true,
98
+ stdout: result.stdout || '',
99
+ stderr: result.stderr || '',
100
+ exitCode: 0,
101
+ executionTime: Date.now() - startTime
102
+ };
103
+
104
+ } catch (error) {
105
+ // Cleanup even on error
106
+ await this.cleanup();
107
+
108
+ // Check if it's an execution error (not syntax)
109
+ if (error.code !== undefined) {
110
+ return {
111
+ success: false,
112
+ stdout: error.stdout || '',
113
+ stderr: error.stderr || error.message,
114
+ exitCode: error.code || 1,
115
+ executionTime: Date.now() - startTime
116
+ };
117
+ }
118
+
119
+ // Unknown error
120
+ return {
121
+ success: false,
122
+ error: 'Execution failed',
123
+ details: error.message,
124
+ executionTime: Date.now() - startTime
125
+ };
126
+ }
127
+ }
128
+ }
129
+
130
+ module.exports = BashExecutor;