@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/main.cpp CHANGED
@@ -62,7 +62,10 @@ int main(int argc, char *argv[]) {
62
62
  Utils::Config::load_environment();
63
63
  Utils::UI::print_logo();
64
64
 
65
- std::string latest = Version::check_update();
65
+ std::string latest;
66
+ if (!Utils::Config::has_env_var("CURSOR_SKIP_UPDATE_CHECK")) {
67
+ latest = Version::check_update();
68
+ }
66
69
  if (!latest.empty()) {
67
70
  std::cout << Utils::Color::YELLOW
68
71
  << "Update available: v" << Version::get_version() << " -> v"
@@ -166,34 +166,18 @@ nlohmann::json AIService::create_payload(const std::string &user_input,
166
166
  return create_standard_payload("deepseek-chat", user_input, system_prompt);
167
167
  case Core::AgentMode::MODE_OPENAI: // OpenAI
168
168
  return create_standard_payload("gpt-4", user_input, system_prompt);
169
- case Core::AgentMode::MODE_LLAMA_3B: // Llama 3B (local)
170
- return {{"model", "llama3.2:3b"},
171
- {"stream", false},
172
- {"messages",
173
- {{{"role", "system"}, {"content", system_prompt}},
174
- {{"role", "user"}, {"content", user_input}}}}};
175
-
176
- case Core::AgentMode::MODE_LLAMA_LATEST: // Llama latest (local)
177
- return {{"model", "llama3.2:latest"},
178
- {"stream", false},
179
- {"messages",
180
- {{{"role", "system"}, {"content", system_prompt}},
181
- {{"role", "user"}, {"content", user_input}}}}};
182
-
183
- case Core::AgentMode::MODE_LLAMA_31: // Llama 3.1 (local)
184
- return {{"model", "llama3.1:latest"},
185
- {"stream", false},
186
- {"messages",
187
- {{{"role", "system"}, {"content", system_prompt}},
188
- {{"role", "user"}, {"content", user_input}}}}};
189
-
190
- default: // Fallback to Llama 3B
191
- return {{"model", "llama3.2:3b"},
169
+ case Core::AgentMode::MODE_LLAMA_3B: // Local Ollama models
170
+ case Core::AgentMode::MODE_LLAMA_LATEST:
171
+ case Core::AgentMode::MODE_LLAMA_31:
172
+ default: {
173
+ std::string model = model_name_.empty() ? "llama3.2:3b" : model_name_;
174
+ return {{"model", model},
192
175
  {"stream", false},
193
176
  {"messages",
194
177
  {{{"role", "system"}, {"content", system_prompt}},
195
178
  {{"role", "user"}, {"content", user_input}}}}};
196
179
  }
180
+ }
197
181
  }
198
182
 
199
183
  std::string AIService::parse_cerebras_stream(const std::string &response) {
@@ -2,6 +2,7 @@
2
2
  #include "utils/platform.h"
3
3
  #include "utils/validation.h"
4
4
  #include <filesystem>
5
+ #include <unordered_set>
5
6
  #include <fstream>
6
7
  #include <regex>
7
8
  #include <sstream>
@@ -67,6 +68,50 @@ std::string GitService::get_git_status(const std::string &path) {
67
68
  return result.empty() ? "Working directory clean" : result;
68
69
  }
69
70
 
71
+ std::vector<std::string>
72
+ GitService::get_working_tree_changed_files(const std::string &path) {
73
+ std::vector<std::string> files;
74
+
75
+ if (!is_git_repository(path)) {
76
+ return files;
77
+ }
78
+
79
+ auto collect_files = [&](const std::string &command) {
80
+ FILE *pipe = Utils::Platform::open_process(command, "r");
81
+ if (!pipe) {
82
+ return;
83
+ }
84
+
85
+ char buffer[256];
86
+ while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
87
+ std::string file(buffer);
88
+ if (!file.empty() && file.back() == '\n') {
89
+ file.pop_back();
90
+ }
91
+ if (!file.empty()) {
92
+ files.push_back(file);
93
+ }
94
+ }
95
+ Utils::Platform::close_process(pipe);
96
+ };
97
+
98
+ std::string base = "cd \"" + path + "\" && ";
99
+ collect_files(base + "git diff --name-only --cached");
100
+ collect_files(base + "git diff --name-only");
101
+ collect_files(base + "git ls-files --others --exclude-standard");
102
+
103
+ std::vector<std::string> unique_files;
104
+ std::unordered_set<std::string> seen;
105
+ for (const auto &file : files) {
106
+ if (!seen.count(file)) {
107
+ seen.insert(file);
108
+ unique_files.push_back(file);
109
+ }
110
+ }
111
+
112
+ return unique_files;
113
+ }
114
+
70
115
  std::vector<std::string> GitService::get_changed_files(const std::string &path,
71
116
  int days) {
72
117
  std::vector<std::string> files;
@@ -87,7 +132,6 @@ std::vector<std::string> GitService::get_changed_files(const std::string &path,
87
132
  char buffer[256];
88
133
  while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
89
134
  std::string file(buffer);
90
- // Remove newline
91
135
  if (!file.empty() && file.back() == '\n') {
92
136
  file.pop_back();
93
137
  }
@@ -155,9 +155,13 @@ bool WebService::is_valid_url(const std::string &url) {
155
155
  if (url.find("http://") == 0 || url.find("https://") == 0) {
156
156
  // Must have at least a domain after protocol
157
157
  size_t domain_start = url.find("://") + 3;
158
- if (domain_start < url.length() &&
159
- url.find('.', domain_start) != std::string::npos) {
160
- return true;
158
+ if (domain_start < url.length()) {
159
+ std::string domain = url.substr(domain_start);
160
+ // Allow localhost without dot
161
+ if (domain.starts_with("localhost") || domain.starts_with("127.") ||
162
+ url.find('.', domain_start) != std::string::npos) {
163
+ return true;
164
+ }
161
165
  }
162
166
  }
163
167
  return false;
package/src/utils/ui.cpp CHANGED
@@ -4,6 +4,12 @@
4
4
  #include <iostream>
5
5
  #include <string>
6
6
  #include <thread>
7
+ #ifdef _WIN32
8
+ #include <windows.h>
9
+ #else
10
+ #include <sys/ioctl.h>
11
+ #include <unistd.h>
12
+ #endif
7
13
 
8
14
  namespace Utils {
9
15
 
@@ -286,22 +292,102 @@ void UI::print_system_info(const std::string &mode, const std::string &model) {
286
292
  // ===== Ready Interface =====
287
293
  void UI::print_ready_interface(const std::string &mode,
288
294
  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
+ std::cout << Color::DIM << "[" << mode << " | " << model << "]" << Color::RESET << "\n";
295
296
  }
296
297
 
297
298
  // ===== Prompted Input =====
298
299
  std::string UI::prompt_user(const std::string &prompt_text) {
299
- // Only print the prompt once with consistent formatting
300
300
  std::cout << Color::BOLD << "[You] " << prompt_text << Color::RESET << " > ";
301
-
302
301
  std::string input;
303
302
  std::getline(std::cin, input);
304
303
  return input;
305
304
  }
306
305
 
306
+ // ===== Terminal Management =====
307
+ int UI::get_terminal_width() {
308
+ #ifndef _WIN32
309
+ struct winsize w;
310
+ if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) == 0 && w.ws_col > 0)
311
+ return w.ws_col;
312
+ #endif
313
+ return 80;
314
+ }
315
+
316
+ int UI::get_terminal_height() {
317
+ #ifndef _WIN32
318
+ struct winsize w;
319
+ if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) == 0 && w.ws_row > 0)
320
+ return w.ws_row;
321
+ #endif
322
+ return 24;
323
+ }
324
+
325
+ void UI::clear_screen() {
326
+ std::cout << "\033[2J\033[H";
327
+ }
328
+
329
+ void UI::enter_chat_mode() {
330
+ clear_screen();
331
+ }
332
+
333
+ void UI::exit_chat_mode() {
334
+ std::cout << "\033[?25h";
335
+ }
336
+
337
+ void UI::clear_line() {
338
+ std::cout << "\033[2K\r";
339
+ }
340
+
341
+ void UI::cursor_to(int line, int col) {
342
+ std::cout << "\033[" << line << ";" << col << "H";
343
+ }
344
+
345
+ void UI::draw_status_line(const std::string &mode,
346
+ const std::string &model,
347
+ const std::string &dir) {
348
+ cursor_to(1, 1);
349
+ clear_line();
350
+ std::cout << Color::DIM << "* " << Color::RESET
351
+ << mode << " "
352
+ << Color::BOLD << model << Color::RESET
353
+ << " " << Color::DIM << dir << Color::RESET;
354
+ std::cout.flush();
355
+ }
356
+
357
+ void UI::draw_context_line(const std::string &hints) {
358
+ int h = get_terminal_height();
359
+ cursor_to(h - 2, 1);
360
+ clear_line();
361
+ if (!hints.empty()) {
362
+ std::cout << Color::DIM << hints << Color::RESET;
363
+ }
364
+ std::cout.flush();
365
+ }
366
+
367
+ void UI::draw_input_bar(const std::string &text, int cursor_pos) {
368
+ int h = get_terminal_height();
369
+ cursor_to(h - 1, 1);
370
+ clear_line();
371
+ std::cout << Color::CYAN << "> " << Color::RESET;
372
+ if (text.empty()) {
373
+ std::cout << Color::DIM << "Ask anything..." << Color::RESET;
374
+ cursor_to(h - 1, 3);
375
+ } else {
376
+ std::cout << text;
377
+ int pos = (cursor_pos < 0) ? (int)text.size() : cursor_pos;
378
+ cursor_to(h - 1, 3 + pos);
379
+ }
380
+ std::cout.flush();
381
+ }
382
+
383
+ void UI::enable_mouse() {
384
+ std::cout << "\033[?1000h\033[?1006h";
385
+ std::cout.flush();
386
+ }
387
+
388
+ void UI::disable_mouse() {
389
+ std::cout << "\033[?1006l\033[?1000l";
390
+ std::cout.flush();
391
+ }
392
+
307
393
  } // namespace Utils
@@ -22,13 +22,13 @@ size_t write_to_file(void *ptr, size_t size, size_t nmemb, void *userdata) {
22
22
  return fwrite(ptr, size, nmemb, file);
23
23
  }
24
24
 
25
- std::string get_platform_binary_name() {
25
+ std::pair<std::string, std::string> get_release_asset_names(const std::string &version) {
26
26
  #ifdef _WIN32
27
- return "cursor-windows.exe";
27
+ return {"cursor_v" + version + "_windows_amd64.zip", "cursor-windows.exe"};
28
28
  #elif defined(__APPLE__)
29
- return "cursor-macos";
29
+ return {"cursor_v" + version + "_darwin_arm64.tar.gz", "cursor-macos"};
30
30
  #else
31
- return "cursor-linux";
31
+ return {"cursor_v" + version + "_linux_amd64.tar.gz", "cursor-linux"};
32
32
  #endif
33
33
  }
34
34
 
@@ -103,14 +103,15 @@ std::string check_update() {
103
103
  }
104
104
 
105
105
  bool download_and_install(const std::string &version) {
106
- std::string binary_name = get_platform_binary_name();
106
+ auto [archive, binary_name] = get_release_asset_names(version);
107
107
  std::string url =
108
- "https://github.com/bniladridas/cursor/releases/download/" + version +
109
- "/" + binary_name;
108
+ "https://github.com/bniladridas/cursor/releases/download/v" + version +
109
+ "/" + archive;
110
110
 
111
111
  std::string tmp = "/tmp/cursor-update-" + version;
112
+ std::string tmpDir = "/tmp/cursor-update-extract-" + version;
112
113
  #ifdef _WIN32
113
- tmp += ".exe";
114
+ tmp += ".zip";
114
115
  #endif
115
116
 
116
117
  std::cout << "Downloading v" << version << "...\n";
@@ -139,12 +140,6 @@ bool download_and_install(const std::string &version) {
139
140
  return false;
140
141
  }
141
142
 
142
- std::filesystem::permissions(tmp,
143
- std::filesystem::perms::owner_exec |
144
- std::filesystem::perms::owner_read |
145
- std::filesystem::perms::owner_write,
146
- std::filesystem::perm_options::add);
147
-
148
143
  std::string exe = get_exe_path();
149
144
  if (exe.empty()) {
150
145
  std::cerr << "Downloaded to: " << tmp << "\nManually replace binary.\n";
@@ -160,14 +155,52 @@ bool download_and_install(const std::string &version) {
160
155
  return false;
161
156
  }
162
157
 
163
- std::filesystem::rename(tmp, exe, ec);
158
+ std::filesystem::create_directories(tmpDir, ec);
159
+ if (ec) {
160
+ std::cerr << "Failed to create extract dir: " << ec.message() << "\n";
161
+ std::filesystem::rename(backup, exe);
162
+ std::filesystem::remove(tmp);
163
+ return false;
164
+ }
165
+
166
+ {
167
+ #ifdef _WIN32
168
+ std::string cmd =
169
+ "powershell -NoProfile -Command \"Expand-Archive -LiteralPath '" +
170
+ tmp + "' -DestinationPath '" + tmpDir + "' -Force\"";
171
+ #else
172
+ std::string cmd = "tar -xzf \"" + tmp + "\" -C \"" + tmpDir + "\"";
173
+ #endif
174
+ int rc = std::system(cmd.c_str());
175
+ if (rc != 0) {
176
+ std::cerr << "Extract failed\n";
177
+ std::filesystem::rename(backup, exe);
178
+ std::filesystem::remove(tmp);
179
+ std::filesystem::remove_all(tmpDir);
180
+ return false;
181
+ }
182
+ }
183
+
184
+ std::string extracted = tmpDir + "/" + binary_name;
185
+ if (!std::filesystem::exists(extracted)) {
186
+ std::cerr << "Extracted binary not found: " << extracted << "\n";
187
+ std::filesystem::rename(backup, exe);
188
+ std::filesystem::remove(tmp);
189
+ std::filesystem::remove_all(tmpDir);
190
+ return false;
191
+ }
192
+
193
+ std::filesystem::rename(extracted, exe, ec);
164
194
  if (ec) {
165
195
  std::cerr << "Failed to install: " << ec.message() << "\n";
166
196
  std::filesystem::rename(backup, exe);
167
197
  std::filesystem::remove(tmp);
198
+ std::filesystem::remove_all(tmpDir);
168
199
  return false;
169
200
  }
170
201
 
202
+ std::filesystem::remove(tmp);
203
+ std::filesystem::remove_all(tmpDir);
171
204
  std::cout << "Updated to v" << version << " successfully!\n";
172
205
  return true;
173
206
  }
@@ -0,0 +1,131 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ repo_root="$(cd "$script_dir/../.." && pwd)"
6
+
7
+ binary_path="${BINARY_PATH:-${1:-}}"
8
+ if [[ -z "$binary_path" ]]; then
9
+ for candidate in \
10
+ "$repo_root/build/bin/cursor-agent" \
11
+ "$repo_root/build/bin/Release/cursor-agent.exe" \
12
+ "$repo_root/build/bin/cursor-agent.exe"; do
13
+ if [[ -x "$candidate" ]]; then
14
+ binary_path="$candidate"
15
+ break
16
+ fi
17
+ done
18
+ fi
19
+
20
+ if [[ -z "$binary_path" ]]; then
21
+ echo "Usage: BINARY_PATH=/path/to/cursor-agent $0 [binary-path]" >&2
22
+ echo "Could not find a built cursor-agent under $repo_root/build/bin." >&2
23
+ exit 2
24
+ fi
25
+
26
+ if [[ ! -x "$binary_path" ]]; then
27
+ echo "Binary is not executable: $binary_path" >&2
28
+ exit 2
29
+ fi
30
+ binary_path="$(cd "$(dirname "$binary_path")" && pwd)/$(basename "$binary_path")"
31
+
32
+ workdir="$(mktemp -d)"
33
+ mkdir -p "$workdir/home"
34
+ cleanup() {
35
+ rm -rf "$workdir"
36
+ }
37
+ trap cleanup EXIT
38
+
39
+ input_file="$workdir/input.txt"
40
+ output_file="$workdir/output.txt"
41
+
42
+ cat >"$input_file" <<'EOF'
43
+ help
44
+ version
45
+ write:e2e.txt alpha beta
46
+ read:e2e.txt
47
+ replace:e2e.txt:alpha:omega:1
48
+ read:e2e.txt
49
+ grep:omega:.:e2e.txt
50
+ remember:e2e-memory-fact
51
+ memory
52
+ clear
53
+ cmd:echo e2e-cmd-ok
54
+ !echo e2e-bang-ok
55
+ !
56
+ echo e2e-shell-mode-ok
57
+ !
58
+ /help
59
+ /stats
60
+ exit
61
+ EOF
62
+
63
+ set +e
64
+ bash -c 'cd "$1" && HOME="$1/home" CURSOR_SKIP_UPDATE_CHECK=1 "$2" <"$3" >"$4" 2>&1' bash "$workdir" "$binary_path" "$input_file" "$output_file" &
65
+ agent_pid=$!
66
+ deadline=$((SECONDS + 20))
67
+ while kill -0 "$agent_pid" 2>/dev/null; do
68
+ if ((SECONDS >= deadline)); then
69
+ kill "$agent_pid" 2>/dev/null || true
70
+ wait "$agent_pid" 2>/dev/null
71
+ run_status=124
72
+ break
73
+ fi
74
+ sleep 1
75
+ done
76
+ if [[ -z "${run_status:-}" ]]; then
77
+ wait "$agent_pid"
78
+ run_status=$?
79
+ fi
80
+ set -e
81
+ if [[ $run_status -ne 0 ]]; then
82
+ echo "Agent batch run failed with exit code $run_status" >&2
83
+ echo "=== Full output ===" >&2
84
+ cat "$output_file" >&2 || true
85
+ exit 1
86
+ fi
87
+
88
+ assert_contains() {
89
+ local expected="$1"
90
+ if ! grep -Fq "$expected" "$output_file"; then
91
+ echo "Missing expected output: $expected" >&2
92
+ echo "=== Full output ===" >&2
93
+ cat "$output_file" >&2
94
+ exit 1
95
+ fi
96
+ }
97
+
98
+ assert_not_contains() {
99
+ local unexpected="$1"
100
+ if grep -Fq "$unexpected" "$output_file"; then
101
+ echo "Unexpected output found: $unexpected" >&2
102
+ echo "=== Full output ===" >&2
103
+ cat "$output_file" >&2
104
+ exit 1
105
+ fi
106
+ }
107
+
108
+ assert_contains "Welcome to Cursor"
109
+ assert_contains "Commands"
110
+ assert_contains "Cursor v"
111
+ assert_contains "File 'e2e.txt' written successfully"
112
+ assert_contains "alpha beta"
113
+ assert_contains "Successfully replaced 1 occurrence(s)"
114
+ assert_contains "omega beta"
115
+ assert_contains "Found 1 matches:"
116
+ assert_contains "Remembered: e2e-memory-fact"
117
+ assert_contains "e2e-memory-fact"
118
+ assert_contains "Session memory cleared"
119
+ assert_contains "e2e-cmd-ok"
120
+ assert_contains "e2e-bang-ok"
121
+ assert_contains "Entering shell mode"
122
+ assert_contains "e2e-shell-mode-ok"
123
+ assert_contains "Exiting shell mode"
124
+ assert_contains "Available meta commands:"
125
+ assert_contains "Session Statistics:"
126
+ assert_contains "Goodbye"
127
+ assert_contains "Agent run completed"
128
+ assert_not_contains "AI service returned status code"
129
+ assert_not_contains "AI service unavailable"
130
+
131
+ echo "Local batch E2E passed"
@@ -1,15 +1,178 @@
1
+ #include "services/ai_service.h"
2
+ #include "memory_manager.h"
3
+ #include "agent.h"
1
4
  #include "version.h"
2
5
  #include <gtest/gtest.h>
6
+ #include <functional>
7
+ #include <iostream>
8
+ #include <sstream>
9
+
10
+ static std::string capture_stdout(const std::function<void()> &fn) {
11
+ std::ostringstream buffer;
12
+ std::streambuf *old_buf = std::cout.rdbuf(buffer.rdbuf());
13
+ fn();
14
+ std::cout.rdbuf(old_buf);
15
+ return buffer.str();
16
+ }
3
17
 
4
18
  // Basic test to verify the testing framework works
5
19
  TEST(BasicTest, SanityCheck) { EXPECT_EQ(1 + 1, 2); }
6
20
 
21
+ TEST(AgentTest, DirectCommandRouting) {
22
+ EXPECT_TRUE(Core::Agent::is_direct_command_input("cmd:ls"));
23
+ EXPECT_TRUE(Core::Agent::is_direct_command_input("build:make"));
24
+ EXPECT_TRUE(Core::Agent::is_direct_command_input("grep:main:src"));
25
+ EXPECT_FALSE(Core::Agent::is_direct_command_input("foo:bar"));
26
+ EXPECT_FALSE(Core::Agent::is_direct_command_input("abc:def:ghi"));
27
+ EXPECT_TRUE(Core::Agent::is_direct_command_input("memory"));
28
+ EXPECT_TRUE(Core::Agent::is_direct_command_input("clear"));
29
+ EXPECT_TRUE(Core::Agent::is_direct_command_input("forget"));
30
+ EXPECT_FALSE(Core::Agent::is_direct_command_input("memory show"));
31
+ EXPECT_FALSE(Core::Agent::is_direct_command_input("clear the build cache"));
32
+ EXPECT_FALSE(Core::Agent::is_direct_command_input("forget about that error"));
33
+ }
34
+
35
+ TEST(AgentTest, MapNaturalLanguageToDirectCommands) {
36
+ EXPECT_TRUE(Core::Agent::map_nl_to_direct_command(
37
+ "can you check the files we changed").has_value());
38
+ EXPECT_EQ(*Core::Agent::map_nl_to_direct_command(
39
+ "can you check the files we changed"), "git:status");
40
+ EXPECT_EQ(*Core::Agent::map_nl_to_direct_command(
41
+ "show changed files in repo"), "git:status");
42
+ EXPECT_EQ(*Core::Agent::map_nl_to_direct_command(
43
+ "what is the commit history"), "git:log");
44
+ EXPECT_EQ(*Core::Agent::map_nl_to_direct_command(
45
+ "find TODO in code"), "todos:.");
46
+ }
47
+
48
+ TEST(AgentTest, MapAllNaturalLanguageToDirectCommands) {
49
+ EXPECT_EQ(*Core::Agent::map_nl_to_direct_command("run make test"), "cmd:make test");
50
+ EXPECT_EQ(*Core::Agent::map_nl_to_direct_command("build the project"), "build:make");
51
+ EXPECT_EQ(*Core::Agent::map_nl_to_direct_command("read file src/main.cpp"), "read:src/main.cpp");
52
+ EXPECT_EQ(*Core::Agent::map_nl_to_direct_command("save file notes.txt as hello world"), "write:notes.txt hello world");
53
+ EXPECT_EQ(*Core::Agent::map_nl_to_direct_command("remember the api key is secret"), "remember:the api key is secret");
54
+ EXPECT_EQ(*Core::Agent::map_nl_to_direct_command("forget everything"), "forget");
55
+ EXPECT_EQ(*Core::Agent::map_nl_to_direct_command("show memory"), "memory");
56
+ EXPECT_EQ(*Core::Agent::map_nl_to_direct_command("clear memory"), "clear");
57
+ EXPECT_EQ(*Core::Agent::map_nl_to_direct_command("replace foo with bar in file README.md"), "replace:README.md:foo:bar");
58
+ EXPECT_EQ(*Core::Agent::map_nl_to_direct_command("open owner/repo on github"), "github:repo:owner/repo");
59
+ }
60
+
61
+ TEST(AgentTest, AgenticWorkflowCommands) {
62
+ Core::Agent agent;
63
+ std::string output = capture_stdout([&] {
64
+ agent.process_user_input("/goal set Fix the issue");
65
+ agent.process_user_input("/task add Investigate logs");
66
+ agent.process_user_input("/task list");
67
+ });
68
+ EXPECT_NE(output.find("Goal set: Fix the issue"), std::string::npos);
69
+ EXPECT_NE(output.find("Task added: [1] Investigate logs"), std::string::npos);
70
+ EXPECT_NE(output.find("Active tasks:"), std::string::npos);
71
+ }
72
+
73
+ TEST(AgentTest, DebugModeTogglesVerbosePipelineOutput) {
74
+ Core::Agent agent;
75
+
76
+ std::string output = capture_stdout([&] {
77
+ agent.process_user_input("git status");
78
+ });
79
+ EXPECT_EQ(output.find("INPUT RECEIVED"), std::string::npos);
80
+ EXPECT_EQ(output.find("Input classification"), std::string::npos);
81
+
82
+ output = capture_stdout([&] {
83
+ agent.process_user_input("/debug");
84
+ agent.process_user_input("git status");
85
+ });
86
+ EXPECT_NE(output.find("Verbose mode ON"), std::string::npos);
87
+ EXPECT_NE(output.find("INPUT RECEIVED"), std::string::npos);
88
+ EXPECT_NE(output.find("Input classification"), std::string::npos);
89
+
90
+ output = capture_stdout([&] {
91
+ agent.process_user_input("/debug");
92
+ agent.process_user_input("git status");
93
+ });
94
+ EXPECT_NE(output.find("Verbose mode OFF"), std::string::npos);
95
+ size_t off_pos = output.find("Verbose mode OFF");
96
+ EXPECT_EQ(output.find("INPUT RECEIVED", off_pos), std::string::npos);
97
+ EXPECT_EQ(output.find("Input classification", off_pos), std::string::npos);
98
+ }
99
+
100
+ TEST(AgentTest, DetectGitStatusQuery) {
101
+ EXPECT_TRUE(Core::Agent::is_git_status_query(
102
+ "can you check the files we changed"));
103
+ EXPECT_TRUE(Core::Agent::is_git_status_query("what changed in the repo"));
104
+ EXPECT_TRUE(Core::Agent::is_git_status_query("show changed files"));
105
+ EXPECT_FALSE(Core::Agent::is_git_status_query("please help me with a diagram"));
106
+ }
107
+
108
+ TEST(AgentTest, ShowAgentDocumentation) {
109
+ Core::Agent agent;
110
+ std::string output = capture_stdout([&] {
111
+ agent.show_agent_documentation();
112
+ });
113
+
114
+ EXPECT_NE(output.find("Runtime help from AGENTS.md:"),
115
+ std::string::npos);
116
+ EXPECT_NE(output.find("Runtime Help Integration"), std::string::npos);
117
+ }
118
+
7
119
  // Test for version functionality
8
120
  TEST(VersionTest, VersionCommand) {
9
121
  const char *version = Version::get_version();
10
122
  EXPECT_STREQ(version, cursor_version_string);
11
123
  }
12
124
 
125
+ TEST(AgentTest, FormattedGitStatusOutput) {
126
+ Core::Agent agent;
127
+ capture_stdout([&] { agent.process_user_input("/debug"); });
128
+ std::vector<std::string> empty_files;
129
+ std::vector<std::string> files = {"src/main.cpp", "include/agent.h", "CMakeLists.txt"};
130
+
131
+ // Test with empty files (should show clean status)
132
+ std::string output = capture_stdout([&] {
133
+ agent.show_git_status_results(empty_files);
134
+ });
135
+ EXPECT_NE(output.find("clean"), std::string::npos);
136
+
137
+ // Test with files (should show file list with numbers)
138
+ output = capture_stdout([&] {
139
+ agent.show_git_status_results(files);
140
+ });
141
+ EXPECT_NE(output.find("src/main.cpp"), std::string::npos);
142
+ EXPECT_NE(output.find("include/agent.h"), std::string::npos);
143
+ EXPECT_NE(output.find("CMakeLists.txt"), std::string::npos);
144
+ }
145
+
146
+ TEST(AgentTest, FormattedFilePreview) {
147
+ Core::Agent agent;
148
+ capture_stdout([&] { agent.process_user_input("/debug"); });
149
+ std::string content = "int main() {\n std::cout << \"Hello\";\n return 0;\n}\n";
150
+
151
+ // Test file preview with syntax highlighting
152
+ std::string output = capture_stdout([&] {
153
+ agent.show_file_preview("test.cpp", content, 10);
154
+ });
155
+
156
+ // Should show filename
157
+ EXPECT_NE(output.find("test.cpp"), std::string::npos);
158
+ // Should show content with line numbers
159
+ EXPECT_NE(output.find("main"), std::string::npos);
160
+ EXPECT_NE(output.find("Hello"), std::string::npos);
161
+ }
162
+
163
+ TEST(AgentTest, FormattedFilePreviewHandlesErrors) {
164
+ Core::Agent agent;
165
+ capture_stdout([&] { agent.process_user_input("/debug"); });
166
+ std::string error_msg = "Error: File 'nonexistent.txt' does not exist";
167
+
168
+ // Test that error messages are displayed
169
+ std::string output = capture_stdout([&] {
170
+ agent.show_file_preview("nonexistent.txt", error_msg, 10);
171
+ });
172
+
173
+ EXPECT_NE(output.find("Error:"), std::string::npos);
174
+ }
175
+
13
176
  int main(int argc, char **argv) {
14
177
  ::testing::InitGoogleTest(&argc, argv);
15
178
  return RUN_ALL_TESTS();