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,117 @@
1
+ """Command validation and duplicate detection."""
2
+
3
+ import os
4
+ import shlex
5
+
6
+
7
+ # Commands that overlap with native tools (blocked - use the tool instead)
8
+ BLOCKED_OVERLAPS = {
9
+ # Code search (use rg tool)
10
+ "rg", "rg.exe", "ripgrep",
11
+
12
+ # File reading (use read_file tool)
13
+ "cat", "get-content", "type",
14
+
15
+ # Directory listing (use list_directory tool)
16
+ "ls", "get-childitem", "dir",
17
+
18
+ # File creation (use create_file tool)
19
+ "touch", "new-item",
20
+
21
+ # File editing (use edit_file tool)
22
+ "set-content", "add-content", "echo", "tee",
23
+ }
24
+
25
+
26
+ def normalize_for_comparison(command):
27
+ """Normalize command for duplicate detection."""
28
+ # Remove shell prefix (e.g., "powershell ") and extra whitespace
29
+ cmd = command.strip().lower()
30
+ if cmd.startswith("powershell "):
31
+ cmd = cmd[11:].strip()
32
+ return cmd
33
+
34
+
35
+ def check_for_duplicate(chat_manager, command):
36
+ """Check if command was already run and return simple warning.
37
+
38
+ Returns:
39
+ tuple: (is_duplicate, redirect_message)
40
+ """
41
+ normalized = normalize_for_comparison(command)
42
+
43
+ if normalized in chat_manager.command_history:
44
+ redirect_msg = (
45
+ f"exit_code=DUPLICATE\n"
46
+ f"This exact command was already executed: {command}\n\n"
47
+ f"The result is already in your conversation history.\n"
48
+ f"Try a DIFFERENT command with different search terms, "
49
+ f"different file paths, or a different approach entirely."
50
+ )
51
+ return True, redirect_msg
52
+
53
+ # Add to history
54
+ chat_manager.command_history.append(normalized)
55
+ return False, None
56
+
57
+
58
+ def _tokenize_segment(segment):
59
+ use_posix = os.name != "nt"
60
+ try:
61
+ return shlex.split(segment, posix=use_posix)
62
+ except ValueError:
63
+ return segment.split()
64
+
65
+
66
+ def check_command(command):
67
+ """Validate command against safety checks.
68
+
69
+ Args:
70
+ command: Command string to validate
71
+
72
+ Returns:
73
+ tuple: (is_safe, reason) - is_safe is True if command is allowed
74
+ If is_safe is True, the caller should check approval requirements separately
75
+ """
76
+ command = command.strip()
77
+ if not command:
78
+ return False, "empty command"
79
+
80
+ # Strip "powershell " prefix if present (legacy support for Windows users)
81
+ if command.lower().startswith("powershell "):
82
+ command = command[len("powershell "):].strip()
83
+
84
+ # Block dangerous operators (command chaining and redirection)
85
+ # Allow && for conditional chaining (stops on error - safer than ; or &)
86
+ blocked_operators = (";", ">", "<", "`", "|")
87
+ if any(token in command for token in blocked_operators):
88
+ return False, "contains disallowed shell operators"
89
+
90
+ # Note: && is allowed for conditional chaining. The agent will display
91
+ # a warning in debug mode when using && for multi-step commands.
92
+
93
+ # After stripping prefix, reject if it still starts with "powershell"
94
+ if command.lower().startswith("powershell"):
95
+ return False, "nested powershell invocation"
96
+
97
+ # Tokenize and validate command name
98
+ tokens = _tokenize_segment(command)
99
+ if not tokens:
100
+ return False, "empty command"
101
+
102
+ cmd_name = tokens[0].lower()
103
+
104
+ # Block commands that overlap with native tools
105
+ if cmd_name in BLOCKED_OVERLAPS:
106
+ tool_map = {
107
+ "rg": "rg tool", "rg.exe": "rg tool", "ripgrep": "rg tool",
108
+ "cat": "read_file tool", "get-content": "read_file tool", "type": "read_file tool",
109
+ "ls": "list_directory tool", "get-childitem": "list_directory tool", "dir": "list_directory tool",
110
+ "touch": "create_file tool", "new-item": "create_file tool",
111
+ "set-content": "edit_file tool", "add-content": "edit_file tool", "echo": "edit_file tool", "tee": "edit_file tool",
112
+ }
113
+ tool_suggestion = tool_map.get(cmd_name, "appropriate native tool")
114
+ return False, f"command '{cmd_name}' overlaps with {tool_suggestion}. Use the native tool instead."
115
+
116
+ # Allow all other commands
117
+ return True, None
@@ -0,0 +1,71 @@
1
+ """Web search using DuckDuckGo (no API key required)."""
2
+
3
+ from ddgs import DDGS
4
+ from exceptions import LLMConnectionError
5
+
6
+
7
+ def run_web_search(arguments, console):
8
+ """Execute web search using DuckDuckGo and return formatted results.
9
+
10
+ Args:
11
+ arguments: {
12
+ "query": "search terms to look for",
13
+ "num_results": 5 # optional, number of results (default: 5, max: 10)
14
+ }
15
+ console: Rich console for output
16
+
17
+ Returns:
18
+ str: Formatted search results with metadata for model consumption
19
+
20
+ Raises:
21
+ LLMConnectionError: If network search fails
22
+ """
23
+ query = arguments.get("query")
24
+ num_results = arguments.get("num_results", 5)
25
+
26
+ if not query:
27
+ raise LLMConnectionError(
28
+ "Missing required parameter: query",
29
+ details={"arguments": arguments}
30
+ )
31
+
32
+ # Validate and clamp num_results between 1 and 10
33
+ try:
34
+ num_results = max(1, min(10, int(num_results)))
35
+ except (ValueError, TypeError):
36
+ num_results = 5
37
+
38
+ try:
39
+ with DDGS() as ddgs:
40
+ results = list(ddgs.text(query, max_results=num_results))
41
+
42
+ if not results:
43
+ return "results_found=0\nNo results found.\n\n"
44
+
45
+ # Format results for model only (not displayed to console)
46
+ output_lines = []
47
+ for idx, result in enumerate(results, 1):
48
+ title = result.get("title", "Untitled")
49
+ url = result.get("href", "N/A")
50
+ body = result.get("body", "No content")
51
+
52
+ output_lines.append(f"[{idx}] {title}")
53
+ output_lines.append(f"URL: {url}")
54
+ output_lines.append(f"Snippet: {body}")
55
+ if idx < len(results):
56
+ output_lines.append("")
57
+
58
+ # Build result string with metadata for model
59
+ result_content = "\n".join(output_lines)
60
+ return f"results_found={len(results)}\n{result_content}\n\n"
61
+
62
+ except LLMConnectionError:
63
+ # Re-raise our custom exceptions
64
+ raise
65
+ except Exception as e:
66
+ console.print(f"Web search failed: {e}", style="red")
67
+ raise LLMConnectionError(
68
+ f"Failed to perform web search",
69
+ details={"query": query, "original_error": str(e)}
70
+ )
71
+
@@ -0,0 +1,5 @@
1
+ # OpenRouter API Key (get yours at https://openrouter.ai/)
2
+ OPENROUTER_API_KEY=sk-or-your-openrouter-api-key-here
3
+
4
+ # Port (default: 3000, Vercel will override this)
5
+ PORT=3000
@@ -0,0 +1,235 @@
1
+ # vmCode Proxy Server
2
+
3
+ Production-ready proxy server for vmCode free tier using OpenRouter's `z-ai/glm-4.5-air:free` model.
4
+
5
+ ## Features
6
+
7
+ - **Rate Limiting**: Multi-tier protection
8
+ - 15-minute window: 100 requests per IP
9
+ - Daily per-IP: 500 requests (free tier)
10
+ - Daily global: 10,000 requests (hard limit)
11
+ - **Security**: CORS enabled, request validation, 60s timeout
12
+ - **Performance**: Gzip compression, memory cleanup
13
+ - **Monitoring**: `/health` and `/stats` endpoints
14
+ - **Reliability**: Graceful shutdown, error handling
15
+
16
+ ## What This Does
17
+
18
+ - Holds your OpenRouter API key (never exposed to users)
19
+ - Forwards requests to OpenRouter's free models
20
+ - Allows vmCode users to use the app without configuring API keys
21
+ - Prevents abuse with IP-based rate limiting
22
+
23
+ ## Local Development
24
+
25
+ ```bash
26
+ # Install dependencies
27
+ npm install
28
+
29
+ # Set up environment variables
30
+ cp .env.example .env
31
+ # Edit .env and add your OpenRouter API key
32
+
33
+ # Run locally
34
+ npm start
35
+ ```
36
+
37
+ The server will run on `http://localhost:3000`
38
+
39
+ ## Deploy to Vercel
40
+
41
+ ### 1. Install Vercel CLI
42
+
43
+ ```bash
44
+ npm install -g vercel
45
+ ```
46
+
47
+ ### 2. Deploy
48
+
49
+ ```bash
50
+ # From the vmcode-proxy directory
51
+ vercel login
52
+ vercel --prod
53
+ ```
54
+
55
+ ### 3. Set Environment Variable
56
+
57
+ ```bash
58
+ vercel env add OPENROUTER_API_KEY production
59
+ # Paste your OpenRouter API key when prompted
60
+ ```
61
+
62
+ ### 4. Get Your URL
63
+
64
+ After deployment, Vercel will give you a URL like:
65
+ `https://vmcode-proxy.vercel.app`
66
+
67
+ ## Update vmCode Config
68
+
69
+ After deploying, update your vmCode config to point to your deployed proxy:
70
+
71
+ In `config.yaml`:
72
+ ```yaml
73
+ VMCODE_FREE_API_BASE: "https://your-deployed-url.vercel.app"
74
+ ```
75
+
76
+ Or update the default in `src/llm/config.py`:
77
+ ```python
78
+ "api_base": _CONFIG.get("VMCODE_FREE_API_BASE", "https://your-deployed-url.vercel.app"),
79
+ ```
80
+
81
+ ## Testing
82
+
83
+ ### Test the Proxy Directly
84
+
85
+ ```bash
86
+ curl -X POST https://your-deployed-url.vercel.app/chat \
87
+ -H "Content-Type: application/json" \
88
+ -d '{
89
+ "messages": [
90
+ {"role": "user", "content": "Hello!"}
91
+ ]
92
+ }'
93
+ ```
94
+
95
+ ### Test Health Endpoint
96
+
97
+ ```bash
98
+ curl https://your-deployed-url.vercel.app/health
99
+ ```
100
+
101
+ ## Rate Limiting
102
+
103
+ The proxy enforces multiple rate limits to prevent abuse:
104
+
105
+ | Limit | Window | Scope |
106
+ |-------|--------|-------|
107
+ | 100 requests | 15 minutes | Per IP |
108
+ | 500 requests | 24 hours | Per IP (free tier) |
109
+ | 10,000 requests | 24 hours | Global (all IPs) |
110
+
111
+ When limits are exceeded, the proxy returns HTTP 429 with reset time.
112
+
113
+ ## Monitoring
114
+
115
+ ### Health Endpoint
116
+
117
+ ```bash
118
+ curl https://your-deployed-url.vercel.app/health
119
+ ```
120
+
121
+ Returns:
122
+ ```json
123
+ {
124
+ "status": "ok",
125
+ "service": "vmcode-proxy",
126
+ "todayRequests": 1234,
127
+ "dailyLimit": 10000,
128
+ "percentUsed": 12,
129
+ "ipUsage": {
130
+ "requests": 50,
131
+ "limit": 500,
132
+ "percentUsed": 10
133
+ },
134
+ "activeIps": 42
135
+ }
136
+ ```
137
+
138
+ ### Stats Endpoint
139
+
140
+ ```bash
141
+ curl https://your-deployed-url.vercel.app/stats
142
+ ```
143
+
144
+ Returns detailed usage statistics including per-IP breakdown.
145
+
146
+ ### OpenRouter Dashboard
147
+
148
+ Check OpenRouter dashboard for:
149
+ - Usage statistics
150
+ - Cost tracking
151
+ - Which free models are being used
152
+
153
+ Set up billing alerts to control costs:
154
+ - $10, $50, $100 alerts recommended
155
+
156
+ ## Costs
157
+
158
+ - **Vercel:** Free tier (up to 100GB bandwidth/month)
159
+ - **OpenRouter:** Pay for what you use
160
+ - Free models: ~$0-0.10 per 1M tokens
161
+ - Estimated: $0-10/month for first 1000 users
162
+
163
+ ## Troubleshooting
164
+
165
+ ### "OPENROUTER_API_KEY not configured"
166
+
167
+ Make sure you set the environment variable in Vercel:
168
+ ```bash
169
+ vercel env ls
170
+ ```
171
+
172
+ ### 500 errors from proxy
173
+
174
+ Check Vercel logs:
175
+ ```bash
176
+ vercel logs
177
+ ```
178
+
179
+ ### 429 Rate limit exceeded
180
+
181
+ The client has exceeded one of the rate limits:
182
+ - Wait until the reset time
183
+ - Reduce request frequency
184
+ - Contact admin if limits need adjustment
185
+
186
+ ### 504 Request timeout
187
+
188
+ The upstream OpenRouter API took too long (>60s). This is rare but can happen during high load.
189
+
190
+ ### Slow responses
191
+
192
+ OpenRouter's `z-ai/glm-4.5-air:free` model may take longer than direct API calls. This is normal.
193
+
194
+ ## Architecture
195
+
196
+ ```
197
+ User → vmcode-proxy → OpenRouter /free models
198
+
199
+ Rate Limiting
200
+ Request Validation
201
+ Error Handling
202
+ Memory Cleanup
203
+ ```
204
+
205
+ **Memory Management:**
206
+ - IP usage entries are automatically cleaned up after 2 days
207
+ - Cleanup runs every hour to prevent memory leaks
208
+ - Active IPs count shown in health endpoint
209
+
210
+ **Graceful Shutdown:**
211
+ - On SIGTERM/SIGINT, server waits 10s for in-flight requests
212
+ - Force shutdown after second signal
213
+
214
+ ## Security
215
+
216
+ - **API Keys**: Stored in environment variables, never in code
217
+ - **CORS**: Enabled for cross-origin requests
218
+ - **Request Validation**: Validates message array structure
219
+ - **Timeout**: 60s timeout prevents hanging requests
220
+ - **Rate Limiting**: Prevents abuse and DDoS
221
+
222
+ ## Scaling
223
+
224
+ **Current Setup (Single Server):**
225
+ - In-memory rate limiting
226
+ - Suitable for up to ~10,000 daily users
227
+
228
+ **For Larger Scale:**
229
+ - Add Redis for distributed rate limiting
230
+ - Use a load balancer with multiple instances
231
+ - Add authentication for per-user limits
232
+
233
+ ## License
234
+
235
+ Same as vmCode-CLI project.