vmcode-cli 1.0.0

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.
Files changed (56) hide show
  1. package/INSTALLATION_METHODS.md +181 -0
  2. package/LICENSE +21 -0
  3. package/README.md +199 -0
  4. package/bin/npm-wrapper.js +171 -0
  5. package/bin/rg +0 -0
  6. package/bin/rg.exe +0 -0
  7. package/config.yaml.example +159 -0
  8. package/package.json +42 -0
  9. package/requirements.txt +7 -0
  10. package/scripts/install.js +132 -0
  11. package/setup.bat +114 -0
  12. package/setup.sh +135 -0
  13. package/src/__init__.py +4 -0
  14. package/src/core/__init__.py +1 -0
  15. package/src/core/agentic.py +2342 -0
  16. package/src/core/chat_manager.py +1201 -0
  17. package/src/core/config_manager.py +269 -0
  18. package/src/core/init.py +161 -0
  19. package/src/core/sub_agent.py +174 -0
  20. package/src/exceptions.py +75 -0
  21. package/src/llm/__init__.py +1 -0
  22. package/src/llm/client.py +149 -0
  23. package/src/llm/config.py +445 -0
  24. package/src/llm/prompts.py +569 -0
  25. package/src/llm/providers.py +402 -0
  26. package/src/llm/token_tracker.py +220 -0
  27. package/src/ui/__init__.py +1 -0
  28. package/src/ui/banner.py +103 -0
  29. package/src/ui/commands.py +489 -0
  30. package/src/ui/displays.py +167 -0
  31. package/src/ui/main.py +351 -0
  32. package/src/ui/prompt_utils.py +162 -0
  33. package/src/utils/__init__.py +1 -0
  34. package/src/utils/editor.py +158 -0
  35. package/src/utils/gitignore_filter.py +149 -0
  36. package/src/utils/logger.py +254 -0
  37. package/src/utils/markdown.py +32 -0
  38. package/src/utils/settings.py +94 -0
  39. package/src/utils/tools/__init__.py +55 -0
  40. package/src/utils/tools/command_executor.py +217 -0
  41. package/src/utils/tools/create_file.py +143 -0
  42. package/src/utils/tools/definitions.py +193 -0
  43. package/src/utils/tools/directory.py +374 -0
  44. package/src/utils/tools/file_editor.py +345 -0
  45. package/src/utils/tools/file_helpers.py +109 -0
  46. package/src/utils/tools/file_reader.py +331 -0
  47. package/src/utils/tools/formatters.py +458 -0
  48. package/src/utils/tools/parallel_executor.py +195 -0
  49. package/src/utils/validation.py +117 -0
  50. package/src/utils/web_search.py +71 -0
  51. package/vmcode-proxy/.env.example +5 -0
  52. package/vmcode-proxy/README.md +235 -0
  53. package/vmcode-proxy/package-lock.json +947 -0
  54. package/vmcode-proxy/package.json +20 -0
  55. package/vmcode-proxy/server.js +248 -0
  56. package/vmcode-proxy/server.js.bak +157 -0
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "vmcode-proxy",
3
+ "version": "1.0.0",
4
+ "description": "Proxy server for vmCode free tier using OpenRouter",
5
+ "main": "server.js",
6
+ "scripts": {
7
+ "start": "node server.js",
8
+ "dev": "node server.js"
9
+ },
10
+ "dependencies": {
11
+ "dotenv": "^16.3.1",
12
+ "express": "^4.18.2",
13
+ "express-rate-limit": "^8.2.1",
14
+ "cors": "^2.8.5",
15
+ "compression": "^1.7.4"
16
+ },
17
+ "engines": {
18
+ "node": ">=18.0.0"
19
+ }
20
+ }
@@ -0,0 +1,248 @@
1
+ const express = require('express');
2
+ const rateLimit = require('express-rate-limit');
3
+ const cors = require('cors');
4
+ const compression = require('compression');
5
+ require('dotenv').config();
6
+
7
+ const app = express();
8
+
9
+ // Middleware
10
+ app.use(cors());
11
+ app.use(compression());
12
+ app.use(express.json({ limit: '1mb' }));
13
+
14
+ // Rate limiting: 100 requests per 15 minutes per IP
15
+ const limiter = rateLimit({
16
+ windowMs: 15 * 60 * 1000,
17
+ max: 100,
18
+ keyGenerator: (req) => req.ip,
19
+ message: { error: 'Rate limit exceeded. Try again later.' }
20
+ });
21
+
22
+ // Daily hard limit: 10,000 requests/day total
23
+ let todayRequestCount = 0;
24
+ let lastResetDate = new Date().toDateString();
25
+ const DAILY_LIMIT = 10000;
26
+
27
+ // Free tier: per-IP daily limit
28
+ const FREE_TIER_DAILY_LIMIT = 500; // 500 requests per day per IP
29
+ const ipDailyUsage = new Map(); // Map<ip, {count: number, date: string}>
30
+
31
+ /**
32
+ * Cleanup function to prevent memory leaks
33
+ * Removes IP entries older than 2 days
34
+ */
35
+ function cleanupOldIps() {
36
+ const today = new Date();
37
+ const twoDaysAgo = new Date(today);
38
+ twoDaysAgo.setDate(today.getDate() - 2);
39
+
40
+ let cleaned = 0;
41
+
42
+ for (const [ip, data] of ipDailyUsage.entries()) {
43
+ const entryDate = new Date(data.date);
44
+
45
+ if (entryDate < twoDaysAgo) {
46
+ ipDailyUsage.delete(ip);
47
+ cleaned++;
48
+ }
49
+ }
50
+
51
+ if (cleaned > 0) {
52
+ console.log(`[${new Date().toISOString()}] Cleaned up ${cleaned} stale IP entries. Active IPs: ${ipDailyUsage.size}`);
53
+ }
54
+ }
55
+
56
+ // Run cleanup every hour
57
+ setInterval(cleanupOldIps, 60 * 60 * 1000);
58
+
59
+ /**
60
+ * Get next midnight timestamp for reset time
61
+ */
62
+ function getNextMidnight() {
63
+ const tomorrow = new Date();
64
+ tomorrow.setDate(tomorrow.getDate() + 1);
65
+ tomorrow.setHours(0, 0, 0, 0);
66
+ return tomorrow.getTime();
67
+ }
68
+
69
+ // Health check endpoint
70
+ app.get('/', (req, res) => {
71
+ res.json({ status: 'ok', service: 'vmcode-proxy' });
72
+ });
73
+
74
+ app.get('/health', (req, res) => {
75
+ const clientIp = req.ip;
76
+ const today = new Date().toDateString();
77
+ const ipUsage = ipDailyUsage.get(clientIp);
78
+
79
+ res.json({
80
+ status: 'ok',
81
+ service: 'vmcode-proxy',
82
+ todayRequests: todayRequestCount,
83
+ dailyLimit: DAILY_LIMIT,
84
+ percentUsed: Math.round((todayRequestCount / DAILY_LIMIT) * 100),
85
+ // Per-IP usage
86
+ ipUsage: {
87
+ requests: ipUsage?.count || 0,
88
+ limit: FREE_TIER_DAILY_LIMIT,
89
+ percentUsed: ipUsage ? Math.round((ipUsage.count / FREE_TIER_DAILY_LIMIT) * 100) : 0
90
+ },
91
+ activeIps: ipDailyUsage.size
92
+ });
93
+ });
94
+
95
+ // Stats endpoint
96
+ app.get('/stats', (req, res) => {
97
+ const clientIp = req.ip;
98
+ const today = new Date().toDateString();
99
+ const ipUsage = ipDailyUsage.get(clientIp);
100
+
101
+ res.json({
102
+ todayRequests: todayRequestCount,
103
+ dailyLimit: DAILY_LIMIT,
104
+ date: lastResetDate,
105
+ percentUsed: Math.round((todayRequestCount / DAILY_LIMIT) * 100),
106
+ status: 'ok',
107
+ // Per-IP usage
108
+ ipUsage: {
109
+ ip: clientIp,
110
+ requests: ipUsage?.count || 0,
111
+ limit: FREE_TIER_DAILY_LIMIT,
112
+ percentUsed: ipUsage ? Math.round((ipUsage.count / FREE_TIER_DAILY_LIMIT) * 100) : 0,
113
+ date: ipUsage?.date || today
114
+ },
115
+ activeIps: ipDailyUsage.size
116
+ });
117
+ });
118
+
119
+ // Chat completion proxy endpoint
120
+ app.post('/chat', limiter, async (req, res) => {
121
+ try {
122
+ const clientIp = req.ip;
123
+ const today = new Date().toDateString();
124
+
125
+ // Reset global daily counter if it's a new day
126
+ if (today !== lastResetDate) {
127
+ todayRequestCount = 0;
128
+ lastResetDate = today;
129
+ }
130
+
131
+ // Reset per-IP usage if it's a new day for this IP
132
+ if (!ipDailyUsage.has(clientIp) || ipDailyUsage.get(clientIp).date !== today) {
133
+ ipDailyUsage.set(clientIp, { count: 0, date: today });
134
+ }
135
+
136
+ const ipUsage = ipDailyUsage.get(clientIp);
137
+
138
+ // Check free tier per-IP daily limit
139
+ if (ipUsage.count >= FREE_TIER_DAILY_LIMIT) {
140
+ return res.status(429).json({
141
+ error: 'Free tier daily limit exceeded. Try again tomorrow.',
142
+ limit: FREE_TIER_DAILY_LIMIT,
143
+ resetTime: getNextMidnight()
144
+ });
145
+ }
146
+
147
+ // Check daily hard limit
148
+ if (todayRequestCount >= DAILY_LIMIT) {
149
+ return res.status(429).json({
150
+ error: 'Daily limit exceeded. Try again tomorrow.',
151
+ resetTime: getNextMidnight()
152
+ });
153
+ }
154
+
155
+ const apiKey = process.env.OPENROUTER_API_KEY;
156
+
157
+ if (!apiKey) {
158
+ return res.status(500).json({
159
+ error: 'OPENROUTER_API_KEY not configured'
160
+ });
161
+ }
162
+
163
+ // Validate request body
164
+ if (!req.body.messages || !Array.isArray(req.body.messages)) {
165
+ return res.status(400).json({
166
+ error: 'Invalid request: messages array required'
167
+ });
168
+ }
169
+
170
+ // Increment counters and log
171
+ todayRequestCount++;
172
+ ipUsage.count++;
173
+ console.log(`[${new Date().toISOString()}] Request ${todayRequestCount}/${DAILY_LIMIT} from ${req.ip} (IP: ${ipUsage.count}/${FREE_TIER_DAILY_LIMIT})`);
174
+
175
+ // Fetch with timeout
176
+ const controller = new AbortController();
177
+ const timeout = setTimeout(() => controller.abort(), 60000); // 60 second timeout
178
+
179
+ const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {
180
+ method: 'POST',
181
+ headers: {
182
+ 'Authorization': `Bearer ${apiKey}`,
183
+ 'Content-Type': 'application/json',
184
+ 'HTTP-Referer': 'https://github.com/vincentm65/vmCode-CLI',
185
+ 'X-Title': 'vmCode'
186
+ },
187
+ body: JSON.stringify({
188
+ model: 'z-ai/glm-4.5-air:free',
189
+ ...req.body
190
+ }),
191
+ signal: controller.signal
192
+ });
193
+
194
+ clearTimeout(timeout);
195
+
196
+ const data = await response.json();
197
+
198
+ if (!response.ok) {
199
+ return res.status(response.status).json(data);
200
+ }
201
+
202
+ res.json(data);
203
+ } catch (error) {
204
+ console.error('Proxy error:', error);
205
+
206
+ if (error.name === 'AbortError') {
207
+ return res.status(504).json({
208
+ error: 'Request timeout'
209
+ });
210
+ }
211
+
212
+ res.status(500).json({
213
+ error: 'Internal server error',
214
+ message: error.message
215
+ });
216
+ }
217
+ });
218
+
219
+ // Graceful shutdown
220
+ let isShuttingDown = false;
221
+
222
+ function gracefulShutdown(signal) {
223
+ if (isShuttingDown) {
224
+ console.log('Force shutdown...');
225
+ process.exit(1);
226
+ }
227
+
228
+ isShuttingDown = true;
229
+ console.log(`${signal} received. Starting graceful shutdown...`);
230
+
231
+ // Give 10 seconds for in-flight requests to complete
232
+ setTimeout(() => {
233
+ console.log('Closing server...');
234
+ server.close(() => {
235
+ console.log('Server closed.');
236
+ process.exit(0);
237
+ });
238
+ }, 10000);
239
+ }
240
+
241
+ const PORT = process.env.PORT || 3000;
242
+ const server = app.listen(PORT, () => {
243
+ console.log(`vmcode-proxy running on port ${PORT}`);
244
+ console.log(`OPENROUTER_API_KEY configured: ${!!process.env.OPENROUTER_API_KEY}`);
245
+ });
246
+
247
+ process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
248
+ process.on('SIGINT', () => gracefulShutdown('SIGINT'));
@@ -0,0 +1,157 @@
1
+ const express = require('express');
2
+ const rateLimit = require('express-rate-limit');
3
+ require('dotenv').config();
4
+
5
+ const app = express();
6
+
7
+ // Middleware
8
+ app.use(express.json());
9
+
10
+ // Rate limiting: 100 requests per 15 minutes per IP
11
+ const limiter = rateLimit({
12
+ windowMs: 15 * 60 * 1000,
13
+ max: 100,
14
+ keyGenerator: (req) => req.ip,
15
+ message: { error: 'Rate limit exceeded. Try again later.' }
16
+ });
17
+
18
+ // Daily hard limit: 10,000 requests/day total
19
+ let todayRequestCount = 0;
20
+ let lastResetDate = new Date().toDateString();
21
+ const DAILY_LIMIT = 10000;
22
+
23
+ // Free tier: per-IP daily limit
24
+ const FREE_TIER_DAILY_LIMIT = 500; // 500 requests per day per IP
25
+ const ipDailyUsage = new Map(); // Map<ip, {count: number, date: string}>
26
+
27
+ // Health check endpoint
28
+ app.get('/', (req, res) => {
29
+ res.json({ status: 'ok', service: 'vmcode-proxy' });
30
+ });
31
+
32
+ app.get('/health', (req, res) => {
33
+ const clientIp = req.ip;
34
+ const today = new Date().toDateString();
35
+ const ipUsage = ipDailyUsage.get(clientIp);
36
+
37
+ res.json({
38
+ status: 'ok',
39
+ service: 'vmcode-proxy',
40
+ todayRequests: todayRequestCount,
41
+ dailyLimit: DAILY_LIMIT,
42
+ percentUsed: Math.round((todayRequestCount / DAILY_LIMIT) * 100),
43
+ // Per-IP usage
44
+ ipUsage: {
45
+ requests: ipUsage?.count || 0,
46
+ limit: FREE_TIER_DAILY_LIMIT,
47
+ percentUsed: ipUsage ? Math.round((ipUsage.count / FREE_TIER_DAILY_LIMIT) * 100) : 0
48
+ }
49
+ });
50
+ });
51
+
52
+ // Stats endpoint
53
+ app.get('/stats', (req, res) => {
54
+ const clientIp = req.ip;
55
+ const today = new Date().toDateString();
56
+ const ipUsage = ipDailyUsage.get(clientIp);
57
+
58
+ res.json({
59
+ todayRequests: todayRequestCount,
60
+ dailyLimit: DAILY_LIMIT,
61
+ date: lastResetDate,
62
+ percentUsed: Math.round((todayRequestCount / DAILY_LIMIT) * 100),
63
+ status: 'ok',
64
+ // Per-IP usage
65
+ ipUsage: {
66
+ ip: clientIp,
67
+ requests: ipUsage?.count || 0,
68
+ limit: FREE_TIER_DAILY_LIMIT,
69
+ percentUsed: ipUsage ? Math.round((ipUsage.count / FREE_TIER_DAILY_LIMIT) * 100) : 0,
70
+ date: ipUsage?.date || today
71
+ }
72
+ });
73
+ });
74
+
75
+ // Chat completion proxy endpoint
76
+ app.post('/chat', limiter, async (req, res) => {
77
+ try {
78
+ const clientIp = req.ip;
79
+ const today = new Date().toDateString();
80
+
81
+ // Reset global daily counter if it's a new day
82
+ if (today !== lastResetDate) {
83
+ todayRequestCount = 0;
84
+ lastResetDate = today;
85
+ }
86
+
87
+ // Reset per-IP usage if it's a new day for this IP
88
+ if (!ipDailyUsage.has(clientIp) || ipDailyUsage.get(clientIp).date !== today) {
89
+ ipDailyUsage.set(clientIp, { count: 0, date: today });
90
+ }
91
+
92
+ const ipUsage = ipDailyUsage.get(clientIp);
93
+
94
+ // Check free tier per-IP daily limit
95
+ if (ipUsage.count >= FREE_TIER_DAILY_LIMIT) {
96
+ return res.status(429).json({
97
+ error: 'Free tier daily limit exceeded. Try again tomorrow.',
98
+ limit: FREE_TIER_DAILY_LIMIT,
99
+ resetTime: new Date().setHours(24, 0, 0, 0)
100
+ });
101
+ }
102
+
103
+ // Check daily hard limit
104
+ if (todayRequestCount >= DAILY_LIMIT) {
105
+ return res.status(429).json({
106
+ error: 'Daily limit exceeded. Try again tomorrow.'
107
+ });
108
+ }
109
+
110
+ const apiKey = process.env.OPENROUTER_API_KEY;
111
+
112
+ if (!apiKey) {
113
+ return res.status(500).json({
114
+ error: 'OPENROUTER_API_KEY not configured'
115
+ });
116
+ }
117
+
118
+ // Increment counters and log
119
+ todayRequestCount++;
120
+ ipUsage.count++;
121
+ console.log(`[${new Date().toISOString()}] Request ${todayRequestCount}/${DAILY_LIMIT} from ${req.ip} (IP: ${ipUsage.count}/${FREE_TIER_DAILY_LIMIT})`);
122
+
123
+ const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {
124
+ method: 'POST',
125
+ headers: {
126
+ 'Authorization': `Bearer ${apiKey}`,
127
+ 'Content-Type': 'application/json',
128
+ 'HTTP-Referer': 'https://github.com/vincentm65/vmCode-CLI',
129
+ 'X-Title': 'vmCode'
130
+ },
131
+ body: JSON.stringify({
132
+ model: 'openrouter/free',
133
+ ...req.body
134
+ })
135
+ });
136
+
137
+ const data = await response.json();
138
+
139
+ if (!response.ok) {
140
+ return res.status(response.status).json(data);
141
+ }
142
+
143
+ res.json(data);
144
+ } catch (error) {
145
+ console.error('Proxy error:', error);
146
+ res.status(500).json({
147
+ error: 'Internal server error',
148
+ message: error.message
149
+ });
150
+ }
151
+ });
152
+
153
+ const PORT = process.env.PORT || 3000;
154
+ app.listen(PORT, () => {
155
+ console.log(`vmcode-proxy running on port ${PORT}`);
156
+ console.log(`OPENROUTER_API_KEY configured: ${!!process.env.OPENROUTER_API_KEY}`);
157
+ });