@bniladridas/cursor 0.1.17 → 0.1.19

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/src/agent.cpp CHANGED
@@ -21,20 +21,44 @@
21
21
  #include "version.h"
22
22
 
23
23
  #include <algorithm>
24
+ #include <optional>
24
25
  #include <atomic>
25
26
  #include <cctype>
27
+ #include <cstdio>
28
+ #include <cstdlib>
29
+ #include <iomanip>
26
30
  #include <iostream>
27
31
  #include <thread>
32
+ #ifdef _WIN32
33
+ #include <io.h>
34
+ #else
35
+ #include <unistd.h>
36
+ #include <termios.h>
37
+ #endif
28
38
 
29
39
  #include <filesystem>
30
40
  #include <fstream>
31
41
  #include <map>
42
+ #include <nlohmann/json.hpp>
32
43
  #include <sstream>
33
44
  #include <vector>
34
45
 
35
46
  // Constants for response handling
36
47
  const size_t MAX_RESPONSE_LENGTH = 8000;
37
48
 
49
+ #ifdef CURSOR_USE_LIBEDIT
50
+ extern "C" char *readline(const char *);
51
+ extern "C" void add_history(const char *);
52
+ #endif
53
+
54
+ static bool is_tty_stream(FILE *stream) {
55
+ #ifdef _WIN32
56
+ return _isatty(_fileno(stream)) != 0;
57
+ #else
58
+ return isatty(fileno(stream)) != 0;
59
+ #endif
60
+ }
61
+
38
62
  // Using the Mode enum from agent.h instead of separate constants
39
63
 
40
64
  namespace Core {
@@ -56,50 +80,513 @@ static inline std::string trim_copy(const std::string &s) {
56
80
  return s.substr(a, b - a);
57
81
  }
58
82
 
83
+ static bool starts_with_at(const std::string &s, size_t pos,
84
+ const std::string &prefix) {
85
+ return pos + prefix.size() <= s.size() &&
86
+ s.compare(pos, prefix.size(), prefix) == 0;
87
+ }
88
+
89
+ static std::string normalize_input(const std::string &input) {
90
+ std::string lower = input;
91
+ std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);
92
+ return lower;
93
+ }
94
+
95
+ static std::string strip_trailing_clause(std::string input) {
96
+ std::string lower = normalize_input(input);
97
+ const std::array<std::string_view, 4> suffixes = {
98
+ " in code", " in repo", " in this repo", " in project"};
99
+ for (const auto &suffix : suffixes) {
100
+ if (lower.ends_with(suffix)) {
101
+ input = trim_copy(input.substr(0, input.size() - suffix.size()));
102
+ break;
103
+ }
104
+ }
105
+ return input;
106
+ }
107
+
108
+ static std::optional<std::string>
109
+ extract_grep_command(const std::string &input, const std::string &marker) {
110
+ std::string lower = normalize_input(input);
111
+ size_t pos = lower.find(marker);
112
+ if (pos == std::string::npos)
113
+ return std::nullopt;
114
+
115
+ std::string query = trim_copy(input.substr(pos + marker.size()));
116
+ query = strip_trailing_clause(query);
117
+ if (query.empty())
118
+ return std::nullopt;
119
+ return std::make_optional(std::string("grep:") + query);
120
+ }
121
+
122
+ bool Agent::is_direct_command_input(const std::string &input) {
123
+ static const std::array<std::string_view, 3> direct_aliases = {
124
+ "memory", "clear", "forget"};
125
+ for (const auto &alias : direct_aliases) {
126
+ if (input == alias) {
127
+ return true;
128
+ }
129
+ }
130
+
131
+ static const std::array<std::string_view, 14> direct_prefixes = {
132
+ "search:", "cmd:", "build:", "read:", "write:",
133
+ "replace:", "grep:", "remember:", "analyze:", "components:",
134
+ "todos:", "git:", "tree:", "github:"};
135
+ for (const auto &prefix : direct_prefixes) {
136
+ if (input.starts_with(prefix)) {
137
+ return true;
138
+ }
139
+ }
140
+ return false;
141
+ }
142
+
143
+ bool Agent::is_git_status_query(const std::string &input) {
144
+ std::string lower = normalize_input(input);
145
+
146
+ static const std::vector<std::string> triggers = {
147
+ "files we changed",
148
+ "changed files",
149
+ "show changed files",
150
+ "what changed",
151
+ "check changed files",
152
+ "check the files we changed",
153
+ "what files changed",
154
+ "what changes",
155
+ "what has changed",
156
+ "git diff",
157
+ "git status"};
158
+
159
+ for (const auto &phrase : triggers) {
160
+ if (lower.find(phrase) != std::string::npos) {
161
+ return true;
162
+ }
163
+ }
164
+ return false;
165
+ }
166
+
167
+ std::optional<std::string> Agent::map_nl_to_direct_command(
168
+ const std::string &input) {
169
+ std::string lower = normalize_input(input);
170
+ // Helper to extract the remainder after a marker
171
+ auto extract_after = [&](const std::vector<std::string> &markers)
172
+ -> std::optional<std::string> {
173
+ for (const auto &m : markers) {
174
+ size_t pos = lower.find(m);
175
+ if (pos != std::string::npos) {
176
+ std::string val = trim_copy(input.substr(pos + m.size()));
177
+ if (!val.empty())
178
+ return std::make_optional(val);
179
+ }
180
+ }
181
+ return std::nullopt;
182
+ };
183
+
184
+ // Git-related queries
185
+ if (is_git_status_query(input) || lower.find("what changed") != std::string::npos) {
186
+ return std::make_optional(std::string("git:status"));
187
+ }
188
+ if (lower.find("git diff") != std::string::npos ||
189
+ lower.find("show diff") != std::string::npos ||
190
+ lower.find("diff") != std::string::npos) {
191
+ return std::make_optional(std::string("git:status"));
192
+ }
193
+ if (lower.find("git log") != std::string::npos ||
194
+ lower.find("commit history") != std::string::npos ||
195
+ lower.find("recent commits") != std::string::npos) {
196
+ return std::make_optional(std::string("git:log"));
197
+ }
198
+
199
+ // Run/execute shell commands
200
+ if (auto cmd = extract_after({"run ", "execute ", "start "})) {
201
+ return std::make_optional(std::string("cmd:") + *cmd);
202
+ }
203
+
204
+ // Build / compile requests
205
+ if (lower.find("build the project") != std::string::npos ||
206
+ lower.find("build project") != std::string::npos ||
207
+ lower.find("compile") != std::string::npos ||
208
+ lower.find("build") == 0) {
209
+ // Prefer explicit make invocation when mentioned, otherwise use build wrapper
210
+ if (auto make_cmd = extract_after({"make "})) {
211
+ return std::make_optional(std::string("cmd:") + *make_cmd);
212
+ }
213
+ return std::make_optional(std::string("build:make"));
214
+ }
215
+
216
+ // TODO / task comment queries (prefer over grep)
217
+ if (lower.find("todo") != std::string::npos || lower.find("task comments") != std::string::npos || lower.find("fixme") != std::string::npos) {
218
+ return std::make_optional(std::string("todos:."));
219
+ }
220
+
221
+ // Grep/search-style queries
222
+ if (lower.find("search for ") != std::string::npos &&
223
+ (lower.find("code") != std::string::npos ||
224
+ lower.find("repo") != std::string::npos ||
225
+ lower.find("project") != std::string::npos)) {
226
+ return extract_grep_command(input, "search for ");
227
+ }
228
+ if ((lower.find("find ") != std::string::npos &&
229
+ (lower.find("in code") != std::string::npos ||
230
+ lower.find("in repo") != std::string::npos ||
231
+ lower.find("in project") != std::string::npos ||
232
+ lower.find("in files") != std::string::npos)) ||
233
+ lower.find("grep ") != std::string::npos) {
234
+ if (auto g = extract_after({"find ", "grep ", "search for "}))
235
+ return extract_grep_command(*g, "");
236
+ }
237
+
238
+ // Read/open file
239
+ if (auto path = extract_after({"read file ", "show file ", "open file "})) {
240
+ return std::make_optional(std::string("read:") + *path);
241
+ }
242
+
243
+ // Write/save file
244
+ if (lower.find("save file ") != std::string::npos ||
245
+ lower.find("write file ") != std::string::npos ||
246
+ lower.find("save ") == 0) {
247
+ if (auto p = extract_after({"save file ", "write file ", "save "})) {
248
+ // If user provided content like "save file X as Y", attempt to parse
249
+ size_t as_pos = normalize_input(*p).find(" as ");
250
+ if (as_pos != std::string::npos) {
251
+ std::string filename = trim_copy(p->substr(0, as_pos));
252
+ std::string content = trim_copy(p->substr(as_pos + 4));
253
+ if (!filename.empty() && !content.empty())
254
+ return std::make_optional(std::string("write:") + filename + " " + content);
255
+ }
256
+ // Fallback: just write filename (caller will validate usage)
257
+ return std::make_optional(std::string("write:") + *p);
258
+ }
259
+ }
260
+
261
+ // Replace text in files: "replace X with Y in file Z"
262
+ if (lower.find("replace ") != std::string::npos && lower.find(" with ") != std::string::npos) {
263
+ // crude parsing
264
+ size_t rep_pos = lower.find("replace ");
265
+ size_t with_pos = lower.find(" with ");
266
+ std::string old_text = trim_copy(input.substr(rep_pos + 8, with_pos - (rep_pos + 8)));
267
+ size_t in_pos = lower.find(" in file ");
268
+ if (in_pos == std::string::npos)
269
+ in_pos = lower.find(" in ");
270
+ if (in_pos != std::string::npos) {
271
+ std::string new_text = trim_copy(input.substr(with_pos + 6, in_pos - (with_pos + 6)));
272
+ std::string filename = trim_copy(input.substr(in_pos + (lower.find(" in file ") != std::string::npos ? 9 : 4)));
273
+ if (!filename.empty() && !old_text.empty() && !new_text.empty()) {
274
+ return std::make_optional(std::string("replace:") + filename + ":" + old_text + ":" + new_text);
275
+ }
276
+ }
277
+ }
278
+
279
+ // Remember / forget / memory / clear
280
+ if (auto fact = extract_after({"remember ", "remember:"})) {
281
+ return std::make_optional(std::string("remember:") + *fact);
282
+ }
283
+ if (lower.find("forget") != std::string::npos) {
284
+ return std::make_optional(std::string("forget"));
285
+ }
286
+ if (lower.find("show memory") != std::string::npos || lower.find("memory show") != std::string::npos || lower == "memory") {
287
+ return std::make_optional(std::string("memory"));
288
+ }
289
+ if (lower.find("clear memory") != std::string::npos || lower == "clear") {
290
+ return std::make_optional(std::string("clear"));
291
+ }
292
+
293
+ // Analyze / components / todos / tree
294
+ if (lower.find("analyze repo") != std::string::npos || lower.find("analyze") == 0 || lower.find("repository analysis") != std::string::npos) {
295
+ return std::make_optional(std::string("analyze:."));
296
+ }
297
+ if (lower.find("components") != std::string::npos) {
298
+ return std::make_optional(std::string("components:."));
299
+ }
300
+ if (lower.find("todo") != std::string::npos || lower.find("task comments") != std::string::npos) {
301
+ return std::make_optional(std::string("todos:."));
302
+ }
303
+ if (lower.find("directory tree") != std::string::npos || lower.find("repo tree") != std::string::npos || lower.find("tree ") != std::string::npos) {
304
+ return std::make_optional(std::string("tree:."));
305
+ }
306
+
307
+ // GitHub quick helpers: "open repo owner/repo on github" or "github repo owner/repo"
308
+ if (lower.find("on github") != std::string::npos) {
309
+ // try to find owner/repo earlier in the sentence
310
+ size_t slash = lower.find('/');
311
+ if (slash != std::string::npos) {
312
+ // extract token around slash
313
+ size_t start = lower.rfind(' ', slash);
314
+ if (start == std::string::npos) start = 0; else start++;
315
+ size_t end = lower.find(' ', slash);
316
+ if (end == std::string::npos) end = lower.size();
317
+ std::string repo_spec = trim_copy(input.substr(start, end - start));
318
+ return std::make_optional(std::string("github:repo:") + repo_spec);
319
+ }
320
+ }
321
+
322
+ // Fallback: no mapping
323
+ return std::nullopt;
324
+ }
325
+
326
+ static std::string replace_inline_markdown(const std::string &line) {
327
+ std::string out;
328
+ bool bold = false;
329
+ bool code = false;
330
+ for (size_t i = 0; i < line.size(); i++) {
331
+ if (starts_with_at(line, i, "**")) {
332
+ out += bold ? Utils::Color::RESET : Utils::Color::BOLD;
333
+ bold = !bold;
334
+ i++;
335
+ } else if (line[i] == '`') {
336
+ out += code ? Utils::Color::RESET : Utils::Color::YELLOW;
337
+ code = !code;
338
+ } else {
339
+ out += line[i];
340
+ }
341
+ }
342
+ if (bold || code) {
343
+ out += Utils::Color::RESET;
344
+ }
345
+ return out;
346
+ }
347
+
348
+ static std::string highlight_code_line(const std::string &line,
349
+ const std::string &language) {
350
+ std::string out;
351
+ std::string token;
352
+ auto flush_token = [&]() {
353
+ if (token.empty()) {
354
+ return;
355
+ }
356
+ static const std::vector<std::string> keywords = {
357
+ "auto", "bool", "break", "case", "class", "const", "continue",
358
+ "else", "false", "for", "function", "if", "int", "let", "return",
359
+ "std", "string", "struct", "true", "var", "void", "while"};
360
+ bool is_keyword =
361
+ std::find(keywords.begin(), keywords.end(), token) != keywords.end();
362
+ if (is_keyword) {
363
+ out += Utils::Color::CYAN + token + Utils::Color::RESET;
364
+ } else {
365
+ out += token;
366
+ }
367
+ token.clear();
368
+ };
369
+
370
+ for (size_t i = 0; i < line.size(); i++) {
371
+ char ch = line[i];
372
+ if (language == "html" && (ch == '<' || ch == '>')) {
373
+ flush_token();
374
+ out += Utils::Color::CYAN;
375
+ out += ch;
376
+ out += Utils::Color::RESET;
377
+ } else if (ch == '"' || ch == '\'') {
378
+ flush_token();
379
+ char quote = ch;
380
+ out += Utils::Color::YELLOW;
381
+ out += ch;
382
+ i++;
383
+ while (i < line.size()) {
384
+ out += line[i];
385
+ if (line[i] == quote) {
386
+ break;
387
+ }
388
+ i++;
389
+ }
390
+ out += Utils::Color::RESET;
391
+ } else if (std::isalnum(static_cast<unsigned char>(ch)) || ch == '_') {
392
+ token += ch;
393
+ } else {
394
+ flush_token();
395
+ out += ch;
396
+ }
397
+ }
398
+ flush_token();
399
+ return out;
400
+ }
401
+
402
+ static std::string render_markdown(const std::string &text) {
403
+ std::stringstream input(text);
404
+ std::ostringstream output;
405
+ std::string line;
406
+ bool in_code = false;
407
+ std::string language;
408
+
409
+ while (std::getline(input, line)) {
410
+ std::string trimmed = trim_copy(line);
411
+ if (trimmed.starts_with("```")) {
412
+ in_code = !in_code;
413
+ language = in_code ? trim_copy(trimmed.substr(3)) : "";
414
+ if (in_code && !language.empty()) {
415
+ output << Utils::Color::DIM << language << Utils::Color::RESET << "\n";
416
+ }
417
+ continue;
418
+ }
419
+
420
+ if (in_code) {
421
+ output << " " << highlight_code_line(line, language) << "\n";
422
+ } else if (trimmed.starts_with("#")) {
423
+ size_t pos = trimmed.find_first_not_of("# ");
424
+ output << Utils::Color::BOLD
425
+ << (pos == std::string::npos ? trimmed : trimmed.substr(pos))
426
+ << Utils::Color::RESET << "\n";
427
+ } else {
428
+ output << replace_inline_markdown(line) << "\n";
429
+ }
430
+ }
431
+ return output.str();
432
+ }
433
+
434
+ static std::string read_prompt(const std::string &prompt,
435
+ const std::vector<std::string> &history) {
436
+ #ifdef CURSOR_USE_LIBEDIT
437
+ (void)history;
438
+ char *line = readline(prompt.c_str());
439
+ if (line == nullptr) {
440
+ std::cin.setstate(std::ios::eofbit);
441
+ return {};
442
+ }
443
+ std::string input(line);
444
+ if (!input.empty()) {
445
+ add_history(line);
446
+ }
447
+ std::free(line);
448
+ return input;
449
+ #else
450
+ #ifdef _WIN32
451
+ (void)prompt;
452
+ (void)history;
453
+ std::string input;
454
+ if (!std::getline(std::cin, input)) {
455
+ std::cin.setstate(std::ios::eofbit);
456
+ return {};
457
+ }
458
+ return input;
459
+ #else
460
+ std::string buf;
461
+ std::string saved;
462
+ int cursor = 0;
463
+ int history_index = (int)history.size();
464
+ struct termios oldt, newt;
465
+ tcgetattr(STDIN_FILENO, &oldt);
466
+ newt = oldt;
467
+ newt.c_lflag &= ~(ICANON | ECHO);
468
+ tcsetattr(STDIN_FILENO, TCSANOW, &newt);
469
+
470
+ auto redraw = [&]() {
471
+ Utils::UI::clear_line();
472
+ std::cout << prompt << buf;
473
+ std::cout << "\r\033[" << ((int)prompt.size() + cursor) << "C"
474
+ << std::flush;
475
+ };
476
+
477
+ while (true) {
478
+ int ch = std::cin.get();
479
+ if (ch == EOF || ch == '\n' || ch == '\r') {
480
+ break;
481
+ }
482
+ if (ch == 127 || ch == '\b') {
483
+ if (cursor > 0) {
484
+ buf.erase(cursor - 1, 1);
485
+ cursor--;
486
+ redraw();
487
+ }
488
+ } else if (ch == '\033') {
489
+ if (std::cin.get() == '[') {
490
+ int c = std::cin.get();
491
+ if (c == 'A' && history_index > 0) {
492
+ if (history_index == (int)history.size()) {
493
+ saved = buf;
494
+ }
495
+ history_index--;
496
+ buf = history[history_index];
497
+ cursor = (int)buf.size();
498
+ redraw();
499
+ } else if (c == 'B' && history_index < (int)history.size()) {
500
+ history_index++;
501
+ buf = history_index == (int)history.size() ? saved
502
+ : history[history_index];
503
+ cursor = (int)buf.size();
504
+ redraw();
505
+ } else if (c == 'D' && cursor > 0) {
506
+ cursor--;
507
+ redraw();
508
+ } else if (c == 'C' && cursor < (int)buf.size()) {
509
+ cursor++;
510
+ redraw();
511
+ }
512
+ }
513
+ } else if (ch >= 32 && ch < 127) {
514
+ buf.insert(cursor, 1, (char)ch);
515
+ cursor++;
516
+ redraw();
517
+ }
518
+ }
519
+
520
+ tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
521
+ std::cout << "\n";
522
+ return buf;
523
+ #endif
524
+ #endif
525
+ }
526
+
527
+ std::string Agent::format_message(const std::string &sender,
528
+ const std::string &content) {
529
+ std::string color;
530
+ if (sender == "Cursor")
531
+ color = Utils::Color::GREEN;
532
+ else
533
+ color = Utils::Color::CYAN;
534
+ return Utils::Color::GREEN + "| " + Utils::Color::RESET + Utils::Color::BOLD +
535
+ sender + Utils::Color::RESET + "\n" + color + "| " +
536
+ Utils::Color::RESET + content;
537
+ }
538
+
59
539
  void Agent::run() {
60
540
  initialize_mode();
61
541
 
62
- // Data-driven model name mapping
63
- // Data-driven model name mapping
64
- std::map<Agent::Mode, std::string> model_names = {
65
- {Agent::Mode::MODE_TOGETHER, "Together AI"},
66
- {Agent::Mode::MODE_CEREBRAS, "Cerebras"},
67
- {Agent::Mode::MODE_FIREWORKS, "Fireworks"},
68
- {Agent::Mode::MODE_GROQ, "Groq"},
69
- {Agent::Mode::MODE_DEEPSEEK, "DeepSeek"},
70
- {Agent::Mode::MODE_OPENAI, "OpenAI"},
71
- {Agent::Mode::MODE_LLAMA_3B, "llama3.2:3b"},
72
- {Agent::Mode::MODE_LLAMA_LATEST, "llama3.2:latest"},
73
- {Agent::Mode::MODE_LLAMA_31, "llama3.1:latest"}};
74
-
75
- // Show enhanced ready interface with system info and quick help
76
542
  std::string mode_name = is_online_mode() ? "Online" : "Offline";
77
- std::string model_name =
78
- model_names.count(mode_) ? model_names[mode_] : "Unknown";
79
- Utils::UI::print_ready_interface(mode_name, model_name);
543
+ std::string model_name = ollama_model_.empty()
544
+ ? "local"
545
+ : ollama_model_;
546
+ bool tty = is_tty_stream(stdout) && is_tty_stream(stdin);
80
547
 
548
+ if (tty) {
549
+ Utils::UI::print_ready_interface(mode_name, model_name);
550
+ } else {
551
+ Utils::UI::print_ready_interface(mode_name, model_name);
552
+ }
553
+
554
+ std::vector<std::string> input_history;
81
555
  while (true) {
82
- std::cout << "> ";
83
556
  std::string user_input;
84
- if (!std::getline(std::cin, user_input)) {
85
- break; // EOF or error
557
+ if (tty) {
558
+ std::cout << Utils::Color::DIM
559
+ << "\xE2\x8C\x98P palette :cmd !shell /help /debug"
560
+ << Utils::Color::RESET << "\n";
561
+ user_input = read_prompt("> ", input_history);
562
+ if (std::cin.eof()) {
563
+ break;
564
+ }
565
+ } else {
566
+ std::cout << "> " << std::flush;
567
+ if (!std::getline(std::cin, user_input)) {
568
+ break;
569
+ }
86
570
  }
87
571
 
88
572
  user_input = trim_copy(user_input);
89
573
  if (user_input.empty())
90
574
  continue;
91
575
 
576
+ if (tty) {
577
+ input_history.push_back(user_input);
578
+ }
579
+
92
580
  if (user_input == "exit" || user_input == "quit")
93
581
  break;
94
- if (user_input == "help") {
95
- Utils::UI::print_help();
96
- continue;
97
- }
98
- if (user_input == "version") {
582
+
583
+ // Input bar will be cleared and redrawn by draw_input_bar on next loop
584
+
585
+ if (user_input == "help" || user_input == "?") {
586
+ process_user_input("/help");
587
+ } else if (user_input == "version") {
99
588
  Version::print_version_info();
100
- continue;
101
- }
102
- if (user_input == "update") {
589
+ } else if (user_input == "update") {
103
590
  std::cout << "Checking for updates...\n";
104
591
  std::string latest = Version::check_update();
105
592
  if (latest.empty()) {
@@ -108,101 +595,63 @@ void Agent::run() {
108
595
  } else if (Version::download_and_install(latest)) {
109
596
  std::cout << "Restart cursor to use the new version.\n";
110
597
  }
111
- continue;
598
+ } else {
599
+ process_user_input(user_input);
112
600
  }
113
-
114
- process_user_input(user_input);
115
601
  }
116
602
 
603
+ if (tty) {
604
+ Utils::UI::exit_chat_mode();
605
+ }
117
606
  std::cout << "Goodbye\n";
118
607
  }
119
608
 
120
609
  void Agent::initialize_mode() {
121
- // Data-driven provider configuration
122
- struct ProviderInfo {
123
- int choice;
124
- Mode mode;
125
- std::string env_var;
126
- std::string display_name;
127
- };
128
-
129
- std::vector<ProviderInfo> providers = {
130
- {1, Agent::Mode::MODE_TOGETHER, "TOGETHER_API_KEY", "Together AI"},
131
- {2, Agent::Mode::MODE_CEREBRAS, "CEREBRAS_API_KEY", "Cerebras"},
132
- {3, Agent::Mode::MODE_FIREWORKS, "FIREWORKS_API_KEY", "Fireworks"},
133
- {4, Agent::Mode::MODE_GROQ, "GROQ_API_KEY", "Groq"},
134
- {5, Agent::Mode::MODE_DEEPSEEK, "DEEPSEEK_API_KEY", "DeepSeek"},
135
- {6, Agent::Mode::MODE_OPENAI, "OPENAI_API_KEY", "OpenAI"}};
136
-
137
- // Simplified mode selection with consistent numbering
138
- int choice =
139
- get_user_choice("Mode [1=Online / 2=Offline] (default 2): ", {1, 2}, 2);
140
-
141
- if (choice == 1) {
142
- // Online mode: pick provider
143
- std::string prompt = "Provider [";
144
- for (size_t i = 0; i < providers.size(); ++i) {
145
- if (i > 0)
146
- prompt += " / ";
147
- prompt +=
148
- std::to_string(providers[i].choice) + "=" + providers[i].display_name;
149
- }
150
- prompt += "] (default 1): ";
151
-
152
- std::vector<int> valid_choices;
153
- for (const auto &p : providers)
154
- valid_choices.push_back(p.choice);
155
-
156
- int provider_choice = get_user_choice(prompt, valid_choices, 1);
157
-
158
- // Find and configure the selected provider
159
- for (const auto &provider : providers) {
160
- if (provider.choice == provider_choice) {
161
- mode_ = provider.mode;
162
- api_key_ = Utils::Config::get_env_var(provider.env_var);
163
- if (api_key_.empty()) {
164
- throw std::runtime_error(provider.env_var + " not set");
610
+ std::vector<std::string> modes = {"Online", "Offline"};
611
+ int choice = show_menu("Mode", modes, 1);
612
+
613
+ if (choice == 0) {
614
+ std::vector<std::string> providers = {
615
+ "Together AI", "Cerebras", "Fireworks",
616
+ "Groq", "DeepSeek", "OpenAI"};
617
+ std::vector<Mode> provider_modes = {
618
+ Agent::Mode::MODE_TOGETHER, Agent::Mode::MODE_CEREBRAS,
619
+ Agent::Mode::MODE_FIREWORKS, Agent::Mode::MODE_GROQ,
620
+ Agent::Mode::MODE_DEEPSEEK, Agent::Mode::MODE_OPENAI};
621
+ std::vector<std::string> provider_keys = {
622
+ "TOGETHER_API_KEY", "CEREBRAS_API_KEY", "FIREWORKS_API_KEY",
623
+ "GROQ_API_KEY", "DEEPSEEK_API_KEY", "OPENAI_API_KEY"};
624
+
625
+ int p = show_menu("Provider", providers, 0);
626
+ mode_ = provider_modes[p];
627
+ api_key_ = Utils::Config::get_env_var(provider_keys[p]);
628
+ if (api_key_.empty()) {
629
+ throw std::runtime_error(provider_keys[p] + " not set");
630
+ }
631
+ } else {
632
+ std::vector<std::string> models;
633
+ try {
634
+ auto response = Services::WebService::fetch_url("http://localhost:11434/api/tags");
635
+ if (response.success && !response.content.empty()) {
636
+ auto json = nlohmann::json::parse(response.content);
637
+ if (json.contains("models") && json["models"].is_array()) {
638
+ for (const auto &m : json["models"]) {
639
+ if (m.contains("name")) {
640
+ models.push_back(m["name"]);
641
+ }
642
+ }
165
643
  }
166
- Utils::UI::print_success(provider.display_name);
167
- break;
168
644
  }
645
+ } catch (...) {
169
646
  }
170
- } else {
171
- // Data-driven offline model configuration
172
- struct ModelInfo {
173
- int choice;
174
- Mode mode;
175
- std::string display_name;
176
- };
177
647
 
178
- std::vector<ModelInfo> models = {
179
- {1, Agent::Mode::MODE_LLAMA_3B, "llama3.2:3b"},
180
- {2, Agent::Mode::MODE_LLAMA_LATEST, "llama3.2:latest"},
181
- {3, Agent::Mode::MODE_LLAMA_31, "llama3.1:latest"}};
182
-
183
- // Offline mode: pick model
184
- std::string prompt = "Model [";
185
- for (size_t i = 0; i < models.size(); ++i) {
186
- if (i > 0)
187
- prompt += " / ";
188
- prompt += std::to_string(models[i].choice) + "=" + models[i].display_name;
648
+ if (models.empty()) {
649
+ models = {"llama3.2:3b", "llama3.2:latest", "llama3.1:latest"};
189
650
  }
190
- prompt += "] (default 1): ";
191
-
192
- std::vector<int> valid_choices;
193
- for (const auto &m : models)
194
- valid_choices.push_back(m.choice);
195
651
 
196
- int model_choice = get_user_choice(prompt, valid_choices, 1);
197
-
198
- // Find and configure the selected model
199
- for (const auto &model : models) {
200
- if (model.choice == model_choice) {
201
- mode_ = model.mode;
202
- Utils::UI::print_success(model.display_name);
203
- break;
204
- }
205
- }
652
+ int m = show_menu("Model", models, 0);
653
+ mode_ = Mode::MODE_LLAMA_3B;
654
+ ollama_model_ = models[m];
206
655
  }
207
656
  }
208
657
 
@@ -215,64 +664,131 @@ bool Agent::is_online_mode() const {
215
664
  mode_ == Agent::Mode::MODE_OPENAI;
216
665
  }
217
666
 
218
- int Agent::get_user_choice(const std::string &prompt,
219
- const std::vector<int> &valid_choices,
220
- int default_choice) {
221
- while (true) {
222
- std::cout << prompt;
223
- std::string input;
224
- if (!std::getline(std::cin, input)) {
225
- // EOF -> return default
226
- return default_choice;
227
- }
667
+ int Agent::show_menu(const std::string &title,
668
+ const std::vector<std::string> &items,
669
+ int default_index) {
670
+ bool tty = is_tty_stream(stdin) && is_tty_stream(stdout);
671
+ if (!tty || items.empty()) {
672
+ return default_index;
673
+ }
228
674
 
229
- input = trim_copy(input);
675
+ #ifdef _WIN32
676
+ (void)title;
677
+ return default_index;
678
+ #else
679
+ int selected = std::clamp(default_index, 0, (int)items.size() - 1);
230
680
 
231
- // Accept empty line as confirmation of default choice
232
- if (input.empty())
233
- return default_choice;
681
+ std::cout << "\033[?25l";
234
682
 
235
- try {
236
- int choice = std::stoi(input);
237
- if (std::find(valid_choices.begin(), valid_choices.end(), choice) !=
238
- valid_choices.end()) {
239
- return choice;
683
+ struct termios oldt, newt;
684
+ tcgetattr(STDIN_FILENO, &oldt);
685
+ newt = oldt;
686
+ newt.c_lflag &= ~(ICANON | ECHO);
687
+ tcsetattr(STDIN_FILENO, TCSANOW, &newt);
688
+
689
+ std::cout << title << ":\n";
690
+ for (size_t i = 0; i < items.size(); i++) {
691
+ std::cout << (i == (size_t)selected ? "> " : " ") << items[i] << "\n";
692
+ }
693
+
694
+ int ch;
695
+ while (true) {
696
+ ch = std::cin.get();
697
+
698
+ if (ch == '\n' || ch == '\r') {
699
+ break;
700
+ }
701
+
702
+ if (ch == 0x1B && std::cin.get() == '[') {
703
+ int arrow = std::cin.get();
704
+ if (arrow == 'A' && selected > 0) {
705
+ selected--;
706
+ } else if (arrow == 'B' && selected < (int)items.size() - 1) {
707
+ selected++;
708
+ } else {
709
+ continue;
710
+ }
711
+ // Move back to first item line and redraw
712
+ std::cout << "\033[" << items.size() << "A";
713
+ for (size_t i = 0; i < items.size(); i++) {
714
+ std::cout << (i == (size_t)selected ? "> " : " ") << items[i] << "\033[K\n";
240
715
  }
241
- } catch (const std::exception &) {
242
- // fallthrough -> invalid input, prompt again
243
716
  }
244
- Utils::UI::print_warning("Invalid choice, please try again.");
245
717
  }
718
+
719
+ tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
720
+
721
+ // Clear menu and print selection
722
+ std::cout << "\033[" << items.size() << "A\033[J";
723
+ std::cout << Utils::Color::GREEN << title << ": " << Utils::Color::RESET
724
+ << items[selected] << "\n";
725
+ std::cout << "\033[?25h";
726
+
727
+ return selected;
728
+ #endif
246
729
  }
247
730
 
248
- void Agent::process_user_input(const std::string &input) {
731
+ std::string Agent::process_user_input(const std::string &input) {
249
732
  command_count_++;
250
733
  std::string trimmed_input = trim_copy(input);
734
+ show_reasoning_header("INPUT RECEIVED");
735
+ show_pipeline_section("Input classification");
736
+ show_parsed_input(input, trimmed_input);
737
+
738
+ if (shell_mode_ && !trimmed_input.starts_with("!")) {
739
+ show_pipeline_section("Shell mode handling");
740
+ show_reasoning_step("Shell mode", "active");
741
+ show_reasoning_step("Command", "!" + trimmed_input);
742
+ handle_shell_command("!" + trimmed_input);
743
+ return {};
744
+ }
251
745
 
252
746
  // Handle @ file injection commands
253
747
  if (trimmed_input.find('@') != std::string::npos) {
748
+ show_pipeline_section("File injection detection");
749
+ show_reasoning_step("Detected", "file injection");
254
750
  handle_file_injection_command(trimmed_input);
255
- return;
751
+ return {};
256
752
  }
257
753
 
258
754
  // Handle ! shell commands
259
755
  if (trimmed_input.starts_with("!")) {
756
+ show_pipeline_section("Shell command execution");
757
+ show_reasoning_step("Command", trimmed_input);
260
758
  handle_shell_command(trimmed_input);
261
- return;
759
+ return {};
262
760
  }
263
761
 
264
762
  // Handle / meta commands
265
763
  if (trimmed_input.starts_with("/")) {
764
+ show_pipeline_section("Meta command execution");
765
+ show_reasoning_step("Command", trimmed_input);
266
766
  handle_meta_command(trimmed_input);
267
- return;
767
+ return {};
268
768
  }
269
769
 
270
- // Check for direct commands (detect colon)
271
- if (trimmed_input.find(':') != std::string::npos) {
770
+ if (is_direct_command_input(trimmed_input)) {
771
+ show_reasoning_header("DIRECT COMMAND");
772
+ show_pipeline_section("Direct command execution");
773
+ show_parsed_input(trimmed_input, "direct command: " + trimmed_input);
774
+ show_context_state();
272
775
  handle_direct_command(trimmed_input);
273
- } else {
274
- handle_ai_chat(trimmed_input);
776
+ return {};
275
777
  }
778
+ if (auto mapped_command = map_nl_to_direct_command(trimmed_input)) {
779
+ show_reasoning_header("NATURAL LANGUAGE TOOLING");
780
+ show_pipeline_section("Natural language mapping");
781
+ show_reasoning_step("User intent", trimmed_input);
782
+ show_reasoning_step("Mapped to", *mapped_command);
783
+ show_context_state();
784
+ handle_direct_command(*mapped_command);
785
+ return {};
786
+ }
787
+
788
+ show_reasoning_header("AI CHAT");
789
+ show_pipeline_section("AI reasoning flow");
790
+ show_context_state();
791
+ return handle_ai_chat(trimmed_input);
276
792
  }
277
793
 
278
794
  void Agent::handle_direct_command(const std::string &input) {
@@ -311,6 +827,9 @@ void Agent::handle_direct_command(const std::string &input) {
311
827
  }
312
828
  result = Services::CommandService::execute(command);
313
829
  memory_->save_interaction("cmd:" + command, result);
830
+ } else if (input.starts_with("build:")) {
831
+ handle_direct_command("cmd:" + trim_copy(input.substr(6)));
832
+ return;
314
833
  } else if (input.starts_with("read:")) {
315
834
  std::string params = trim_copy(input.substr(5));
316
835
  // Check if it has range parameters: read:filename:start:count
@@ -340,6 +859,7 @@ void Agent::handle_direct_command(const std::string &input) {
340
859
  result = "Error: " + validation.error_message;
341
860
  } else {
342
861
  result = Services::FileService::read_file(params);
862
+ show_file_preview(params, result, 30);
343
863
  }
344
864
  }
345
865
  memory_->save_interaction("read:" + params, result);
@@ -414,10 +934,14 @@ void Agent::handle_direct_command(const std::string &input) {
414
934
  result = "No matches found for pattern: " + pattern;
415
935
  } else {
416
936
  result = "Found " + std::to_string(search_results.size()) + " matches:\n";
937
+ std::vector<std::string> formatted_results;
417
938
  for (const auto &match : search_results) {
418
- result += match.file_path + ":" + std::to_string(match.line_number) +
419
- ": " + match.line_content + "\n";
939
+ std::string line_text = match.file_path + ":" + std::to_string(match.line_number) +
940
+ ": " + match.line_content;
941
+ result += line_text + "\n";
942
+ formatted_results.push_back(line_text);
420
943
  }
944
+ show_search_results(pattern, formatted_results);
421
945
  }
422
946
  memory_->save_interaction("grep:" + pattern, result);
423
947
  } else if (input.starts_with("remember:")) {
@@ -471,6 +995,8 @@ void Agent::handle_direct_command(const std::string &input) {
471
995
  result = Services::GitService::get_git_log(path, 7);
472
996
  } else if (params.find("status") == 0) {
473
997
  result = Services::GitService::get_git_status(path);
998
+ auto files = Services::GitService::get_working_tree_changed_files(path);
999
+ show_git_status_results(files);
474
1000
  } else if (params.find("analyze") == 0) {
475
1001
  result = Services::GitService::analyze_repository(path);
476
1002
  } else {
@@ -538,19 +1064,24 @@ void Agent::handle_direct_command(const std::string &input) {
538
1064
  result = "Unknown command";
539
1065
  }
540
1066
 
1067
+ show_operation_result("Operation complete", result);
541
1068
  if (!result.empty()) {
542
1069
  std::cout << result << std::endl;
543
1070
  }
544
1071
  }
545
1072
 
546
- void Agent::handle_ai_chat(const std::string &input) {
1073
+ std::string Agent::handle_ai_chat(const std::string &input) {
547
1074
  if (!ai_service_) {
548
1075
  ai_service_ = std::make_unique<Services::AIService>(mode_, api_key_);
1076
+ if (!ollama_model_.empty()) {
1077
+ ai_service_->set_model_name(ollama_model_);
1078
+ }
549
1079
  }
550
1080
 
551
1081
  if (!ai_service_->is_available()) {
552
- std::cout << "AI service unavailable\n";
553
- return;
1082
+ std::string msg = "AI service unavailable\n";
1083
+ std::cout << msg;
1084
+ return msg;
554
1085
  }
555
1086
 
556
1087
  std::atomic<bool> done(false);
@@ -566,6 +1097,14 @@ void Agent::handle_ai_chat(const std::string &input) {
566
1097
  full_context = hierarchical_context + "\n\n" + memory_context;
567
1098
  }
568
1099
 
1100
+ std::string agent_context = build_agent_context();
1101
+ if (!agent_context.empty()) {
1102
+ full_context = agent_context + "\n\n" + full_context;
1103
+ }
1104
+
1105
+ std::string system_prompt = "You are an advanced AI agent with comprehensive codebase analysis capabilities.";
1106
+ show_ai_prompt(system_prompt, input);
1107
+
569
1108
  std::string response = ai_service_->chat(input, full_context);
570
1109
 
571
1110
  done = true;
@@ -573,16 +1112,24 @@ void Agent::handle_ai_chat(const std::string &input) {
573
1112
  spin.join();
574
1113
 
575
1114
  if (!response.empty()) {
576
- std::cout << response << std::endl;
1115
+ if (is_tty_stream(stdout)) {
1116
+ std::string formatted_response = render_markdown(response);
1117
+ std::cout << format_message("Cursor", formatted_response) << "\n";
1118
+ } else {
1119
+ std::cout << response << "\n";
1120
+ }
577
1121
  memory_->save_interaction(input, response);
1122
+
1123
+ return response;
578
1124
  } else {
579
1125
  std::cout << "No response\n";
1126
+ return "No response\n";
580
1127
  }
581
1128
  }
582
1129
 
583
1130
  void Agent::handle_file_injection_command(const std::string &input) {
584
- // Process @ file injections and then send to AI
585
1131
  std::string processed_input = process_file_injections(input);
1132
+ // Result is printed by the caller via process_user_input's return
586
1133
  handle_ai_chat(processed_input);
587
1134
  }
588
1135
 
@@ -623,9 +1170,17 @@ void Agent::handle_meta_command(const std::string &input) {
623
1170
  std::string command = trim_copy(input.substr(1));
624
1171
 
625
1172
  if (command == "help" || command == "?") {
626
- show_meta_help();
1173
+ show_agent_documentation();
1174
+ } else if (command == "docs") {
1175
+ show_agent_documentation();
1176
+ } else if (command == "debug") {
1177
+ toggle_verbose_mode();
627
1178
  } else if (command == "clear") {
628
1179
  clear_screen();
1180
+ } else if (command == "goal" || command.starts_with("goal ") ||
1181
+ command == "task" || command.starts_with("task ") ||
1182
+ command == "params" || command.starts_with("params ")) {
1183
+ handle_agentic_command(command);
629
1184
  } else if (command.starts_with("chat ")) {
630
1185
  handle_chat_management(command.substr(5));
631
1186
  } else if (command == "tools") {
@@ -804,9 +1359,20 @@ bool Agent::should_skip_file(const std::string &file_path,
804
1359
 
805
1360
  void Agent::show_meta_help() {
806
1361
  std::cout << "Available meta commands:" << std::endl;
807
- std::cout << " /help or /? - Show this help" << std::endl;
808
- std::cout << " /clear - Clear screen" << std::endl;
809
- std::cout << " /chat save <tag> - Save conversation state" << std::endl;
1362
+ std::cout << " /help or /? - Show this help" << std::endl;
1363
+ std::cout << " /debug - Toggle verbose/debug mode" << std::endl;
1364
+ std::cout << " /clear - Clear screen" << std::endl;
1365
+ std::cout << " /goal set <description> - Set a project goal" << std::endl;
1366
+ std::cout << " /goal show - Show current goal and task status" << std::endl;
1367
+ std::cout << " /goal clear - Clear goal, tasks, and params" << std::endl;
1368
+ std::cout << " /task add <description> - Add a task for the current goal" << std::endl;
1369
+ std::cout << " /task list - List active tasks" << std::endl;
1370
+ std::cout << " /task complete <id> - Mark a task complete" << std::endl;
1371
+ std::cout << " /task remove <id> - Remove a task" << std::endl;
1372
+ std::cout << " /params set key=value - Set goal/task parameters" << std::endl;
1373
+ std::cout << " /params show - Show current parameters" << std::endl;
1374
+ std::cout << " /params clear - Clear current parameters" << std::endl;
1375
+ std::cout << " /chat save <tag> - Save conversation state" << std::endl;
810
1376
  std::cout << " /chat resume <tag> - Resume conversation state"
811
1377
  << std::endl;
812
1378
  std::cout << " /chat list - List saved conversations"
@@ -857,6 +1423,414 @@ void Agent::show_meta_help() {
857
1423
  std::cout << "Shell commands:" << std::endl;
858
1424
  std::cout << " !<command> - Execute shell command" << std::endl;
859
1425
  std::cout << " ! - Toggle shell mode" << std::endl;
1426
+ std::cout << " /docs - Show agent documentation" << std::endl;
1427
+ }
1428
+
1429
+ void Agent::handle_agentic_command(const std::string &command) {
1430
+ if (command == "goal" || command == "goal show") {
1431
+ show_goal();
1432
+ } else if (command.starts_with("goal set ")) {
1433
+ set_goal(trim_copy(command.substr(9)));
1434
+ } else if (command == "goal clear") {
1435
+ clear_goal();
1436
+ } else if (command == "task" || command == "task list") {
1437
+ list_tasks();
1438
+ } else if (command.starts_with("task add ")) {
1439
+ add_task(trim_copy(command.substr(9)));
1440
+ } else if (command.starts_with("task complete ")) {
1441
+ complete_task(trim_copy(command.substr(14)));
1442
+ } else if (command.starts_with("task remove ")) {
1443
+ remove_task(trim_copy(command.substr(12)));
1444
+ } else if (command == "params" || command == "params show") {
1445
+ show_params();
1446
+ } else if (command.starts_with("params set ")) {
1447
+ set_param(trim_copy(command.substr(11)));
1448
+ } else if (command == "params clear") {
1449
+ agent_params_.clear();
1450
+ std::cout << "Cleared agent parameters." << std::endl;
1451
+ } else {
1452
+ show_agentic_help();
1453
+ }
1454
+ }
1455
+
1456
+ void Agent::show_agentic_help() {
1457
+ std::cout << "Agentic workflow commands:" << std::endl;
1458
+ std::cout << " /goal set <description> - Set or update the project goal" << std::endl;
1459
+ std::cout << " /goal show - Show active goal and tasks" << std::endl;
1460
+ std::cout << " /goal clear - Clear the current goal and tasks" << std::endl;
1461
+ std::cout << " /task add <description> - Add a task to the current goal" << std::endl;
1462
+ std::cout << " /task list - List active tasks" << std::endl;
1463
+ std::cout << " /task complete <id> - Mark a task as completed" << std::endl;
1464
+ std::cout << " /task remove <id> - Remove a task" << std::endl;
1465
+ std::cout << " /params set key=value - Set structured goal/task parameters" << std::endl;
1466
+ std::cout << " /params show - Show task parameters" << std::endl;
1467
+ std::cout << " /params clear - Clear current task parameters" << std::endl;
1468
+ }
1469
+
1470
+ void Agent::set_goal(const std::string &goal) {
1471
+ if (goal.empty()) {
1472
+ std::cout << "Usage: /goal set <description>" << std::endl;
1473
+ return;
1474
+ }
1475
+ active_goal_ = goal;
1476
+ tasks_.clear();
1477
+ agent_params_.clear();
1478
+ std::cout << "Goal set: " << active_goal_ << std::endl;
1479
+ memory_->save_interaction("goal:set", active_goal_);
1480
+ }
1481
+
1482
+ void Agent::show_goal() const {
1483
+ if (active_goal_.empty()) {
1484
+ std::cout << "No active goal set." << std::endl;
1485
+ return;
1486
+ }
1487
+ std::cout << "Active goal: " << active_goal_ << std::endl;
1488
+ if (tasks_.empty()) {
1489
+ std::cout << "No tasks added yet." << std::endl;
1490
+ } else {
1491
+ std::cout << "Tasks:" << std::endl;
1492
+ for (const auto &task : tasks_) {
1493
+ std::cout << " [" << (task.completed ? "x" : " ") << "] "
1494
+ << task.id << ": " << task.description << std::endl;
1495
+ }
1496
+ }
1497
+ if (!agent_params_.empty()) {
1498
+ std::cout << "Parameters:" << std::endl;
1499
+ for (const auto &pair : agent_params_) {
1500
+ std::cout << " " << pair.first << " = " << pair.second << std::endl;
1501
+ }
1502
+ }
1503
+ }
1504
+
1505
+ void Agent::clear_goal() {
1506
+ active_goal_.clear();
1507
+ tasks_.clear();
1508
+ agent_params_.clear();
1509
+ std::cout << "Cleared goal, tasks, and parameters." << std::endl;
1510
+ memory_->save_interaction("goal:clear", "Cleared goal and task state");
1511
+ }
1512
+
1513
+ void Agent::add_task(const std::string &task_description) {
1514
+ if (task_description.empty()) {
1515
+ std::cout << "Usage: /task add <description>" << std::endl;
1516
+ return;
1517
+ }
1518
+
1519
+ int task_id = tasks_.empty() ? 1 : tasks_.back().id + 1;
1520
+ tasks_.push_back({task_id, task_description, false});
1521
+ std::cout << "Task added: [" << task_id << "] " << task_description
1522
+ << std::endl;
1523
+ memory_->save_interaction("task:add", task_description);
1524
+ }
1525
+
1526
+ void Agent::list_tasks() const {
1527
+ if (tasks_.empty()) {
1528
+ std::cout << "No active tasks." << std::endl;
1529
+ return;
1530
+ }
1531
+ std::cout << "Active tasks:" << std::endl;
1532
+ for (const auto &task : tasks_) {
1533
+ std::cout << " [" << (task.completed ? "x" : " ") << "] "
1534
+ << task.id << ": " << task.description << std::endl;
1535
+ }
1536
+ }
1537
+
1538
+ void Agent::complete_task(const std::string &args) {
1539
+ if (args.empty()) {
1540
+ std::cout << "Usage: /task complete <id>" << std::endl;
1541
+ return;
1542
+ }
1543
+ try {
1544
+ int task_id = std::stoi(args);
1545
+ for (auto &task : tasks_) {
1546
+ if (task.id == task_id) {
1547
+ task.completed = true;
1548
+ std::cout << "Task completed: [" << task_id << "] "
1549
+ << task.description << std::endl;
1550
+ memory_->save_interaction("task:complete", task.description);
1551
+ return;
1552
+ }
1553
+ }
1554
+ std::cout << "Task not found: " << args << std::endl;
1555
+ } catch (const std::exception &) {
1556
+ std::cout << "Invalid task id: " << args << std::endl;
1557
+ }
1558
+ }
1559
+
1560
+ void Agent::remove_task(const std::string &args) {
1561
+ if (args.empty()) {
1562
+ std::cout << "Usage: /task remove <id>" << std::endl;
1563
+ return;
1564
+ }
1565
+ try {
1566
+ int task_id = std::stoi(args);
1567
+ auto it = std::remove_if(tasks_.begin(), tasks_.end(),
1568
+ [&](const AgentTask &task) {
1569
+ return task.id == task_id;
1570
+ });
1571
+ if (it != tasks_.end()) {
1572
+ std::cout << "Task removed: " << task_id << std::endl;
1573
+ tasks_.erase(it, tasks_.end());
1574
+ memory_->save_interaction("task:remove", std::to_string(task_id));
1575
+ } else {
1576
+ std::cout << "Task not found: " << args << std::endl;
1577
+ }
1578
+ } catch (const std::exception &) {
1579
+ std::cout << "Invalid task id: " << args << std::endl;
1580
+ }
1581
+ }
1582
+
1583
+ void Agent::set_param(const std::string &param_string) {
1584
+ size_t equals_pos = param_string.find('=');
1585
+ if (equals_pos == std::string::npos) {
1586
+ std::cout << "Usage: /params set key=value" << std::endl;
1587
+ return;
1588
+ }
1589
+ std::string key = trim_copy(param_string.substr(0, equals_pos));
1590
+ std::string value = trim_copy(param_string.substr(equals_pos + 1));
1591
+ if (key.empty() || value.empty()) {
1592
+ std::cout << "Usage: /params set key=value" << std::endl;
1593
+ return;
1594
+ }
1595
+ agent_params_[key] = value;
1596
+ std::cout << "Parameter set: " << key << " = " << value << std::endl;
1597
+ memory_->save_interaction("params:set", key + "=" + value);
1598
+ }
1599
+
1600
+ void Agent::show_params() const {
1601
+ if (agent_params_.empty()) {
1602
+ std::cout << "No parameters set." << std::endl;
1603
+ return;
1604
+ }
1605
+ std::cout << "Agent parameters:" << std::endl;
1606
+ for (const auto &pair : agent_params_) {
1607
+ std::cout << " " << pair.first << " = " << pair.second << std::endl;
1608
+ }
1609
+ }
1610
+
1611
+ std::string Agent::build_agent_context() const {
1612
+ if (active_goal_.empty() && tasks_.empty() && agent_params_.empty()) {
1613
+ return "";
1614
+ }
1615
+
1616
+ std::ostringstream context;
1617
+ if (!active_goal_.empty()) {
1618
+ context << "Current objective: " << active_goal_ << "\n";
1619
+ }
1620
+ if (!tasks_.empty()) {
1621
+ context << "Tasks:\n";
1622
+ for (const auto &task : tasks_) {
1623
+ context << "- [" << (task.completed ? "x" : " ") << "] "
1624
+ << task.id << ": " << task.description << "\n";
1625
+ }
1626
+ }
1627
+ if (!agent_params_.empty()) {
1628
+ context << "Parameters:\n";
1629
+ for (const auto &pair : agent_params_) {
1630
+ context << "- " << pair.first << " = " << pair.second << "\n";
1631
+ }
1632
+ }
1633
+ context << "Use this goal, task list, and parameters to guide your next actions.";
1634
+ return context.str();
1635
+ }
1636
+
1637
+ void Agent::show_agent_documentation() {
1638
+ std::string help_path = "AGENTS.md";
1639
+ std::string doc = Services::FileService::read_file(help_path);
1640
+ if (doc.starts_with("Error:")) {
1641
+ std::filesystem::path current = std::filesystem::current_path();
1642
+ while (true) {
1643
+ std::filesystem::path candidate = current / "AGENTS.md";
1644
+ if (Services::FileService::file_exists(candidate.string())) {
1645
+ help_path = candidate.string();
1646
+ doc = Services::FileService::read_file(help_path);
1647
+ break;
1648
+ }
1649
+ if (!current.has_parent_path() || current == current.parent_path()) {
1650
+ break;
1651
+ }
1652
+ current = current.parent_path();
1653
+ }
1654
+ }
1655
+
1656
+ if (doc.starts_with("Error:")) {
1657
+ std::cout << "Unable to load runtime help from AGENTS.md: " << doc
1658
+ << std::endl;
1659
+ std::cout << "Falling back to built-in help.\n";
1660
+ show_meta_help();
1661
+ return;
1662
+ }
1663
+
1664
+ std::cout << "Runtime help from AGENTS.md:\n" << doc << std::endl;
1665
+ }
1666
+
1667
+ void Agent::toggle_verbose_mode() {
1668
+ verbose_mode_ = !verbose_mode_;
1669
+ std::cout << (verbose_mode_ ? Utils::Color::GREEN : Utils::Color::YELLOW)
1670
+ << (verbose_mode_ ? "[debug] Verbose mode ON"
1671
+ : "[debug] Verbose mode OFF")
1672
+ << Utils::Color::RESET << std::endl;
1673
+ }
1674
+
1675
+ void Agent::show_reasoning_header(const std::string &operation_type) {
1676
+ if (!verbose_mode_)
1677
+ return;
1678
+ std::cout << "\n" << Utils::Color::CYAN << "=== " << Utils::Color::BOLD
1679
+ << operation_type << Utils::Color::RESET << Utils::Color::CYAN
1680
+ << " ===" << Utils::Color::RESET << std::endl;
1681
+ }
1682
+
1683
+ void Agent::show_pipeline_section(const std::string &section_title) {
1684
+ if (!verbose_mode_)
1685
+ return;
1686
+ std::cout << "\n" << Utils::Color::BOLD << Utils::Color::CYAN << "[ "
1687
+ << section_title << " ]" << Utils::Color::RESET << std::endl;
1688
+ }
1689
+
1690
+ void Agent::show_reasoning_step(const std::string &label,
1691
+ const std::string &detail) {
1692
+ if (!verbose_mode_)
1693
+ return;
1694
+ std::cout << Utils::Color::CYAN << " - " << Utils::Color::RESET << label;
1695
+ if (!detail.empty()) {
1696
+ std::cout << ": " << Utils::Color::YELLOW << detail << Utils::Color::RESET;
1697
+ }
1698
+ std::cout << std::endl;
1699
+ }
1700
+
1701
+ void Agent::show_parsed_input(const std::string &input,
1702
+ const std::string &parsed_as) {
1703
+ if (!verbose_mode_)
1704
+ return;
1705
+ show_reasoning_step("Heard", input);
1706
+ show_reasoning_step("Parsed as", parsed_as);
1707
+ }
1708
+
1709
+ void Agent::show_context_state() {
1710
+ if (!verbose_mode_)
1711
+ return;
1712
+ std::cout << Utils::Color::PINK << "Agent Context:" << Utils::Color::RESET
1713
+ << std::endl;
1714
+ if (!active_goal_.empty()) {
1715
+ std::cout << " Goal: " << active_goal_ << std::endl;
1716
+ }
1717
+ if (!tasks_.empty()) {
1718
+ std::cout << " Tasks: " << tasks_.size() << " active";
1719
+ int completed = 0;
1720
+ for (const auto &t : tasks_) {
1721
+ if (t.completed) completed++;
1722
+ }
1723
+ if (completed > 0)
1724
+ std::cout << " (" << completed << " completed)";
1725
+ std::cout << std::endl;
1726
+ }
1727
+ if (!agent_params_.empty()) {
1728
+ std::cout << " Params: " << agent_params_.size() << " set" << std::endl;
1729
+ }
1730
+ }
1731
+
1732
+ void Agent::show_ai_prompt(const std::string &system_prompt,
1733
+ const std::string &user_input) {
1734
+ if (!verbose_mode_)
1735
+ return;
1736
+ show_pipeline_section("AI prompt construction");
1737
+ show_reasoning_step("System prompt length",
1738
+ std::to_string(system_prompt.length()) + " chars");
1739
+ show_reasoning_step("User input preview",
1740
+ user_input.length() > 100
1741
+ ? user_input.substr(0, 100) + "..."
1742
+ : user_input);
1743
+ }
1744
+
1745
+ void Agent::show_operation_result(const std::string &operation,
1746
+ const std::string &result) {
1747
+ if (!verbose_mode_)
1748
+ return;
1749
+ std::cout << Utils::Color::GREEN << "[ok] " << operation << ": "
1750
+ << Utils::Color::RESET;
1751
+ if (result.length() > 150) {
1752
+ std::cout << result.substr(0, 150) << "..." << std::endl;
1753
+ } else {
1754
+ std::cout << result << std::endl;
1755
+ }
1756
+ }
1757
+
1758
+ void Agent::show_search_results(const std::string &query,
1759
+ const std::vector<std::string> &results) {
1760
+ if (!verbose_mode_ || results.empty())
1761
+ return;
1762
+ std::cout << "\n" << Utils::Color::GREEN << "Search results for '"
1763
+ << query << "' (" << results.size() << " matches)"
1764
+ << Utils::Color::RESET << std::endl;
1765
+ int line_num = 1;
1766
+ for (const auto &result : results) {
1767
+ std::cout << Utils::Color::CYAN << std::setw(4) << std::right << line_num
1768
+ << Utils::Color::RESET << ": " << result << std::endl;
1769
+ line_num++;
1770
+ }
1771
+ }
1772
+
1773
+ void Agent::show_git_status_results(const std::vector<std::string> &files) {
1774
+ if (!verbose_mode_)
1775
+ return;
1776
+ if (files.empty()) {
1777
+ std::cout << Utils::Color::GREEN << "[ok] Working directory clean" << Utils::Color::RESET << std::endl;
1778
+ return;
1779
+ }
1780
+ std::cout << "\n" << Utils::Color::YELLOW << "Modified / Untracked files (" << files.size() << ")" << Utils::Color::RESET << std::endl;
1781
+ int i = 1;
1782
+ for (const auto &f : files) {
1783
+ std::cout << Utils::Color::PINK << std::setw(3) << i << Utils::Color::RESET << " " << f << std::endl;
1784
+ i++;
1785
+ }
1786
+ }
1787
+
1788
+ static std::string detect_language_from_filename(const std::string &filename) {
1789
+ auto pos = filename.find_last_of('.');
1790
+ if (pos == std::string::npos)
1791
+ return "";
1792
+ std::string ext = filename.substr(pos + 1);
1793
+ if (ext == "cpp" || ext == "cc" || ext == "cxx" || ext == "c")
1794
+ return "cpp";
1795
+ if (ext == "h" || ext == "hpp" || ext == "hh")
1796
+ return "cpp";
1797
+ if (ext == "py")
1798
+ return "python";
1799
+ if (ext == "js" || ext == "ts")
1800
+ return "javascript";
1801
+ if (ext == "md")
1802
+ return "markdown";
1803
+ if (ext == "html" || ext == "htm")
1804
+ return "html";
1805
+ return "";
1806
+ }
1807
+
1808
+ void Agent::show_file_preview(const std::string &filename, const std::string &content, int max_lines) {
1809
+ if (!verbose_mode_)
1810
+ return;
1811
+ if (content.rfind("Error:", 0) == 0) {
1812
+ // content starts with Error:
1813
+ std::cout << Utils::Color::RED << content << Utils::Color::RESET << std::endl;
1814
+ return;
1815
+ }
1816
+ std::string language = detect_language_from_filename(filename);
1817
+ std::istringstream in(content);
1818
+ std::string line;
1819
+ int line_no = 1;
1820
+ std::cout << "\n" << Utils::Color::GREEN << filename << Utils::Color::RESET << std::endl;
1821
+ while (line_no <= max_lines && std::getline(in, line)) {
1822
+ std::ostringstream pref;
1823
+ pref << Utils::Color::CYAN << std::setw(4) << std::right << line_no << Utils::Color::RESET << ": ";
1824
+ if (!language.empty()) {
1825
+ std::cout << pref.str() << highlight_code_line(line, language) << std::endl;
1826
+ } else {
1827
+ std::cout << pref.str() << replace_inline_markdown(line) << std::endl;
1828
+ }
1829
+ line_no++;
1830
+ }
1831
+ if (in && !in.eof()) {
1832
+ std::cout << Utils::Color::DIM << "... (file truncated)" << Utils::Color::RESET << std::endl;
1833
+ }
860
1834
  }
861
1835
 
862
1836
  void Agent::clear_screen() {
@@ -902,15 +1876,13 @@ void Agent::handle_chat_management(const std::string &command) {
902
1876
  void Agent::show_available_tools() {
903
1877
  std::cout << "Available tools:" << std::endl;
904
1878
  std::cout << " File Operations:" << std::endl;
905
- std::cout << " read:file[:start:count] - Read file content"
906
- << std::endl;
1879
+ std::cout << " read:file[:start:count] - Read file content" << std::endl;
907
1880
  std::cout << " write:file content - Write to file" << std::endl;
908
- std::cout << " replace:file:old:new - Replace text in file"
909
- << std::endl;
1881
+ std::cout << " replace:file:old:new - Replace text in file" << std::endl;
910
1882
  std::cout << " grep:pattern[:dir[:ext]] - Search in files" << std::endl;
1883
+ std::cout << " build:command - Execute build or shell commands" << std::endl;
911
1884
  std::cout << " Project Analysis:" << std::endl;
912
- std::cout << " analyze:[path] - Analyze project structure"
913
- << std::endl;
1885
+ std::cout << " analyze:[path] - Analyze project structure" << std::endl;
914
1886
  std::cout << " components:[path] - Find main components"
915
1887
  << std::endl;
916
1888
  std::cout << " todos:[path] - Find task comments"