@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/.github/workflows/release.yml +2 -0
- package/AGENTS.md +66 -36
- package/CMakeLists.txt +54 -10
- package/DESIGN.md +775 -0
- package/README.md +10 -5
- package/cli.js +26 -3
- package/include/agent.h +55 -6
- package/include/services/ai_service.h +3 -0
- package/include/services/git_service.h +4 -0
- package/include/utils/platform.h +2 -2
- package/include/utils/ui.h +19 -0
- package/install.js +1 -0
- package/install.sh +2 -1
- package/package.json +3 -2
- package/release/checksums.txt +4 -4
- package/release/cursor-linux/cursor_v0.1.19_linux_amd64.tar.gz +0 -0
- package/release/cursor-macos/cursor-0.1.19.arm64_sequoia.bottle.tar.gz +0 -0
- package/release/cursor-macos/cursor_v0.1.19_darwin_arm64.tar.gz +0 -0
- package/release/cursor-windows/cursor__windows_amd64.zip +0 -0
- package/src/agent.cpp +1129 -157
- package/src/main.cpp +4 -1
- package/src/services/ai_service.cpp +7 -23
- package/src/services/git_service.cpp +45 -1
- package/src/services/web_service.cpp +7 -3
- package/src/utils/ui.cpp +94 -8
- package/src/utils/version.cpp +48 -15
- package/tests/e2e/run_local_batch.sh +131 -0
- package/tests/main_test.cpp +163 -0
- package/release/cursor-linux/cursor_v0.1.17_linux_amd64.tar.gz +0 -0
- package/release/cursor-macos/cursor-0.1.17.arm64_sequoia.bottle.tar.gz +0 -0
- package/release/cursor-macos/cursor_v0.1.17_darwin_arm64.tar.gz +0 -0
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
|
-
|
|
79
|
-
|
|
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 (
|
|
85
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
std::string
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
std::string
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
179
|
-
|
|
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
|
|
197
|
-
|
|
198
|
-
|
|
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::
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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
|
-
|
|
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
|
-
|
|
232
|
-
if (input.empty())
|
|
233
|
-
return default_choice;
|
|
681
|
+
std::cout << "\033[?25l";
|
|
234
682
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
-
|
|
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
|
-
|
|
271
|
-
|
|
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
|
-
|
|
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
|
-
|
|
419
|
-
": " + match.line_content
|
|
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
|
-
|
|
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::
|
|
553
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 /?
|
|
808
|
-
std::cout << " /
|
|
809
|
-
std::cout << " /
|
|
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 ¶m_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 §ion_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"
|