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.
- package/INSTALLATION_METHODS.md +181 -0
- package/LICENSE +21 -0
- package/README.md +199 -0
- package/bin/npm-wrapper.js +171 -0
- package/bin/rg +0 -0
- package/bin/rg.exe +0 -0
- package/config.yaml.example +159 -0
- package/package.json +42 -0
- package/requirements.txt +7 -0
- package/scripts/install.js +132 -0
- package/setup.bat +114 -0
- package/setup.sh +135 -0
- package/src/__init__.py +4 -0
- package/src/core/__init__.py +1 -0
- package/src/core/agentic.py +2342 -0
- package/src/core/chat_manager.py +1201 -0
- package/src/core/config_manager.py +269 -0
- package/src/core/init.py +161 -0
- package/src/core/sub_agent.py +174 -0
- package/src/exceptions.py +75 -0
- package/src/llm/__init__.py +1 -0
- package/src/llm/client.py +149 -0
- package/src/llm/config.py +445 -0
- package/src/llm/prompts.py +569 -0
- package/src/llm/providers.py +402 -0
- package/src/llm/token_tracker.py +220 -0
- package/src/ui/__init__.py +1 -0
- package/src/ui/banner.py +103 -0
- package/src/ui/commands.py +489 -0
- package/src/ui/displays.py +167 -0
- package/src/ui/main.py +351 -0
- package/src/ui/prompt_utils.py +162 -0
- package/src/utils/__init__.py +1 -0
- package/src/utils/editor.py +158 -0
- package/src/utils/gitignore_filter.py +149 -0
- package/src/utils/logger.py +254 -0
- package/src/utils/markdown.py +32 -0
- package/src/utils/settings.py +94 -0
- package/src/utils/tools/__init__.py +55 -0
- package/src/utils/tools/command_executor.py +217 -0
- package/src/utils/tools/create_file.py +143 -0
- package/src/utils/tools/definitions.py +193 -0
- package/src/utils/tools/directory.py +374 -0
- package/src/utils/tools/file_editor.py +345 -0
- package/src/utils/tools/file_helpers.py +109 -0
- package/src/utils/tools/file_reader.py +331 -0
- package/src/utils/tools/formatters.py +458 -0
- package/src/utils/tools/parallel_executor.py +195 -0
- package/src/utils/validation.py +117 -0
- package/src/utils/web_search.py +71 -0
- package/vmcode-proxy/.env.example +5 -0
- package/vmcode-proxy/README.md +235 -0
- package/vmcode-proxy/package-lock.json +947 -0
- package/vmcode-proxy/package.json +20 -0
- package/vmcode-proxy/server.js +248 -0
- 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
|
+
});
|