@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.
Files changed (100) hide show
  1. package/.clang-tidy +28 -0
  2. package/.dockerignore +56 -0
  3. package/.env.example +29 -0
  4. package/.github/CODEOWNERS +2 -0
  5. package/.github/ISSUE_TEMPLATE/blank.md +27 -0
  6. package/.github/ISSUE_TEMPLATE/bug_report.md +33 -0
  7. package/.github/ISSUE_TEMPLATE/feature_request.md +24 -0
  8. package/.github/SECURITY.md +24 -0
  9. package/.github/codeql/codeql-config.yml +8 -0
  10. package/.github/dependabot.yml +14 -0
  11. package/.github/labeler.yml +50 -0
  12. package/.github/packaging/brand-cursor.png +0 -0
  13. package/.github/packaging/database/init.sql +48 -0
  14. package/.github/packaging/docker/Dockerfile +111 -0
  15. package/.github/packaging/docker/docker-compose.yml +56 -0
  16. package/.github/packaging/scripts/preflight.sh +413 -0
  17. package/.github/packaging/scripts/prepare-release.sh +141 -0
  18. package/.github/packaging/scripts/release.sh +22 -0
  19. package/.github/packaging/scripts/setup-git-hooks.sh +73 -0
  20. package/.github/pull_request_template.md +31 -0
  21. package/.github/signed.json +9 -0
  22. package/.github/workflows/README.md +23 -0
  23. package/.github/workflows/ci.yml +181 -0
  24. package/.github/workflows/cla.yml +33 -0
  25. package/.github/workflows/formula-sha.yml +63 -0
  26. package/.github/workflows/issue-response.yml +44 -0
  27. package/.github/workflows/labeler.yml +42 -0
  28. package/.github/workflows/pr-body.yml +49 -0
  29. package/.github/workflows/release.yml +176 -0
  30. package/.github/workflows/security.yml +94 -0
  31. package/.github/workflows/stale.yml +38 -0
  32. package/AGENTS.md +49 -0
  33. package/CHANGELOG.md +3 -0
  34. package/CMakeLists.txt +646 -0
  35. package/Formula/cursor.rb +46 -0
  36. package/LICENSE +201 -0
  37. package/Makefile +28 -0
  38. package/README.md +46 -0
  39. package/cli.js +16 -0
  40. package/include/agent.h +86 -0
  41. package/include/agent_mode.h +17 -0
  42. package/include/memory_manager.h +102 -0
  43. package/include/services/ai_service.h +31 -0
  44. package/include/services/auth_service.h +87 -0
  45. package/include/services/checkpoint_service.h +69 -0
  46. package/include/services/codebase_service.h +38 -0
  47. package/include/services/command_service.h +23 -0
  48. package/include/services/context_service.h +74 -0
  49. package/include/services/database_service.h +56 -0
  50. package/include/services/error_service.h +106 -0
  51. package/include/services/file_service.h +51 -0
  52. package/include/services/git_service.h +29 -0
  53. package/include/services/github_service.h +85 -0
  54. package/include/services/mcp_service.h +85 -0
  55. package/include/services/multi_file_service.h +93 -0
  56. package/include/services/sandbox_service.h +96 -0
  57. package/include/services/theme_service.h +67 -0
  58. package/include/services/web_service.h +52 -0
  59. package/include/utils/config.h +68 -0
  60. package/include/utils/memory_utils.h +79 -0
  61. package/include/utils/platform.h +56 -0
  62. package/include/utils/ui.h +43 -0
  63. package/include/utils/validation.h +63 -0
  64. package/include/utils/version.h.in +17 -0
  65. package/install.js +49 -0
  66. package/package.json +16 -0
  67. package/release/checksums.txt +3 -0
  68. package/release/cursor-linux/cursor_v0.1.7_linux_amd64.tar.gz +0 -0
  69. package/release/cursor-macos/cursor_v0.1.7_darwin_arm64.tar.gz +0 -0
  70. package/release/cursor-windows/cursor__windows_amd64.zip +0 -0
  71. package/src/agent.cpp +2026 -0
  72. package/src/main.cpp +97 -0
  73. package/src/memory_manager.cpp +814 -0
  74. package/src/services/ai_service.cpp +366 -0
  75. package/src/services/auth_service.cpp +779 -0
  76. package/src/services/checkpoint_service.cpp +465 -0
  77. package/src/services/codebase_service.cpp +233 -0
  78. package/src/services/command_service.cpp +82 -0
  79. package/src/services/context_service.cpp +348 -0
  80. package/src/services/database_service.cpp +148 -0
  81. package/src/services/error_service.cpp +438 -0
  82. package/src/services/file_service.cpp +349 -0
  83. package/src/services/git_service.cpp +148 -0
  84. package/src/services/github_service.cpp +435 -0
  85. package/src/services/mcp_service.cpp +481 -0
  86. package/src/services/multi_file_service.cpp +591 -0
  87. package/src/services/sandbox_service.cpp +678 -0
  88. package/src/services/theme_service.cpp +429 -0
  89. package/src/services/web_service.cpp +532 -0
  90. package/src/utils/config.cpp +77 -0
  91. package/src/utils/memory_utils.cpp +93 -0
  92. package/src/utils/ui.cpp +307 -0
  93. package/src/utils/validation.cpp +306 -0
  94. package/src/utils/version.cpp +175 -0
  95. package/tests/e2e/docker-compose.yml +195 -0
  96. package/tests/e2e/run_e2e_tests.sh +70 -0
  97. package/tests/e2e/run_tests_in_docker.sh +115 -0
  98. package/tests/main_test.cpp +16 -0
  99. package/tests/mocks/mock_ollama.py +98 -0
  100. package/tests/mocks/start_nginx.sh +64 -0
@@ -0,0 +1,779 @@
1
+ #include "services/auth_service.h"
2
+ #include <cstdlib>
3
+ #include <filesystem>
4
+ #include <fstream>
5
+ #include <iomanip>
6
+ #include <iostream>
7
+ #include <nlohmann/json.hpp>
8
+ #include <openssl/err.h>
9
+ #include <openssl/evp.h>
10
+ #include <openssl/kdf.h>
11
+ #include <openssl/rand.h>
12
+ #include <random>
13
+ #include <sstream>
14
+ #ifdef _WIN32
15
+ #include <Lmcons.h> // for UNLEN and GetUserName
16
+ #include <windows.h>
17
+ #else
18
+ #include <pwd.h>
19
+ #include <unistd.h> // for getlogin_r
20
+ #endif
21
+
22
+ struct EvpCipherCtx {
23
+ EVP_CIPHER_CTX *ctx;
24
+ EvpCipherCtx() : ctx(EVP_CIPHER_CTX_new()) {}
25
+ ~EvpCipherCtx() {
26
+ if (ctx)
27
+ EVP_CIPHER_CTX_free(ctx);
28
+ }
29
+ EvpCipherCtx(const EvpCipherCtx &) = delete;
30
+ EvpCipherCtx &operator=(const EvpCipherCtx &) = delete;
31
+ EVP_CIPHER_CTX *get() const { return ctx; }
32
+ explicit operator bool() const { return ctx != nullptr; }
33
+ };
34
+
35
+ // Initialize static members
36
+ namespace Services {
37
+ std::map<std::string, AuthProvider> AuthService::providers;
38
+ std::string AuthService::active_provider = "together";
39
+ std::vector<unsigned char> AuthService::encryption_key;
40
+ std::string AuthService::key_file_path = "data/encryption_key.bin";
41
+ } // namespace Services
42
+
43
+ std::string Services::AuthService::get_auth_config_path() {
44
+ return "data/auth_config.json";
45
+ }
46
+
47
+ void Services::AuthService::ensure_auth_directory() {
48
+ std::filesystem::create_directories("data");
49
+ }
50
+
51
+ std::vector<unsigned char>
52
+ Services::AuthService::generate_random_bytes(size_t length) {
53
+ std::vector<unsigned char> buffer(length);
54
+ if (RAND_bytes(buffer.data(), static_cast<int>(length)) != 1) {
55
+ throw std::runtime_error("Failed to generate random bytes");
56
+ }
57
+ return buffer;
58
+ }
59
+
60
+ std::string
61
+ Services::AuthService::bytes_to_hex(const std::vector<unsigned char> &bytes) {
62
+ std::ostringstream oss;
63
+ oss << std::hex << std::setfill('0');
64
+ for (unsigned char byte : bytes) {
65
+ oss << std::setw(2) << static_cast<int>(byte);
66
+ }
67
+ return oss.str();
68
+ }
69
+
70
+ std::vector<unsigned char>
71
+ Services::AuthService::hex_to_bytes(const std::string &hex) {
72
+ std::vector<unsigned char> bytes;
73
+ for (size_t i = 0; i < hex.length(); i += 2) {
74
+ std::string byte_string = hex.substr(i, 2);
75
+ auto byte =
76
+ static_cast<unsigned char>(strtol(byte_string.c_str(), nullptr, 16));
77
+ bytes.push_back(byte);
78
+ }
79
+ return bytes;
80
+ }
81
+
82
+ bool Services::AuthService::derive_key(const std::string &password,
83
+ const std::vector<unsigned char> &salt,
84
+ std::vector<unsigned char> &key) {
85
+ key.resize(AuthService::key_size);
86
+ return PKCS5_PBKDF2_HMAC(
87
+ password.c_str(), static_cast<int>(password.length()), salt.data(),
88
+ static_cast<int>(salt.size()), ITERATIONS, EVP_sha256(),
89
+ AuthService::key_size, key.data()) == 1;
90
+ }
91
+
92
+ bool Services::AuthService::save_encryption_key() {
93
+ ensure_auth_directory();
94
+ std::ofstream key_file(key_file_path, std::ios::binary);
95
+ if (!key_file)
96
+ return false;
97
+
98
+ // Generate a random salt
99
+ auto salt = generate_random_bytes(SALT_SIZE);
100
+
101
+ // Derive a key from the system's username and salt
102
+ std::vector<unsigned char> derived_key;
103
+ char username[256] = {0};
104
+
105
+ // Get username in a cross-platform way
106
+ #ifdef _WIN32
107
+ DWORD username_len = UNLEN + 1;
108
+ if (!GetUserNameA(username, &username_len)) {
109
+ throw std::runtime_error("Failed to get Windows username");
110
+ }
111
+ #else
112
+ if (getlogin_r(username, sizeof(username) - 1) != 0) {
113
+ // Try getpwuid if getlogin_r fails
114
+ struct passwd *pwd = getpwuid(geteuid());
115
+ if (!pwd || !pwd->pw_name) {
116
+ throw std::runtime_error("Failed to get system username");
117
+ }
118
+ strncpy(username, pwd->pw_name, sizeof(username) - 1);
119
+ username[sizeof(username) - 1] = '\0';
120
+ }
121
+ #endif
122
+
123
+ if (!derive_key(username, salt, derived_key)) {
124
+ throw std::runtime_error("Key derivation failed");
125
+ }
126
+
127
+ // Encrypt the master key with the derived key
128
+ std::vector<unsigned char> iv = generate_random_bytes(AuthService::iv_size);
129
+ std::vector<unsigned char> tag(AuthService::tag_size);
130
+ std::vector<unsigned char> ciphertext(encryption_key.size() +
131
+ EVP_MAX_BLOCK_LENGTH);
132
+
133
+ int len = 0;
134
+ int ciphertext_len = 0;
135
+
136
+ EvpCipherCtx ctx;
137
+ if (!ctx) {
138
+ return false;
139
+ }
140
+
141
+ if (EVP_EncryptInit_ex(ctx.get(), EVP_aes_256_gcm(), nullptr,
142
+ derived_key.data(), iv.data()) != 1) {
143
+ return false;
144
+ }
145
+
146
+ if (EVP_EncryptUpdate(ctx.get(), ciphertext.data(), &len,
147
+ encryption_key.data(),
148
+ static_cast<int>(encryption_key.size())) != 1) {
149
+ return false;
150
+ }
151
+ ciphertext_len = len;
152
+
153
+ if (EVP_EncryptFinal_ex(ctx.get(), ciphertext.data() + len, &len) != 1) {
154
+ return false;
155
+ }
156
+ ciphertext_len += len;
157
+
158
+ if (EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_GET_TAG,
159
+ AuthService::tag_size, tag.data()) != 1) {
160
+ return false;
161
+ }
162
+
163
+ // Write salt, IV, tag, and ciphertext to file
164
+ key_file.write(reinterpret_cast<const char *>(salt.data()), salt.size());
165
+ key_file.write(reinterpret_cast<const char *>(iv.data()), iv.size());
166
+ key_file.write(reinterpret_cast<const char *>(tag.data()), tag.size());
167
+ key_file.write(reinterpret_cast<const char *>(ciphertext.data()),
168
+ ciphertext_len);
169
+
170
+ return true;
171
+ }
172
+
173
+ bool Services::AuthService::load_or_generate_key() {
174
+ // Try to load existing key
175
+ std::ifstream key_file(key_file_path, std::ios::binary);
176
+ if (key_file) {
177
+ // Read salt, IV, tag, and ciphertext
178
+ std::vector<unsigned char> salt(SALT_SIZE);
179
+ std::vector<unsigned char> iv(AuthService::iv_size);
180
+ std::vector<unsigned char> tag(tag_size);
181
+
182
+ key_file.read(reinterpret_cast<char *>(salt.data()), SALT_SIZE);
183
+ key_file.read(reinterpret_cast<char *>(iv.data()), iv_size);
184
+ key_file.read(reinterpret_cast<char *>(tag.data()), tag_size);
185
+
186
+ // Read the rest as ciphertext
187
+ std::vector<unsigned char> ciphertext(
188
+ (std::istreambuf_iterator<char>(key_file)),
189
+ std::istreambuf_iterator<char>());
190
+
191
+ // Derive key from username and salt
192
+ char username[256] = {0};
193
+ #ifdef _WIN32
194
+ DWORD username_len = UNLEN + 1;
195
+ if (!GetUserNameA(username, &username_len)) {
196
+ throw std::runtime_error("Failed to get Windows username");
197
+ }
198
+ #else
199
+ if (getlogin_r(username, sizeof(username) - 1) != 0) {
200
+ // Try getpwuid if getlogin_r fails
201
+ struct passwd *pwd = getpwuid(geteuid());
202
+ if (!pwd || !pwd->pw_name) {
203
+ throw std::runtime_error("Failed to get system username");
204
+ }
205
+ strncpy(username, pwd->pw_name, sizeof(username) - 1);
206
+ username[sizeof(username) - 1] = '\0';
207
+ }
208
+ #endif
209
+
210
+ std::vector<unsigned char> derived_key;
211
+ if (!derive_key(username, salt, derived_key)) {
212
+ throw std::runtime_error("Key derivation failed");
213
+ }
214
+
215
+ // Decrypt the master key
216
+ std::vector<unsigned char> plaintext(ciphertext.size());
217
+ int len;
218
+ int plaintext_len;
219
+
220
+ EvpCipherCtx ctx;
221
+ if (!ctx)
222
+ return false;
223
+
224
+ if (EVP_DecryptInit_ex(ctx.get(), EVP_aes_256_gcm(), nullptr,
225
+ derived_key.data(), iv.data()) != 1) {
226
+ return false;
227
+ }
228
+
229
+ if (EVP_DecryptUpdate(ctx.get(), plaintext.data(), &len, ciphertext.data(),
230
+ static_cast<int>(ciphertext.size())) != 1) {
231
+ return false;
232
+ }
233
+ plaintext_len = len;
234
+
235
+ // Set expected tag value
236
+ if (EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_TAG, tag_size,
237
+ tag.data()) != 1) {
238
+ return false;
239
+ }
240
+
241
+ // Finalize decryption and check authentication tag
242
+ int ret = EVP_DecryptFinal_ex(ctx.get(), plaintext.data() + len, &len);
243
+
244
+ if (ret <= 0) {
245
+ // Authentication failed
246
+ return false;
247
+ }
248
+
249
+ plaintext_len += len;
250
+ plaintext.resize(plaintext_len);
251
+ encryption_key = plaintext;
252
+ return true;
253
+ }
254
+
255
+ // No existing key, generate a new one
256
+ encryption_key = generate_random_bytes(key_size);
257
+ return save_encryption_key();
258
+ }
259
+
260
+ bool Services::AuthService::initialize_encryption_key() {
261
+ try {
262
+ return load_or_generate_key();
263
+ } catch (const std::exception &e) {
264
+ std::cerr << "Encryption key initialization failed: " << e.what()
265
+ << std::endl;
266
+ return false;
267
+ }
268
+ }
269
+
270
+ std::string
271
+ Services::AuthService::encrypt_credential(const std::string &credential) {
272
+ if (encryption_key.empty() && !initialize_encryption_key()) {
273
+ throw std::runtime_error("Failed to initialize encryption key");
274
+ }
275
+
276
+ // Generate random IV
277
+ std::vector<unsigned char> iv = generate_random_bytes(AuthService::iv_size);
278
+ std::vector<unsigned char> tag(AuthService::tag_size);
279
+ std::vector<unsigned char> ciphertext(credential.size() +
280
+ EVP_MAX_BLOCK_LENGTH);
281
+
282
+ int len;
283
+ int ciphertext_len;
284
+
285
+ // Create and initialize the encryption context
286
+ EvpCipherCtx ctx;
287
+ if (!ctx)
288
+ throw std::runtime_error("Failed to create encryption context");
289
+
290
+ // Initialize the encryption operation with AES-256-GCM
291
+ if (EVP_EncryptInit_ex(ctx.get(), EVP_aes_256_gcm(), nullptr,
292
+ encryption_key.data(), iv.data()) != 1) {
293
+ throw std::runtime_error("Encryption initialization failed");
294
+ }
295
+
296
+ // Encrypt the plaintext
297
+ if (EVP_EncryptUpdate(
298
+ ctx.get(), ciphertext.data(), &len,
299
+ reinterpret_cast<const unsigned char *>(credential.c_str()),
300
+ static_cast<int>(credential.length())) != 1) {
301
+ throw std::runtime_error("Encryption update failed");
302
+ }
303
+ ciphertext_len = len;
304
+
305
+ // Finalize the encryption
306
+ if (EVP_EncryptFinal_ex(ctx.get(), ciphertext.data() + len, &len) != 1) {
307
+ throw std::runtime_error("Encryption finalization failed");
308
+ }
309
+ ciphertext_len += len;
310
+
311
+ // Get the authentication tag
312
+ if (EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_GET_TAG,
313
+ AuthService::tag_size, tag.data()) != 1) {
314
+ throw std::runtime_error("Failed to get authentication tag");
315
+ }
316
+
317
+ // Resize the ciphertext to actual size
318
+ ciphertext.resize(ciphertext_len);
319
+
320
+ // Combine IV + tag + ciphertext and encode as hex
321
+ std::vector<unsigned char> combined;
322
+ combined.reserve(iv.size() + tag.size() + ciphertext.size());
323
+ combined.insert(combined.end(), iv.begin(), iv.end());
324
+ combined.insert(combined.end(), tag.begin(), tag.end());
325
+ combined.insert(combined.end(), ciphertext.begin(), ciphertext.end());
326
+
327
+ return bytes_to_hex(combined);
328
+ }
329
+
330
+ std::string
331
+ Services::AuthService::decrypt_credential(const std::string &encrypted_hex) {
332
+ if (encryption_key.empty() && !initialize_encryption_key()) {
333
+ throw std::runtime_error("Failed to initialize encryption key");
334
+ }
335
+
336
+ // Convert hex string back to bytes
337
+ std::vector<unsigned char> combined = hex_to_bytes(encrypted_hex);
338
+
339
+ // Extract IV, tag, and ciphertext
340
+ if (combined.size() < iv_size + tag_size) {
341
+ throw std::runtime_error("Invalid encrypted data format");
342
+ }
343
+
344
+ std::vector<unsigned char> iv(combined.begin(), combined.begin() + iv_size);
345
+ std::vector<unsigned char> tag(combined.begin() + iv_size,
346
+ combined.begin() + iv_size + tag_size);
347
+ std::vector<unsigned char> ciphertext(combined.begin() + iv_size + tag_size,
348
+ combined.end());
349
+
350
+ // Decrypt the ciphertext
351
+ std::vector<unsigned char> plaintext(ciphertext.size() +
352
+ EVP_MAX_BLOCK_LENGTH);
353
+ int len;
354
+ int plaintext_len;
355
+
356
+ // Create and initialize the decryption context
357
+ EvpCipherCtx ctx;
358
+ if (!ctx)
359
+ throw std::runtime_error("Failed to create decryption context");
360
+
361
+ // Initialize the decryption operation with AES-256-GCM
362
+ if (EVP_DecryptInit_ex(ctx.get(), EVP_aes_256_gcm(), nullptr,
363
+ encryption_key.data(), iv.data()) != 1) {
364
+ throw std::runtime_error("Decryption initialization failed");
365
+ }
366
+
367
+ // Provide the message to be decrypted and obtain the plaintext output
368
+ if (EVP_DecryptUpdate(ctx.get(), plaintext.data(), &len, ciphertext.data(),
369
+ static_cast<int>(ciphertext.size())) != 1) {
370
+ throw std::runtime_error("Decryption update failed");
371
+ }
372
+ plaintext_len = len;
373
+
374
+ // Set the expected tag value
375
+ if (EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_TAG, tag_size,
376
+ tag.data()) != 1) {
377
+ throw std::runtime_error("Failed to set authentication tag");
378
+ }
379
+
380
+ // Finalize the decryption and check the authentication tag
381
+ int ret = EVP_DecryptFinal_ex(ctx.get(), plaintext.data() + len, &len);
382
+
383
+ if (ret <= 0) {
384
+ // Authentication failed
385
+ throw std::runtime_error(
386
+ "Authentication failed - invalid key or corrupted data");
387
+ }
388
+
389
+ plaintext_len += len;
390
+ plaintext.resize(plaintext_len);
391
+
392
+ return std::string(plaintext.begin(), plaintext.end());
393
+ }
394
+
395
+ void Services::AuthService::initialize_default_providers() {
396
+ // Together AI
397
+ AuthProvider together;
398
+ together.name = "together";
399
+ together.display_name = "Together AI";
400
+ together.base_url = "https://api.together.xyz/v1";
401
+ together.model = "meta-llama/Llama-3.2-3B-Instruct-Turbo";
402
+ together.is_active = true;
403
+ providers["together"] = together;
404
+
405
+ // Ollama (local)
406
+ AuthProvider ollama;
407
+ ollama.name = "ollama";
408
+ ollama.display_name = "Ollama (Local)";
409
+ ollama.base_url = "http://localhost:11434/v1";
410
+ ollama.model = "llama3.2:3b";
411
+ ollama.api_key = "ollama"; // Placeholder for local
412
+ providers["ollama"] = ollama;
413
+
414
+ // OpenAI
415
+ AuthProvider openai;
416
+ openai.name = "openai";
417
+ openai.display_name = "OpenAI";
418
+ openai.base_url = "https://api.openai.com/v1";
419
+ openai.model = "gpt-4";
420
+ providers["openai"] = openai;
421
+
422
+ // Anthropic
423
+ AuthProvider anthropic;
424
+ anthropic.name = "anthropic";
425
+ anthropic.display_name = "Anthropic Claude";
426
+ anthropic.base_url = "https://api.anthropic.com/v1";
427
+ anthropic.model = "claude-3-sonnet-20240229";
428
+ providers["anthropic"] = anthropic;
429
+
430
+ // Cerebras
431
+ AuthProvider cerebras;
432
+ cerebras.name = "cerebras";
433
+ cerebras.display_name = "Cerebras";
434
+ cerebras.base_url = "https://api.cerebras.ai/v1";
435
+ cerebras.model = "llama3.1-8b";
436
+ providers["cerebras"] = cerebras;
437
+ }
438
+
439
+ void Services::AuthService::initialize() {
440
+ // Initialize encryption first
441
+ if (!initialize_encryption_key()) {
442
+ throw std::runtime_error("Failed to initialize encryption");
443
+ }
444
+
445
+ initialize_default_providers();
446
+ load_auth_config();
447
+
448
+ // Try to load API keys from environment variables
449
+ for (auto &[name, provider] : providers) {
450
+ std::string env_var = name + "_API_KEY";
451
+ std::transform(env_var.begin(), env_var.end(), env_var.begin(), ::toupper);
452
+
453
+ const char *env_key = std::getenv(env_var.c_str());
454
+ if (env_key && provider.api_key.empty()) {
455
+ try {
456
+ // Encrypt the API key before storing it
457
+ provider.api_key = encrypt_credential(env_key);
458
+ provider.is_valid = true;
459
+ // Save the encrypted key to config
460
+ save_auth_config();
461
+ } catch (const std::exception &e) {
462
+ std::cerr << "Failed to encrypt API key for " << name << ": "
463
+ << e.what() << std::endl;
464
+ }
465
+ }
466
+ }
467
+ }
468
+
469
+ bool Services::AuthService::add_provider(const AuthProvider &provider) {
470
+ providers[provider.name] = provider;
471
+ return save_auth_config();
472
+ }
473
+
474
+ bool Services::AuthService::remove_provider(const std::string &provider_name) {
475
+ auto it = providers.find(provider_name);
476
+ if (it == providers.end()) {
477
+ return false;
478
+ }
479
+
480
+ // Don't allow removal of built-in providers
481
+ if (provider_name == "together" || provider_name == "ollama" ||
482
+ provider_name == "openai" || provider_name == "anthropic" ||
483
+ provider_name == "cerebras") {
484
+ return false;
485
+ }
486
+
487
+ providers.erase(it);
488
+
489
+ // If we're removing the active provider, switch to together
490
+ if (active_provider == provider_name) {
491
+ active_provider = "together";
492
+ }
493
+
494
+ return save_auth_config();
495
+ }
496
+
497
+ bool Services::AuthService::set_active_provider(
498
+ const std::string &provider_name) {
499
+ auto it = providers.find(provider_name);
500
+ if (it == providers.end()) {
501
+ return false;
502
+ }
503
+
504
+ // Deactivate all providers
505
+ for (auto &[name, provider] : providers) {
506
+ provider.is_active = false;
507
+ }
508
+
509
+ // Activate the selected provider
510
+ it->second.is_active = true;
511
+ active_provider = provider_name;
512
+
513
+ return save_auth_config();
514
+ }
515
+
516
+ std::string Services::AuthService::get_active_provider() {
517
+ return active_provider;
518
+ }
519
+
520
+ std::vector<std::string> Services::AuthService::list_providers() {
521
+ std::vector<std::string> provider_names;
522
+ for (const auto &[name, provider] : providers) {
523
+ provider_names.push_back(name);
524
+ }
525
+ return provider_names;
526
+ }
527
+
528
+ Services::AuthProvider
529
+ Services::AuthService::get_provider_info(const std::string &provider_name) {
530
+ auto it = providers.find(provider_name);
531
+ if (it != providers.end()) {
532
+ return it->second;
533
+ }
534
+ return AuthProvider{}; // Return empty provider if not found
535
+ }
536
+
537
+ bool Services::AuthService::set_api_key(const std::string &provider_name,
538
+ const std::string &api_key) {
539
+ auto it = providers.find(provider_name);
540
+ if (it == providers.end()) {
541
+ return false;
542
+ }
543
+
544
+ it->second.api_key = api_key;
545
+ it->second.is_valid = !api_key.empty();
546
+
547
+ return save_auth_config();
548
+ }
549
+
550
+ std::string
551
+ Services::AuthService::get_api_key(const std::string &provider_name) {
552
+ std::string target_provider =
553
+ provider_name.empty() ? active_provider : provider_name;
554
+
555
+ auto it = providers.find(target_provider);
556
+ if (it != providers.end()) {
557
+ return it->second.api_key;
558
+ }
559
+ return "";
560
+ }
561
+
562
+ bool Services::AuthService::validate_credentials(
563
+ const std::string &provider_name) {
564
+ auto it = providers.find(provider_name);
565
+ if (it == providers.end()) {
566
+ return false;
567
+ }
568
+
569
+ // For local providers like Ollama, always consider valid
570
+ if (provider_name == "ollama") {
571
+ it->second.is_valid = true;
572
+ return true;
573
+ }
574
+
575
+ // For API-based providers, check if API key exists
576
+ bool is_valid = !it->second.api_key.empty();
577
+ it->second.is_valid = is_valid;
578
+
579
+ return is_valid;
580
+ }
581
+
582
+ void Services::AuthService::clear_credentials(
583
+ const std::string &provider_name) {
584
+ auto it = providers.find(provider_name);
585
+ if (it != providers.end()) {
586
+ it->second.api_key.clear();
587
+ it->second.is_valid = false;
588
+ save_auth_config();
589
+ }
590
+ }
591
+
592
+ bool Services::AuthService::save_auth_config() {
593
+ try {
594
+ ensure_auth_directory();
595
+
596
+ nlohmann::json config;
597
+ config["active_provider"] = active_provider;
598
+ config["providers"] = nlohmann::json::object();
599
+
600
+ for (const auto &[name, provider] : providers) {
601
+ nlohmann::json provider_json;
602
+ provider_json["name"] = provider.name;
603
+ provider_json["display_name"] = provider.display_name;
604
+ provider_json["base_url"] = provider.base_url;
605
+ provider_json["model"] = provider.model;
606
+ provider_json["is_active"] = provider.is_active;
607
+ provider_json["is_valid"] = provider.is_valid;
608
+
609
+ // Encrypt API key before saving
610
+ if (!provider.api_key.empty()) {
611
+ provider_json["api_key"] = encrypt_credential(provider.api_key);
612
+ }
613
+
614
+ if (!provider.additional_config.empty()) {
615
+ provider_json["additional_config"] = provider.additional_config;
616
+ }
617
+
618
+ config["providers"][name] = provider_json;
619
+ }
620
+
621
+ std::ofstream file(get_auth_config_path());
622
+ file << config.dump(2);
623
+
624
+ return true;
625
+
626
+ } catch (const std::exception &e) {
627
+ std::cerr << "Failed to save auth config: " << e.what() << std::endl;
628
+ return false;
629
+ }
630
+ }
631
+
632
+ bool Services::AuthService::load_auth_config() {
633
+ try {
634
+ std::string config_path = get_auth_config_path();
635
+ if (!std::filesystem::exists(config_path)) {
636
+ return true; // Use defaults
637
+ }
638
+
639
+ std::ifstream file(config_path);
640
+ nlohmann::json config;
641
+ file >> config;
642
+
643
+ if (config.contains("active_provider")) {
644
+ active_provider = config["active_provider"];
645
+ }
646
+
647
+ if (config.contains("providers")) {
648
+ for (const auto &[name, provider_json] : config["providers"].items()) {
649
+ auto it = providers.find(name);
650
+ if (it != providers.end()) {
651
+ // Update existing provider
652
+ it->second.is_active = provider_json.value("is_active", false);
653
+ it->second.is_valid = provider_json.value("is_valid", false);
654
+
655
+ if (provider_json.contains("api_key")) {
656
+ it->second.api_key = decrypt_credential(provider_json["api_key"]);
657
+ }
658
+
659
+ if (provider_json.contains("additional_config")) {
660
+ it->second.additional_config = provider_json["additional_config"];
661
+ }
662
+ } else {
663
+ // Add new custom provider
664
+ AuthProvider provider;
665
+ provider.name = provider_json.value("name", name);
666
+ provider.display_name = provider_json.value("display_name", name);
667
+ provider.base_url = provider_json.value("base_url", "");
668
+ provider.model = provider_json.value("model", "");
669
+ provider.is_active = provider_json.value("is_active", false);
670
+ provider.is_valid = provider_json.value("is_valid", false);
671
+
672
+ if (provider_json.contains("api_key")) {
673
+ provider.api_key = decrypt_credential(provider_json["api_key"]);
674
+ }
675
+
676
+ if (provider_json.contains("additional_config")) {
677
+ provider.additional_config = provider_json["additional_config"];
678
+ }
679
+
680
+ providers[name] = provider;
681
+ }
682
+ }
683
+ }
684
+
685
+ return true;
686
+
687
+ } catch (const std::exception &e) {
688
+ std::cerr << "Failed to load auth config: " << e.what() << std::endl;
689
+ return false;
690
+ }
691
+ }
692
+
693
+ bool Services::AuthService::update_provider_config(
694
+ const std::string &provider_name,
695
+ const std::map<std::string, std::string> &config) {
696
+ auto it = providers.find(provider_name);
697
+ if (it == providers.end()) {
698
+ return false;
699
+ }
700
+
701
+ for (const auto &[key, value] : config) {
702
+ it->second.additional_config[key] = value;
703
+ }
704
+
705
+ return save_auth_config();
706
+ }
707
+
708
+ bool Services::AuthService::test_provider_connection(
709
+ const std::string &provider_name) {
710
+ auto it = providers.find(provider_name);
711
+ if (it == providers.end()) {
712
+ return false;
713
+ }
714
+
715
+ // For local providers like Ollama, always return true for now
716
+ if (provider_name == "ollama") {
717
+ return true;
718
+ }
719
+
720
+ // For API providers, check if we have credentials
721
+ return !it->second.api_key.empty();
722
+ }
723
+
724
+ std::string
725
+ Services::AuthService::get_provider_status(const std::string &provider_name) {
726
+ auto it = providers.find(provider_name);
727
+ if (it == providers.end()) {
728
+ return "Not Found";
729
+ }
730
+
731
+ if (!it->second.is_valid) {
732
+ return "Invalid Credentials";
733
+ }
734
+
735
+ if (test_provider_connection(provider_name)) {
736
+ return "Connected";
737
+ }
738
+
739
+ return "Disconnected";
740
+ }
741
+
742
+ void Services::AuthService::refresh_all_provider_status() {
743
+ for (auto &[name, provider] : providers) {
744
+ provider.is_valid = validate_credentials(name);
745
+ }
746
+ save_auth_config();
747
+ }
748
+
749
+ bool Services::AuthService::is_credential_secure() {
750
+ // Check if we have proper encryption setup
751
+ return true; // For now, assume secure
752
+ }
753
+
754
+ void Services::AuthService::generate_encryption_key() {
755
+ // In a real implementation, generate a proper encryption key
756
+ // For now, this is a placeholder
757
+ }
758
+
759
+ bool Services::AuthService::backup_credentials(const std::string &backup_path) {
760
+ try {
761
+ std::filesystem::copy_file(get_auth_config_path(), backup_path);
762
+ return true;
763
+ } catch (const std::exception &e) {
764
+ std::cerr << "Failed to backup credentials: " << e.what() << std::endl;
765
+ return false;
766
+ }
767
+ }
768
+
769
+ bool Services::AuthService::restore_credentials(
770
+ const std::string &backup_path) {
771
+ try {
772
+ std::filesystem::copy_file(backup_path, get_auth_config_path());
773
+ load_auth_config();
774
+ return true;
775
+ } catch (const std::exception &e) {
776
+ std::cerr << "Failed to restore credentials: " << e.what() << std::endl;
777
+ return false;
778
+ }
779
+ }