vg-coder-cli 2.0.15 → 2.0.17

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vg-coder-cli",
3
- "version": "2.0.15",
3
+ "version": "2.0.17",
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": {
@@ -31,26 +31,27 @@
31
31
  "url": "https://github.com/tinhthanh/vg-coder-cli.git"
32
32
  },
33
33
  "dependencies": {
34
+ "body-parser": "^1.20.2",
35
+ "chalk": "^4.1.2",
34
36
  "commander": "^11.1.0",
37
+ "cors": "^2.8.5",
35
38
  "directory-tree": "^3.5.1",
36
- "tiktoken": "^1.0.10",
39
+ "express": "^4.18.2",
40
+ "fs-extra": "^11.2.0",
37
41
  "highlight.js": "^11.9.0",
38
42
  "ignore": "^5.3.0",
39
- "fs-extra": "^11.2.0",
40
- "path": "^0.12.7",
41
- "chalk": "^4.1.2",
42
- "ora": "^5.4.1",
43
- "express": "^4.18.2",
44
- "cors": "^2.8.5",
45
- "body-parser": "^1.20.2",
46
43
  "node-pty": "^1.0.0",
47
- "socket.io": "^4.7.2"
44
+ "ora": "^5.4.1",
45
+ "path": "^0.12.7",
46
+ "socket.io": "^4.7.2",
47
+ "tiktoken": "^1.0.10",
48
+ "vg-coder-cli": "^2.0.15"
48
49
  },
49
50
  "devDependencies": {
50
- "jest": "^29.7.0",
51
- "nodemon": "^3.0.2",
52
51
  "@types/jest": "^29.5.8",
53
- "adm-zip": "^0.5.10"
52
+ "adm-zip": "^0.5.10",
53
+ "jest": "^29.7.0",
54
+ "nodemon": "^3.0.2"
54
55
  },
55
56
  "engines": {
56
57
  "node": ">=16.0.0"
@@ -51,13 +51,8 @@ class ApiServer {
51
51
 
52
52
  setupSocketIO() {
53
53
  this.io.on('connection', (socket) => {
54
- // Nhận event init với termId
55
- // FIX: Thêm default value = {} để tránh crash khi data undefined
56
54
  socket.on('terminal:init', (data) => {
57
- if (!data || !data.termId) {
58
- // console.warn('[Socket] Ignored invalid terminal:init', data);
59
- return;
60
- }
55
+ if (!data || !data.termId) return;
61
56
  const { termId, cols, rows } = data;
62
57
  terminalManager.createTerminal(socket, termId, cols, rows, this.workingDir);
63
58
  });
@@ -99,19 +94,144 @@ class ApiServer {
99
94
  }
100
95
  });
101
96
 
97
+ // --- GIT API START ---
98
+
99
+ // Get Git Status
100
+ this.app.get('/api/git/status', async (req, res) => {
101
+ try {
102
+ const { stdout } = await execAsync('git status --porcelain', { cwd: this.workingDir });
103
+
104
+ const staged = [];
105
+ const unstaged = [];
106
+ const untracked = [];
107
+
108
+ const lines = stdout.split('\n').filter(l => l.trim());
109
+
110
+ lines.forEach(line => {
111
+ const x = line[0];
112
+ const y = line[1];
113
+ const path = line.substring(3);
114
+
115
+ if (x !== ' ' && x !== '?') {
116
+ staged.push({ path, status: x });
117
+ }
118
+
119
+ if (y !== ' ') {
120
+ if (x === '?' && y === '?') {
121
+ untracked.push({ path, status: 'U' });
122
+ } else {
123
+ unstaged.push({ path, status: y });
124
+ }
125
+ }
126
+ });
127
+
128
+ const changes = [...unstaged, ...untracked];
129
+ res.json({ staged, changes });
130
+ } catch (error) {
131
+ console.error(chalk.red('❌ [GIT STATUS] Error:'), error.message);
132
+ res.status(500).json({ error: error.message });
133
+ }
134
+ });
135
+
136
+ // Git Stage
137
+ this.app.post('/api/git/stage', async (req, res) => {
138
+ try {
139
+ const { files } = req.body;
140
+ const target = files.includes('*') ? '.' : files.map(f => `"${f}"`).join(' ');
141
+ await execAsync(`git add ${target}`, { cwd: this.workingDir });
142
+ res.json({ success: true });
143
+ } catch (error) {
144
+ res.status(500).json({ error: error.message });
145
+ }
146
+ });
147
+
148
+ // Git Unstage
149
+ this.app.post('/api/git/unstage', async (req, res) => {
150
+ try {
151
+ const { files } = req.body;
152
+ const target = files.includes('*') ? '' : files.map(f => `"${f}"`).join(' ');
153
+ await execAsync(`git reset HEAD ${target}`, { cwd: this.workingDir });
154
+ res.json({ success: true });
155
+ } catch (error) {
156
+ res.status(500).json({ error: error.message });
157
+ }
158
+ });
159
+
160
+ // Git Discard (NEW)
161
+ this.app.post('/api/git/discard', async (req, res) => {
162
+ try {
163
+ const { files } = req.body; // array or '*'
164
+
165
+ // Discard All
166
+ if (files.includes('*')) {
167
+ // Restore tracked files
168
+ try { await execAsync('git restore .', { cwd: this.workingDir }); } catch (e) {}
169
+ // Clean untracked files
170
+ try { await execAsync('git clean -fd', { cwd: this.workingDir }); } catch (e) {}
171
+ } else {
172
+ // Discard specific files
173
+ for (const file of files) {
174
+ // Try restore (for tracked modified/deleted)
175
+ try { await execAsync(`git restore "${file}"`, { cwd: this.workingDir }); } catch (e) {}
176
+ // Try clean (for untracked)
177
+ try { await execAsync(`git clean -f "${file}"`, { cwd: this.workingDir }); } catch (e) {}
178
+ }
179
+ }
180
+ res.json({ success: true });
181
+ } catch (error) {
182
+ console.error('Discard error:', error);
183
+ res.status(500).json({ error: error.message });
184
+ }
185
+ });
186
+
187
+ // Git Commit
188
+ this.app.post('/api/git/commit', async (req, res) => {
189
+ try {
190
+ const { message } = req.body;
191
+ if (!message) throw new Error('Commit message is required');
192
+ const safeMessage = message.replace(/"/g, '\\"');
193
+ await execAsync(`git commit -m "${safeMessage}"`, { cwd: this.workingDir });
194
+ res.json({ success: true });
195
+ } catch (error) {
196
+ res.status(500).json({ error: error.message });
197
+ }
198
+ });
199
+
200
+ // Get Diff
102
201
  this.app.get('/api/git/diff', async (req, res) => {
103
202
  try {
104
- const { stdout, stderr } = await execAsync('git diff HEAD', {
105
- cwd: this.workingDir,
106
- maxBuffer: 20 * 1024 * 1024
107
- });
203
+ const file = req.query.file;
204
+ const type = req.query.type || 'working';
205
+
206
+ let cmd = '';
207
+
208
+ if (type === 'staged') {
209
+ cmd = file ? `git diff --cached -- "${file}"` : `git diff --cached`;
210
+ } else {
211
+ if (file) {
212
+ const { stdout: isUntracked } = await execAsync(`git ls-files --others --exclude-standard "${file}"`, { cwd: this.workingDir });
213
+ if (isUntracked.trim()) {
214
+ const content = await fs.readFile(path.join(this.workingDir, file), 'utf8');
215
+ 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`;
216
+ content.split('\n').forEach(l => fakeDiff += `+${l}\n`);
217
+ return res.json({ diff: fakeDiff });
218
+ }
219
+ cmd = `git diff -- "${file}"`;
220
+ } else {
221
+ cmd = `git diff`;
222
+ }
223
+ }
224
+
225
+ const { stdout } = await execAsync(cmd, { cwd: this.workingDir, maxBuffer: 20 * 1024 * 1024 });
108
226
  res.json({ diff: stdout });
109
227
  } catch (error) {
110
- console.error(chalk.red('❌ [GIT] Error:'), error.message);
228
+ console.error(chalk.red('❌ [GIT DIFF] Error:'), error.message);
111
229
  res.json({ diff: '', error: error.message });
112
230
  }
113
231
  });
114
232
 
233
+ // --- GIT API END ---
234
+
115
235
  this.app.post('/api/analyze', async (req, res) => {
116
236
  const { path: projectPath, options = {}, specificFiles } = req.body;
117
237
  if (!projectPath) return res.status(400).json({ error: 'Missing path' });
@@ -1,4 +1,4 @@
1
- /* --- LAYOUT HEADER --- */
1
+ /* --- CONTAINER & LAYOUT --- */
2
2
  .git-toggle-group {
3
3
  display: flex;
4
4
  align-items: center;
@@ -42,24 +42,6 @@
42
42
  justify-content: center;
43
43
  }
44
44
 
45
- /* --- CONTAINER CHÍNH --- */
46
- .right-panel {
47
- position: relative;
48
- /* Quan trọng */
49
- }
50
-
51
- .d2h-file-list {
52
- position: fixed;
53
- top: 0;
54
- left: 0;
55
- z-index: 99;
56
- background-color: #0d1117;
57
- overflow: scroll;
58
- height: calc(100vh - 46px);
59
- width: 446px;
60
- top: 46px;
61
- }
62
-
63
45
  .git-view-container {
64
46
  position: absolute;
65
47
  top: 50px;
@@ -67,89 +49,275 @@
67
49
  right: 0;
68
50
  bottom: 0;
69
51
  z-index: 9999;
70
- /* Z-index cao nhất */
71
52
  background: #0d1117;
72
- /* Nền đen GitHub */
73
53
  display: none;
74
- flex-direction: column;
75
- overflow: scroll;
54
+ flex-direction: row;
55
+ overflow: hidden;
76
56
  }
77
57
 
78
58
  .git-view-container.active {
79
59
  display: flex;
80
60
  }
81
61
 
82
- /* --- DEBUG / SAFETY NET CSS --- */
83
- /* Ép toàn bộ text trong vùng code phải có màu sáng */
84
- .d2h-wrapper * {
85
- box-sizing: border-box;
62
+ /* --- SIDEBAR --- */
63
+ .git-sidebar {
64
+ width: 300px;
65
+ min-width: 250px;
66
+ background: #0d1117;
67
+ border-right: 1px solid #30363d;
68
+ display: flex;
69
+ flex-direction: column;
70
+ overflow-y: auto;
71
+ color: #c9d1d9;
72
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
73
+ user-select: none;
86
74
  }
87
75
 
88
- /* Force text color */
89
- .d2h-code-line-ctn,
90
- .d2h-code-line,
91
- .hljs {
92
- color: #e6edf3 !important;
93
- /* Trắng xám */
94
- background: transparent !important;
76
+ /* --- COMMIT SECTION --- */
77
+ .git-commit-section {
78
+ padding: 10px;
79
+ border-bottom: 1px solid #30363d;
80
+ background: #0d1117;
95
81
  }
96
82
 
97
- /* Force line number color */
98
- .d2h-code-side-linenumber {
99
- color: #6e7681 !important;
100
- background: #0d1117 !important;
101
- border-color: #30363d !important;
83
+ .git-commit-input {
84
+ width: 100%;
85
+ background: #161b22;
86
+ border: 1px solid #30363d;
87
+ color: #c9d1d9;
88
+ padding: 6px 8px;
89
+ border-radius: 4px;
90
+ font-family: inherit;
91
+ font-size: 13px;
92
+ resize: vertical;
93
+ min-height: 32px;
94
+ margin-bottom: 8px;
102
95
  }
103
96
 
104
- /* Fix background diff */
105
- .d2h-ins {
106
- background-color: rgba(46, 160, 67, 0.15) !important;
97
+ .git-commit-input:focus {
98
+ outline: 1px solid #58a6ff;
99
+ border-color: #58a6ff;
107
100
  }
108
101
 
109
- .d2h-del {
110
- background-color: rgba(248, 81, 73, 0.15) !important;
102
+ .git-commit-btn {
103
+ width: 100%;
104
+ background: #0078d4;
105
+ color: white;
106
+ border: none;
107
+ padding: 6px 12px;
108
+ border-radius: 2px;
109
+ cursor: pointer;
110
+ font-size: 13px;
111
+ font-weight: 500;
112
+ display: flex;
113
+ align-items: center;
114
+ justify-content: center;
115
+ gap: 6px;
111
116
  }
112
117
 
113
- /* Layout */
114
- .d2h-files-wrapper {
115
- flex: 1;
116
- overflow: auto;
117
- background: #0d1117;
118
+ .git-commit-btn:hover {
119
+ background: #026ec1;
118
120
  }
119
121
 
120
- .d2h-file-list-wrapper {
121
- width: 250px;
122
- flex-shrink: 0;
123
- background: #0d1117;
124
- border-right: 1px solid #30363d;
125
- display: flex;
126
- flex-direction: column;
122
+ .git-commit-btn:disabled {
123
+ background: #30363d;
124
+ color: #8b949e;
125
+ cursor: not-allowed;
127
126
  }
128
127
 
129
- .d2h-file-list-header,
130
- .d2h-file-header {
131
- background: #161b22 !important;
132
- border-bottom: 1px solid #30363d !important;
133
- color: #e6edf3 !important;
128
+ /* --- SECTIONS & TREE --- */
129
+ .git-section {
130
+ margin-bottom: 0;
131
+ }
132
+
133
+ .git-section-header {
134
+ padding: 8px 16px;
135
+ font-size: 11px;
136
+ font-weight: bold;
137
+ text-transform: uppercase;
138
+ color: #8b949e;
139
+ background: #161b22;
140
+ display: flex;
141
+ justify-content: space-between;
142
+ align-items: center;
143
+ cursor: pointer;
134
144
  position: sticky;
135
145
  top: 0;
136
146
  z-index: 10;
147
+ border-bottom: 1px solid #21262d;
148
+ }
149
+
150
+ .git-section-header:hover {
151
+ color: #c9d1d9;
152
+ }
153
+
154
+ .git-badge {
155
+ background: #30363d;
156
+ color: #c9d1d9;
157
+ padding: 2px 6px;
158
+ border-radius: 10px;
159
+ font-size: 10px;
160
+ min-width: 18px;
161
+ text-align: center;
162
+ }
163
+
164
+ .git-tree-root {
165
+ list-style: none;
166
+ padding: 0;
167
+ margin: 0;
137
168
  }
138
169
 
139
- .d2h-file-list a {
140
- color: #8b949e !important;
141
- text-decoration: none;
142
- display: block;
143
- padding: 5px 10px;
170
+ .git-tree-node {
171
+ list-style: none;
144
172
  }
145
173
 
146
- .d2h-file-list a:hover {
147
- color: #58a6ff !important;
174
+ .git-tree-content {
175
+ display: flex;
176
+ align-items: center;
177
+ padding: 4px 8px;
178
+ cursor: pointer;
179
+ font-size: 13px;
180
+ height: 24px;
181
+ color: #c9d1d9;
182
+ white-space: nowrap;
183
+ }
184
+
185
+ .git-tree-content:hover {
148
186
  background: #161b22;
149
187
  }
150
188
 
151
- /* Ẩn rác */
152
- .d2h-file-switch,
153
- .d2h-tag {
154
- display: none !important;
155
- }
189
+ .git-tree-content.selected {
190
+ background: #30363d;
191
+ color: #fff;
192
+ }
193
+
194
+ .git-indent-guide {
195
+ display: inline-block;
196
+ width: 16px;
197
+ height: 100%;
198
+ flex-shrink: 0;
199
+ }
200
+
201
+ .git-arrow {
202
+ width: 16px;
203
+ text-align: center;
204
+ font-size: 10px;
205
+ color: #8b949e;
206
+ transition: transform 0.15s;
207
+ display: inline-block;
208
+ flex-shrink: 0;
209
+ }
210
+
211
+ .git-tree-node.collapsed > .git-tree-content > .git-arrow {
212
+ transform: rotate(-90deg);
213
+ }
214
+
215
+ .git-tree-node.collapsed > ul {
216
+ display: none;
217
+ }
218
+
219
+ .git-icon {
220
+ margin-right: 6px;
221
+ font-weight: bold;
222
+ width: 16px;
223
+ text-align: center;
224
+ flex-shrink: 0;
225
+ font-size: 14px;
226
+ }
227
+
228
+ /* Status Colors */
229
+ .git-status-M { color: #d29922; }
230
+ .git-status-A, .git-status-U { color: #3fb950; }
231
+ .git-status-D { color: #f85149; text-decoration: line-through; }
232
+ .git-status-R { color: #d29922; }
233
+
234
+ .git-label {
235
+ flex: 1;
236
+ overflow: hidden;
237
+ text-overflow: ellipsis;
238
+ white-space: nowrap;
239
+ }
240
+
241
+ .git-dir-label {
242
+ color: #c9d1d9;
243
+ }
244
+
245
+ .git-file-label {
246
+ color: #8b949e;
247
+ }
248
+ .git-tree-content:hover .git-file-label,
249
+ .git-tree-content.selected .git-file-label {
250
+ color: #e6edf3;
251
+ }
252
+
253
+ /* ACTIONS */
254
+ .git-actions {
255
+ display: none;
256
+ margin-left: 6px;
257
+ flex-shrink: 0;
258
+ }
259
+
260
+ .git-tree-content:hover .git-actions {
261
+ display: flex;
262
+ gap: 4px;
263
+ }
264
+
265
+ .git-btn-action {
266
+ background: transparent;
267
+ border: none;
268
+ color: #c9d1d9;
269
+ cursor: pointer;
270
+ padding: 0 4px;
271
+ border-radius: 4px;
272
+ font-size: 14px;
273
+ line-height: 1;
274
+ }
275
+
276
+ .git-btn-action:hover {
277
+ background: #30363d;
278
+ color: #fff;
279
+ }
280
+
281
+ /* Destructive Action (Discard) */
282
+ .git-btn-action.destructive:hover {
283
+ background: #f85149;
284
+ color: #fff;
285
+ }
286
+
287
+ /* --- DIFF AREA --- */
288
+ .git-diff-area {
289
+ flex: 1;
290
+ overflow: auto;
291
+ background: #0d1117;
292
+ position: relative;
293
+ display: flex;
294
+ flex-direction: column;
295
+ }
296
+
297
+ .git-empty-state {
298
+ display: flex;
299
+ flex-direction: column;
300
+ align-items: center;
301
+ justify-content: center;
302
+ height: 100%;
303
+ color: #8b949e;
304
+ }
305
+
306
+ /* Diff2Html Overrides */
307
+ .d2h-wrapper * { box-sizing: border-box; }
308
+ .d2h-file-list-wrapper { display: none; }
309
+ .d2h-wrapper { margin: 0; }
310
+ .d2h-file-header { display: none; }
311
+ .d2h-code-line-ctn, .d2h-code-line, .hljs {
312
+ color: #e6edf3 !important;
313
+ background: transparent !important;
314
+ font-family: Menlo, Monaco, Consolas, monospace;
315
+ font-size: 12px;
316
+ }
317
+ .d2h-code-side-linenumber {
318
+ background: #0d1117 !important;
319
+ border-color: #30363d !important;
320
+ color: #6e7681 !important;
321
+ }
322
+ .d2h-ins { background-color: rgba(46, 160, 67, 0.15) !important; }
323
+ .d2h-del { background-color: rgba(248, 81, 73, 0.15) !important; }