@bniladridas/cursor 0.1.7
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/.clang-tidy +28 -0
- package/.dockerignore +56 -0
- package/.env.example +29 -0
- package/.github/CODEOWNERS +2 -0
- package/.github/ISSUE_TEMPLATE/blank.md +27 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +33 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +24 -0
- package/.github/SECURITY.md +24 -0
- package/.github/codeql/codeql-config.yml +8 -0
- package/.github/dependabot.yml +14 -0
- package/.github/labeler.yml +50 -0
- package/.github/packaging/brand-cursor.png +0 -0
- package/.github/packaging/database/init.sql +48 -0
- package/.github/packaging/docker/Dockerfile +111 -0
- package/.github/packaging/docker/docker-compose.yml +56 -0
- package/.github/packaging/scripts/preflight.sh +413 -0
- package/.github/packaging/scripts/prepare-release.sh +141 -0
- package/.github/packaging/scripts/release.sh +22 -0
- package/.github/packaging/scripts/setup-git-hooks.sh +73 -0
- package/.github/pull_request_template.md +31 -0
- package/.github/signed.json +9 -0
- package/.github/workflows/README.md +23 -0
- package/.github/workflows/ci.yml +181 -0
- package/.github/workflows/cla.yml +33 -0
- package/.github/workflows/formula-sha.yml +63 -0
- package/.github/workflows/issue-response.yml +44 -0
- package/.github/workflows/labeler.yml +42 -0
- package/.github/workflows/pr-body.yml +49 -0
- package/.github/workflows/release.yml +176 -0
- package/.github/workflows/security.yml +94 -0
- package/.github/workflows/stale.yml +38 -0
- package/AGENTS.md +49 -0
- package/CHANGELOG.md +3 -0
- package/CMakeLists.txt +646 -0
- package/Formula/cursor.rb +46 -0
- package/LICENSE +201 -0
- package/Makefile +28 -0
- package/README.md +46 -0
- package/cli.js +16 -0
- package/include/agent.h +86 -0
- package/include/agent_mode.h +17 -0
- package/include/memory_manager.h +102 -0
- package/include/services/ai_service.h +31 -0
- package/include/services/auth_service.h +87 -0
- package/include/services/checkpoint_service.h +69 -0
- package/include/services/codebase_service.h +38 -0
- package/include/services/command_service.h +23 -0
- package/include/services/context_service.h +74 -0
- package/include/services/database_service.h +56 -0
- package/include/services/error_service.h +106 -0
- package/include/services/file_service.h +51 -0
- package/include/services/git_service.h +29 -0
- package/include/services/github_service.h +85 -0
- package/include/services/mcp_service.h +85 -0
- package/include/services/multi_file_service.h +93 -0
- package/include/services/sandbox_service.h +96 -0
- package/include/services/theme_service.h +67 -0
- package/include/services/web_service.h +52 -0
- package/include/utils/config.h +68 -0
- package/include/utils/memory_utils.h +79 -0
- package/include/utils/platform.h +56 -0
- package/include/utils/ui.h +43 -0
- package/include/utils/validation.h +63 -0
- package/include/utils/version.h.in +17 -0
- package/install.js +49 -0
- package/package.json +16 -0
- package/release/checksums.txt +3 -0
- package/release/cursor-linux/cursor_v0.1.7_linux_amd64.tar.gz +0 -0
- package/release/cursor-macos/cursor_v0.1.7_darwin_arm64.tar.gz +0 -0
- package/release/cursor-windows/cursor__windows_amd64.zip +0 -0
- package/src/agent.cpp +2026 -0
- package/src/main.cpp +97 -0
- package/src/memory_manager.cpp +814 -0
- package/src/services/ai_service.cpp +366 -0
- package/src/services/auth_service.cpp +779 -0
- package/src/services/checkpoint_service.cpp +465 -0
- package/src/services/codebase_service.cpp +233 -0
- package/src/services/command_service.cpp +82 -0
- package/src/services/context_service.cpp +348 -0
- package/src/services/database_service.cpp +148 -0
- package/src/services/error_service.cpp +438 -0
- package/src/services/file_service.cpp +349 -0
- package/src/services/git_service.cpp +148 -0
- package/src/services/github_service.cpp +435 -0
- package/src/services/mcp_service.cpp +481 -0
- package/src/services/multi_file_service.cpp +591 -0
- package/src/services/sandbox_service.cpp +678 -0
- package/src/services/theme_service.cpp +429 -0
- package/src/services/web_service.cpp +532 -0
- package/src/utils/config.cpp +77 -0
- package/src/utils/memory_utils.cpp +93 -0
- package/src/utils/ui.cpp +307 -0
- package/src/utils/validation.cpp +306 -0
- package/src/utils/version.cpp +175 -0
- package/tests/e2e/docker-compose.yml +195 -0
- package/tests/e2e/run_e2e_tests.sh +70 -0
- package/tests/e2e/run_tests_in_docker.sh +115 -0
- package/tests/main_test.cpp +16 -0
- package/tests/mocks/mock_ollama.py +98 -0
- package/tests/mocks/start_nginx.sh +64 -0
package/src/utils/ui.cpp
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
#include "utils/ui.h"
|
|
2
|
+
#include <array>
|
|
3
|
+
#include <chrono>
|
|
4
|
+
#include <iostream>
|
|
5
|
+
#include <string>
|
|
6
|
+
#include <thread>
|
|
7
|
+
|
|
8
|
+
namespace Utils {
|
|
9
|
+
|
|
10
|
+
// ===== Color Codes =====
|
|
11
|
+
namespace Color {
|
|
12
|
+
// Exported color constants
|
|
13
|
+
CURSOR_API const std::string RESET = "\033[0m";
|
|
14
|
+
CURSOR_API const std::string GREEN = "\033[32m";
|
|
15
|
+
CURSOR_API const std::string YELLOW = "\033[33m";
|
|
16
|
+
CURSOR_API const std::string RED = "\033[31m";
|
|
17
|
+
CURSOR_API const std::string CYAN = "\033[36m";
|
|
18
|
+
CURSOR_API const std::string BOLD = "\033[1m";
|
|
19
|
+
CURSOR_API const std::string DIM = "\033[2m";
|
|
20
|
+
CURSOR_API const std::string PINK = "\033[38;2;255;105;180m";
|
|
21
|
+
} // namespace Color
|
|
22
|
+
|
|
23
|
+
// ===== Logo =====
|
|
24
|
+
void UI::print_logo() {
|
|
25
|
+
std::cout << Color::PINK
|
|
26
|
+
<< " ▄▄▄▄▄▄▄▄▄▄▄\n"
|
|
27
|
+
<< " █ █\n"
|
|
28
|
+
<< " █ ▄▄▄▄▄▄ █\n"
|
|
29
|
+
<< " █ █ █ █\n"
|
|
30
|
+
<< " █ █ ██ █ █\n"
|
|
31
|
+
<< " █ █ █ █ █ █\n"
|
|
32
|
+
<< " █ █ █ █ █ █\n"
|
|
33
|
+
<< " █ █ ██ █ █\n"
|
|
34
|
+
<< " █ █ █ █\n"
|
|
35
|
+
<< " █ ▀▀▀▀▀▀ █\n"
|
|
36
|
+
<< " █ █\n"
|
|
37
|
+
<< " ▀▀▀▀▀▀▀▀▀\n"
|
|
38
|
+
<< Color::RESET
|
|
39
|
+
<< Color::BOLD << " CURSOR\n"
|
|
40
|
+
<< Color::RESET;
|
|
41
|
+
|
|
42
|
+
std::cout << Color::DIM << "Welcome to Cursor - Choose your mode below.\n"
|
|
43
|
+
<< Color::RESET;
|
|
44
|
+
|
|
45
|
+
print_divider();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ===== Help Menu =====
|
|
49
|
+
void UI::print_help() {
|
|
50
|
+
|
|
51
|
+
std::cout << Color::BOLD << "Commands\n" << Color::RESET;
|
|
52
|
+
print_divider();
|
|
53
|
+
|
|
54
|
+
// File Operations
|
|
55
|
+
std::cout << Color::GREEN << "Files\n" << Color::RESET;
|
|
56
|
+
std::cout << Color::CYAN << " @file <path>" << Color::RESET
|
|
57
|
+
<< " - Inject file\n";
|
|
58
|
+
std::cout << Color::CYAN << " @directory <path>" << Color::RESET
|
|
59
|
+
<< " - Inject directory\n";
|
|
60
|
+
std::cout << Color::CYAN << " read:<file>" << Color::RESET
|
|
61
|
+
<< " - Read file\n";
|
|
62
|
+
std::cout << Color::CYAN << " read:<file>:<start>:<count>" << Color::RESET
|
|
63
|
+
<< " - Read range\n";
|
|
64
|
+
std::cout << Color::CYAN << " write:<file> <content>" << Color::RESET
|
|
65
|
+
<< " - Write file\n";
|
|
66
|
+
std::cout << Color::CYAN << " replace:<file>:<old>:<new>" << Color::RESET
|
|
67
|
+
<< " - Replace text\n";
|
|
68
|
+
std::cout << Color::CYAN << " grep:<pattern>[:dir[:filter]]" << Color::RESET
|
|
69
|
+
<< " - Search text\n";
|
|
70
|
+
|
|
71
|
+
// Sessions
|
|
72
|
+
std::cout << "\n" << Color::GREEN << "Sessions\n" << Color::RESET;
|
|
73
|
+
std::cout << Color::CYAN << " /save <name> [tags]" << Color::RESET
|
|
74
|
+
<< " - Save session\n";
|
|
75
|
+
std::cout << Color::CYAN << " /resume <name>" << Color::RESET
|
|
76
|
+
<< " - Resume session\n";
|
|
77
|
+
std::cout << Color::CYAN << " /sessions" << Color::RESET
|
|
78
|
+
<< " - List sessions\n";
|
|
79
|
+
std::cout << Color::CYAN << " /compress" << Color::RESET
|
|
80
|
+
<< " - Compress context\n";
|
|
81
|
+
std::cout << Color::CYAN << " remember:<fact>" << Color::RESET
|
|
82
|
+
<< " - Remember fact\n";
|
|
83
|
+
std::cout << Color::CYAN << " memory" << Color::RESET
|
|
84
|
+
<< " - Show memory\n";
|
|
85
|
+
std::cout << Color::CYAN << " clear" << Color::RESET
|
|
86
|
+
<< " - Clear session\n";
|
|
87
|
+
std::cout << Color::CYAN << " forget" << Color::RESET
|
|
88
|
+
<< " - Clear memory\n";
|
|
89
|
+
|
|
90
|
+
// Web
|
|
91
|
+
std::cout << "\n" << Color::GREEN << "Web\n" << Color::RESET;
|
|
92
|
+
std::cout << Color::CYAN << " /fetch <url> [format]" << Color::RESET
|
|
93
|
+
<< " - Fetch content\n";
|
|
94
|
+
std::cout << Color::CYAN << " search:<query>" << Color::RESET
|
|
95
|
+
<< " - Search web\n";
|
|
96
|
+
std::cout << Color::CYAN << " /mcp servers" << Color::RESET
|
|
97
|
+
<< " - List servers\n";
|
|
98
|
+
std::cout << Color::CYAN << " /mcp resources <server>" << Color::RESET
|
|
99
|
+
<< " - List resources\n";
|
|
100
|
+
std::cout << Color::CYAN << " /mcp tools <server>" << Color::RESET
|
|
101
|
+
<< " - List tools\n";
|
|
102
|
+
|
|
103
|
+
// Checkpoints
|
|
104
|
+
std::cout << "\n" << Color::GREEN << "Checkpoints\n" << Color::RESET;
|
|
105
|
+
std::cout << Color::CYAN << " /checkpoint <name>" << Color::RESET
|
|
106
|
+
<< " - Create checkpoint\n";
|
|
107
|
+
std::cout << Color::CYAN << " /restore <name>" << Color::RESET
|
|
108
|
+
<< " - Restore checkpoint\n";
|
|
109
|
+
std::cout << Color::CYAN << " /checkpoints" << Color::RESET
|
|
110
|
+
<< " - List checkpoints\n";
|
|
111
|
+
|
|
112
|
+
// Themes
|
|
113
|
+
std::cout << "\n" << Color::GREEN << "Themes\n" << Color::RESET;
|
|
114
|
+
std::cout << Color::CYAN << " /theme list" << Color::RESET
|
|
115
|
+
<< " - List themes\n";
|
|
116
|
+
std::cout << Color::CYAN << " /theme set <name>" << Color::RESET
|
|
117
|
+
<< " - Set theme\n";
|
|
118
|
+
std::cout << Color::CYAN << " /theme preview <name>" << Color::RESET
|
|
119
|
+
<< " - Preview theme\n";
|
|
120
|
+
|
|
121
|
+
// Security
|
|
122
|
+
std::cout << "\n" << Color::GREEN << "Security\n" << Color::RESET;
|
|
123
|
+
std::cout << Color::CYAN << " /auth providers" << Color::RESET
|
|
124
|
+
<< " - List providers\n";
|
|
125
|
+
std::cout << Color::CYAN << " /auth set <provider>" << Color::RESET
|
|
126
|
+
<< " - Set provider\n";
|
|
127
|
+
std::cout << Color::CYAN << " /auth key <provider> <key>" << Color::RESET
|
|
128
|
+
<< " - Set key\n";
|
|
129
|
+
std::cout << Color::CYAN << " /sandbox run <command>" << Color::RESET
|
|
130
|
+
<< " - Run sandboxed\n";
|
|
131
|
+
std::cout << Color::CYAN << " /sandbox status" << Color::RESET
|
|
132
|
+
<< " - Check status\n";
|
|
133
|
+
|
|
134
|
+
// Errors
|
|
135
|
+
std::cout << "\n" << Color::GREEN << "Errors\n" << Color::RESET;
|
|
136
|
+
std::cout << Color::CYAN << " /error report" << Color::RESET
|
|
137
|
+
<< " - View errors\n";
|
|
138
|
+
std::cout << Color::CYAN << " /error recent <count>" << Color::RESET
|
|
139
|
+
<< " - Show recent\n";
|
|
140
|
+
std::cout << Color::CYAN << " /error export <file>" << Color::RESET
|
|
141
|
+
<< " - Export log\n";
|
|
142
|
+
|
|
143
|
+
// System
|
|
144
|
+
std::cout << "\n" << Color::GREEN << "System\n" << Color::RESET;
|
|
145
|
+
std::cout << Color::CYAN << " cmd:<command>" << Color::RESET
|
|
146
|
+
<< " - Execute shell command\n";
|
|
147
|
+
std::cout << Color::CYAN << " /update" << Color::RESET
|
|
148
|
+
<< " - Check for updates\n";
|
|
149
|
+
std::cout << Color::CYAN << " /tools" << Color::RESET
|
|
150
|
+
<< " - Show tools\n";
|
|
151
|
+
std::cout << Color::CYAN << " !" << Color::RESET
|
|
152
|
+
<< " - Toggle shell\n";
|
|
153
|
+
std::cout << Color::CYAN << " help" << Color::RESET
|
|
154
|
+
<< " - Show help\n";
|
|
155
|
+
std::cout << Color::CYAN << " quit" << Color::RESET
|
|
156
|
+
<< " - Exit\n";
|
|
157
|
+
|
|
158
|
+
std::cout << "\n" << Color::BOLD << "Notes\n" << Color::RESET;
|
|
159
|
+
std::cout << Color::DIM << "Set theme with /theme set dark\n" << Color::RESET;
|
|
160
|
+
std::cout << Color::DIM << "Configure auth with /auth key\n" << Color::RESET;
|
|
161
|
+
std::cout << Color::DIM << "Create checkpoints before changes\n"
|
|
162
|
+
<< Color::RESET;
|
|
163
|
+
std::cout << Color::DIM << "Use /sandbox for safe execution\n"
|
|
164
|
+
<< Color::RESET;
|
|
165
|
+
print_divider();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ===== Status =====
|
|
169
|
+
void UI::print_enterprise_status() {
|
|
170
|
+
std::cout << Color::BOLD << "Status\n" << Color::RESET;
|
|
171
|
+
print_divider();
|
|
172
|
+
|
|
173
|
+
std::cout << Color::GREEN << "Files (4/4)\n" << Color::RESET;
|
|
174
|
+
std::cout << Color::DIM << " File injection\n" << Color::RESET;
|
|
175
|
+
std::cout << Color::DIM << " Multi-file operations\n" << Color::RESET;
|
|
176
|
+
std::cout << Color::DIM << " Context hierarchy\n" << Color::RESET;
|
|
177
|
+
std::cout << Color::DIM << " Shell integration\n" << Color::RESET;
|
|
178
|
+
|
|
179
|
+
std::cout << "\n" << Color::GREEN << "Sessions (4/4)\n" << Color::RESET;
|
|
180
|
+
std::cout << Color::DIM << " Session management\n" << Color::RESET;
|
|
181
|
+
std::cout << Color::DIM << " Tool registry\n" << Color::RESET;
|
|
182
|
+
std::cout << Color::DIM << " Configuration\n" << Color::RESET;
|
|
183
|
+
std::cout << Color::DIM << " Context compression\n" << Color::RESET;
|
|
184
|
+
|
|
185
|
+
std::cout << "\n" << Color::GREEN << "Extensions (4/4)\n" << Color::RESET;
|
|
186
|
+
std::cout << Color::DIM << " MCP servers\n" << Color::RESET;
|
|
187
|
+
std::cout << Color::DIM << " Checkpoints\n" << Color::RESET;
|
|
188
|
+
std::cout << Color::DIM << " Web fetch\n" << Color::RESET;
|
|
189
|
+
std::cout << Color::DIM << " File filtering\n" << Color::RESET;
|
|
190
|
+
|
|
191
|
+
std::cout << "\n" << Color::GREEN << "Security (4/4)\n" << Color::RESET;
|
|
192
|
+
std::cout << Color::DIM << " Themes\n" << Color::RESET;
|
|
193
|
+
std::cout << Color::DIM << " Authentication\n" << Color::RESET;
|
|
194
|
+
std::cout << Color::DIM << " Sandboxing\n" << Color::RESET;
|
|
195
|
+
std::cout << Color::DIM << " Error handling\n" << Color::RESET;
|
|
196
|
+
|
|
197
|
+
std::cout << "\n"
|
|
198
|
+
<< Color::BOLD << Color::GREEN << "16 features active\n"
|
|
199
|
+
<< Color::RESET;
|
|
200
|
+
print_divider();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ===== Spinner =====
|
|
204
|
+
void UI::spinner(const std::string &message, int duration_ms) {
|
|
205
|
+
const std::array<std::string_view, 4> frames = {"/", "-", "\\", "|"};
|
|
206
|
+
const size_t num_frames = frames.size();
|
|
207
|
+
size_t frame = 0;
|
|
208
|
+
auto start = std::chrono::steady_clock::now();
|
|
209
|
+
|
|
210
|
+
std::cout << Color::CYAN << message << " " << Color::RESET;
|
|
211
|
+
while (std::chrono::steady_clock::now() - start <
|
|
212
|
+
std::chrono::milliseconds(duration_ms)) {
|
|
213
|
+
std::cout << "\b" << frames[frame] << std::flush;
|
|
214
|
+
frame = (frame + 1) % num_frames;
|
|
215
|
+
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
216
|
+
}
|
|
217
|
+
std::cout << "\b" << " " << "\n";
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ===== Threaded Spinner =====
|
|
221
|
+
void UI::spinner(std::atomic<bool> &done) {
|
|
222
|
+
const char frames[] = {'|', '/', '-', '\\'};
|
|
223
|
+
int frame = 0;
|
|
224
|
+
|
|
225
|
+
while (!done) {
|
|
226
|
+
std::cout << "\r" << frames[frame % 4] << std::flush;
|
|
227
|
+
frame++;
|
|
228
|
+
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
229
|
+
}
|
|
230
|
+
std::cout << "\r \r" << std::flush;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// ===== Status Messages =====
|
|
234
|
+
void UI::print_success(const std::string &message) {
|
|
235
|
+
std::cout << Color::GREEN << "[OK] " << Color::RESET << message << "\n";
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
void UI::print_error(const std::string &message) {
|
|
239
|
+
std::cout << Color::RED << "[ERROR] " << Color::RESET << message << "\n";
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
void UI::print_warning(const std::string &message) {
|
|
243
|
+
std::cout << Color::YELLOW << "[WARN] " << Color::RESET << message << "\n";
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
void UI::print_info(const std::string &message) {
|
|
247
|
+
std::cout << Color::CYAN << "[INFO] " << Color::RESET << message << "\n";
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ===== Divider =====
|
|
251
|
+
void UI::print_divider() {
|
|
252
|
+
std::cout << Color::DIM << "----------------------------------------\n"
|
|
253
|
+
<< Color::RESET;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// ===== Quick Help Interface =====
|
|
257
|
+
void UI::print_quick_help() {
|
|
258
|
+
std::cout << Color::DIM << "Quick Commands: " << Color::RESET;
|
|
259
|
+
std::cout << Color::CYAN << "help" << Color::RESET << " | ";
|
|
260
|
+
|
|
261
|
+
std::cout << Color::CYAN << "search:query" << Color::RESET << " | ";
|
|
262
|
+
std::cout << Color::CYAN << "cmd:command" << Color::RESET << " | ";
|
|
263
|
+
std::cout << Color::CYAN << "read:file" << Color::RESET << " | ";
|
|
264
|
+
std::cout << Color::CYAN << "quit" << Color::RESET << "\n";
|
|
265
|
+
|
|
266
|
+
std::cout << Color::CYAN << "version" << Color::RESET << " | ";
|
|
267
|
+
std::cout << Color::CYAN << "update" << Color::RESET << " | ";
|
|
268
|
+
std::cout << Color::CYAN << "search:query" << Color::RESET << " | ";
|
|
269
|
+
std::cout << Color::CYAN << "cmd:command" << Color::RESET << " | ";
|
|
270
|
+
std::cout << Color::CYAN << "read:file" << Color::RESET << " | ";
|
|
271
|
+
std::cout << Color::CYAN << "write:file text" << Color::RESET << " | ";
|
|
272
|
+
std::cout << Color::CYAN << "exit" << Color::RESET << "\n";
|
|
273
|
+
|
|
274
|
+
print_divider();
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// ===== System Info =====
|
|
278
|
+
void UI::print_system_info(const std::string &mode, const std::string &model) {
|
|
279
|
+
std::cout << Color::DIM << "System: " << Color::RESET;
|
|
280
|
+
std::cout << Color::GREEN << "Mode=" << mode << Color::RESET << " | ";
|
|
281
|
+
std::cout << Color::GREEN << "Model=" << model << Color::RESET << " | ";
|
|
282
|
+
std::cout << Color::GREEN << "Memory=Active" << Color::RESET << " | ";
|
|
283
|
+
std::cout << Color::GREEN << "Commands=Available" << Color::RESET << "\n";
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// ===== Ready Interface =====
|
|
287
|
+
void UI::print_ready_interface(const std::string &mode,
|
|
288
|
+
const std::string &model) {
|
|
289
|
+
print_system_info(mode, model);
|
|
290
|
+
print_quick_help();
|
|
291
|
+
|
|
292
|
+
std::cout << "Ready - Type a command:\n";
|
|
293
|
+
|
|
294
|
+
std::cout << "Ready - Type a command or chat naturally:\n";
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// ===== Prompted Input =====
|
|
298
|
+
std::string UI::prompt_user(const std::string &prompt_text) {
|
|
299
|
+
// Only print the prompt once with consistent formatting
|
|
300
|
+
std::cout << Color::BOLD << "[You] " << prompt_text << Color::RESET << " > ";
|
|
301
|
+
|
|
302
|
+
std::string input;
|
|
303
|
+
std::getline(std::cin, input);
|
|
304
|
+
return input;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
} // namespace Utils
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
#include "utils/validation.h"
|
|
2
|
+
#include "services/file_service.h"
|
|
3
|
+
#include <algorithm>
|
|
4
|
+
#include <cctype>
|
|
5
|
+
#include <filesystem>
|
|
6
|
+
#include <regex>
|
|
7
|
+
|
|
8
|
+
namespace Utils {
|
|
9
|
+
|
|
10
|
+
ValidationResult
|
|
11
|
+
Validator::validate_file_path(const std::string &path,
|
|
12
|
+
const std::string &base_directory) {
|
|
13
|
+
ValidationResult result = {true, "", {}};
|
|
14
|
+
|
|
15
|
+
if (path.empty()) {
|
|
16
|
+
result.is_valid = false;
|
|
17
|
+
result.error_message = "File path cannot be empty";
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Check for dangerous path patterns
|
|
22
|
+
if (path.find("..") != std::string::npos) {
|
|
23
|
+
result.warnings.push_back("Path contains '..' which may be unsafe");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Validate against base directory if provided
|
|
27
|
+
if (!base_directory.empty()) {
|
|
28
|
+
if (!Services::FileService::is_within_directory(path, base_directory)) {
|
|
29
|
+
result.is_valid = false;
|
|
30
|
+
result.error_message = "Path is outside the allowed base directory";
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Check path length
|
|
36
|
+
if (path.length() > 260) { // Windows MAX_PATH limit
|
|
37
|
+
result.warnings.push_back(
|
|
38
|
+
"Path is very long and may cause issues on some systems");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
ValidationResult Validator::validate_file_exists(const std::string &path) {
|
|
45
|
+
ValidationResult result = {true, "", {}};
|
|
46
|
+
|
|
47
|
+
if (!Services::FileService::file_exists(path)) {
|
|
48
|
+
result.is_valid = false;
|
|
49
|
+
result.error_message = "File does not exist: " + path;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
ValidationResult Validator::validate_file_writable(const std::string &path) {
|
|
56
|
+
ValidationResult result = {true, "", {}};
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
std::filesystem::path file_path(path);
|
|
60
|
+
std::filesystem::path parent_path = file_path.parent_path();
|
|
61
|
+
|
|
62
|
+
// Check if parent directory exists and is writable
|
|
63
|
+
if (!parent_path.empty() && !std::filesystem::exists(parent_path)) {
|
|
64
|
+
// Try to create parent directories
|
|
65
|
+
try {
|
|
66
|
+
std::filesystem::create_directories(parent_path);
|
|
67
|
+
} catch (const std::exception &e) {
|
|
68
|
+
result.is_valid = false;
|
|
69
|
+
result.error_message =
|
|
70
|
+
"Cannot create parent directory: " + std::string(e.what());
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Check if file exists and is writable
|
|
76
|
+
if (std::filesystem::exists(path)) {
|
|
77
|
+
auto perms = std::filesystem::status(path).permissions();
|
|
78
|
+
if ((perms & std::filesystem::perms::owner_write) ==
|
|
79
|
+
std::filesystem::perms::none) {
|
|
80
|
+
result.warnings.push_back("File may not be writable");
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
} catch (const std::exception &e) {
|
|
85
|
+
result.is_valid = false;
|
|
86
|
+
result.error_message =
|
|
87
|
+
"Error checking file writability: " + std::string(e.what());
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
ValidationResult Validator::validate_non_empty(const std::string &text,
|
|
94
|
+
const std::string &field_name) {
|
|
95
|
+
ValidationResult result = {true, "", {}};
|
|
96
|
+
|
|
97
|
+
if (text.empty()) {
|
|
98
|
+
result.is_valid = false;
|
|
99
|
+
result.error_message = field_name + " cannot be empty";
|
|
100
|
+
} else if (text.find_first_not_of(" \t\n\r") == std::string::npos) {
|
|
101
|
+
result.is_valid = false;
|
|
102
|
+
result.error_message = field_name + " cannot be only whitespace";
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
ValidationResult Validator::validate_regex_pattern(const std::string &pattern) {
|
|
109
|
+
ValidationResult result = {true, "", {}};
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
std::regex test_regex(pattern);
|
|
113
|
+
(void)test_regex; // Suppress unused variable warning - we only care about
|
|
114
|
+
// construction validity
|
|
115
|
+
} catch (const std::regex_error &e) {
|
|
116
|
+
result.is_valid = false;
|
|
117
|
+
result.error_message =
|
|
118
|
+
"Invalid regular expression: " + std::string(e.what());
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
ValidationResult Validator::validate_command_safe(const std::string &command) {
|
|
125
|
+
ValidationResult result = {true, "", {}};
|
|
126
|
+
|
|
127
|
+
// List of potentially dangerous commands
|
|
128
|
+
static const std::vector<std::string> dangerous_commands = {
|
|
129
|
+
"rm", "del", "format", "fdisk", "mkfs", "dd", "shutdown",
|
|
130
|
+
"reboot", "halt", "poweroff", "init", "kill", "killall", "pkill"};
|
|
131
|
+
|
|
132
|
+
std::string lower_command = command;
|
|
133
|
+
std::transform(lower_command.begin(), lower_command.end(),
|
|
134
|
+
lower_command.begin(), ::tolower);
|
|
135
|
+
|
|
136
|
+
for (const auto &dangerous : dangerous_commands) {
|
|
137
|
+
if (lower_command.find(dangerous) != std::string::npos) {
|
|
138
|
+
result.warnings.push_back(
|
|
139
|
+
"Command contains potentially dangerous operation: " + dangerous);
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Check for shell injection patterns
|
|
145
|
+
if (command.find(";") != std::string::npos ||
|
|
146
|
+
command.find("&&") != std::string::npos ||
|
|
147
|
+
command.find("||") != std::string::npos ||
|
|
148
|
+
command.find("|") != std::string::npos) {
|
|
149
|
+
result.warnings.push_back(
|
|
150
|
+
"Command contains shell operators that may be unsafe");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return result;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
ValidationResult Validator::validate_search_query(const std::string &query) {
|
|
157
|
+
ValidationResult result = validate_non_empty(query, "Search query");
|
|
158
|
+
|
|
159
|
+
if (result.is_valid) {
|
|
160
|
+
if (query.length() > 1000) {
|
|
161
|
+
result.warnings.push_back("Search query is very long");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Check for potentially problematic characters
|
|
165
|
+
if (query.find_first_of("<>\"'&") != std::string::npos) {
|
|
166
|
+
result.warnings.push_back(
|
|
167
|
+
"Query contains special characters that may affect search results");
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return result;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
ValidationResult Validator::validate_line_range(int start_line, int line_count,
|
|
175
|
+
int total_lines) {
|
|
176
|
+
ValidationResult result = {true, "", {}};
|
|
177
|
+
|
|
178
|
+
if (start_line < 0) {
|
|
179
|
+
result.is_valid = false;
|
|
180
|
+
result.error_message = "Start line cannot be negative";
|
|
181
|
+
return result;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (line_count < -1) {
|
|
185
|
+
result.is_valid = false;
|
|
186
|
+
result.error_message = "Line count must be -1 (all) or positive";
|
|
187
|
+
return result;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (total_lines >= 0) {
|
|
191
|
+
if (start_line >= total_lines) {
|
|
192
|
+
result.is_valid = false;
|
|
193
|
+
result.error_message = "Start line is beyond end of file";
|
|
194
|
+
return result;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (line_count > 0 && start_line + line_count > total_lines) {
|
|
198
|
+
result.warnings.push_back("Requested range extends beyond end of file");
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return result;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
ValidationResult Validator::validate_replacement_count(int expected,
|
|
206
|
+
int actual) {
|
|
207
|
+
ValidationResult result = {true, "", {}};
|
|
208
|
+
|
|
209
|
+
if (expected > 0 && expected != actual) {
|
|
210
|
+
result.is_valid = false;
|
|
211
|
+
result.error_message = "Expected " + std::to_string(expected) +
|
|
212
|
+
" replacements but found " + std::to_string(actual);
|
|
213
|
+
} else if (actual == 0) {
|
|
214
|
+
result.is_valid = false;
|
|
215
|
+
result.error_message = "No matches found for replacement";
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return result;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
ValidationResult
|
|
222
|
+
Validator::combine_results(const std::vector<ValidationResult> &results) {
|
|
223
|
+
ValidationResult combined = {true, "", {}};
|
|
224
|
+
|
|
225
|
+
for (const auto &result : results) {
|
|
226
|
+
if (!result.is_valid) {
|
|
227
|
+
combined.is_valid = false;
|
|
228
|
+
if (combined.error_message.empty()) {
|
|
229
|
+
combined.error_message = result.error_message;
|
|
230
|
+
} else {
|
|
231
|
+
combined.error_message += "; " + result.error_message;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Combine warnings
|
|
236
|
+
combined.warnings.insert(combined.warnings.end(), result.warnings.begin(),
|
|
237
|
+
result.warnings.end());
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return combined;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
bool Validator::is_safe_path(const std::string &path) {
|
|
244
|
+
// Check for path traversal attempts
|
|
245
|
+
if (path.find("..") != std::string::npos)
|
|
246
|
+
return false;
|
|
247
|
+
|
|
248
|
+
// Check for absolute paths that might be dangerous
|
|
249
|
+
if (path.find("/etc/") == 0 || path.find("/sys/") == 0 ||
|
|
250
|
+
path.find("/proc/") == 0 || path.find("/dev/") == 0) {
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Check for Windows system paths
|
|
255
|
+
if (path.find("C:\\Windows\\") == 0 || path.find("C:\\System32\\") == 0) {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
bool Validator::is_text_file_extension(const std::string &extension) {
|
|
263
|
+
const auto &text_extensions = get_text_extensions();
|
|
264
|
+
|
|
265
|
+
std::string lower_ext = extension;
|
|
266
|
+
std::transform(lower_ext.begin(), lower_ext.end(), lower_ext.begin(),
|
|
267
|
+
::tolower);
|
|
268
|
+
|
|
269
|
+
return std::find(text_extensions.begin(), text_extensions.end(), lower_ext) !=
|
|
270
|
+
text_extensions.end();
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const std::vector<std::string> &Validator::get_text_extensions() {
|
|
274
|
+
static const std::vector<std::string> text_extensions = {
|
|
275
|
+
".txt", ".md", ".rst", ".log", ".cfg", ".conf",
|
|
276
|
+
".ini", ".yaml", ".yml", ".json", ".xml", ".html",
|
|
277
|
+
".htm", ".css", ".js", ".ts", ".jsx", ".tsx",
|
|
278
|
+
".c", ".cpp", ".cc", ".cxx", ".h", ".hpp",
|
|
279
|
+
".hxx", ".java", ".py", ".rb", ".go", ".rs",
|
|
280
|
+
".php", ".pl", ".sh", ".bash", ".zsh", ".fish",
|
|
281
|
+
".ps1", ".sql", ".r", ".m", ".swift", ".kt",
|
|
282
|
+
".scala", ".clj", ".hs", ".ml", ".dockerfile", ".makefile",
|
|
283
|
+
".cmake", ".gradle", ".maven", ".sbt", ".bat", ".csv",
|
|
284
|
+
".tsv"};
|
|
285
|
+
return text_extensions;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
std::string Validator::sanitize_input(const std::string &input) {
|
|
289
|
+
std::string sanitized = input;
|
|
290
|
+
|
|
291
|
+
// Remove null bytes
|
|
292
|
+
sanitized.erase(std::remove(sanitized.begin(), sanitized.end(), '\0'),
|
|
293
|
+
sanitized.end());
|
|
294
|
+
|
|
295
|
+
// Trim whitespace
|
|
296
|
+
size_t start = sanitized.find_first_not_of(" \t\n\r");
|
|
297
|
+
if (start == std::string::npos)
|
|
298
|
+
return "";
|
|
299
|
+
|
|
300
|
+
size_t end = sanitized.find_last_not_of(" \t\n\r");
|
|
301
|
+
sanitized = sanitized.substr(start, end - start + 1);
|
|
302
|
+
|
|
303
|
+
return sanitized;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
} // namespace Utils
|