yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
general_commands.cc
Go to the documentation of this file.
1#include "cli/handlers/commands.h"
2
3#include <algorithm>
4#include <filesystem>
5#include <fstream>
6#include <optional>
7#include <sstream>
8#include <string>
9#include <utility>
10#include <vector>
11
12#include "absl/flags/declare.h"
13#include "absl/flags/flag.h"
14#include "absl/status/status.h"
15#include "absl/status/status.h"
16#include "absl/strings/ascii.h"
17#include "absl/strings/match.h"
18#include "absl/strings/str_cat.h"
19#include "absl/strings/str_format.h"
20#include "absl/strings/str_join.h"
21#include "absl/time/clock.h"
22#include "absl/time/time.h"
23#include "absl/strings/string_view.h"
24#include "app/core/project.h"
27#include "cli/cli.h"
40#include "cli/tui/chat_tui.h"
41#include "cli/cli.h"
42#include "util/macro.h"
43
44ABSL_DECLARE_FLAG(std::string, rom);
45ABSL_DECLARE_FLAG(std::string, ai_provider);
46
47namespace yaze {
48namespace cli {
49namespace agent {
50
51namespace {
52
54 std::optional<std::string> resource;
55 std::string format = "json";
56 std::optional<std::string> output_path;
57 std::string version = "0.1.0";
58 std::optional<std::string> last_updated;
59};
60
61
62// Helper to load project and labels if available
63absl::Status TryLoadProjectAndLabels(Rom& rom) {
64 // Try to find and load a project file in current directory
65 core::YazeProject project;
66 auto project_status = project.Open(".");
67
68 if (project_status.ok()) {
69 std::cout << "šŸ“‚ Loaded project: " << project.name << "\n";
70
71 // Initialize embedded labels (all default Zelda3 resource names)
72 auto labels_status = project.InitializeEmbeddedLabels();
73 if (labels_status.ok()) {
74 std::cout << "āœ… Embedded labels initialized (all Zelda3 resources available)\n";
75 }
76
77 // Load labels from project (either embedded or external)
78 if (!project.labels_filename.empty()) {
79 auto* label_mgr = rom.resource_label();
80 if (label_mgr && label_mgr->LoadLabels(project.labels_filename)) {
81 std::cout << "šŸ·ļø Loaded custom labels from: " << project.labels_filename << "\n";
82 }
83 } else if (!project.resource_labels.empty() || project.use_embedded_labels) {
84 // Use labels embedded in project or default Zelda3 labels
85 auto* label_mgr = rom.resource_label();
86 if (label_mgr) {
87 label_mgr->labels_ = project.resource_labels;
88 label_mgr->labels_loaded_ = true;
89 std::cout << "šŸ·ļø Using embedded Zelda3 labels (rooms, sprites, entrances, items, etc.)\n";
90 }
91 }
92 } else {
93 // No project found - use embedded defaults anyway
94 std::cout << "ā„¹ļø No project file found. Using embedded default Zelda3 labels.\n";
96 }
97
98 return absl::OkStatus();
99}
100
101absl::Status EnsureRomLoaded(Rom& rom, const std::string& command) {
102 if (rom.is_loaded()) {
103 return absl::OkStatus();
104 }
105
106 std::string rom_path = absl::GetFlag(FLAGS_rom);
107 if (rom_path.empty()) {
108 return absl::FailedPreconditionError(
109 absl::StrFormat(
110 "No ROM loaded. Pass --rom=<path> when running %s.\n"
111 "Example: z3ed %s --rom=zelda3.sfc",
112 command, command));
113 }
114
115 // Load the ROM
116 auto status = rom.LoadFromFile(rom_path);
117 if (!status.ok()) {
118 return absl::FailedPreconditionError(absl::StrFormat(
119 "Failed to load ROM from '%s': %s", rom_path, status.message()));
120 }
121
122 return absl::OkStatus();
123}
124
125absl::StatusOr<DescribeOptions> ParseDescribeArgs(
126 const std::vector<std::string>& args) {
127 DescribeOptions options;
128 for (size_t i = 0; i < args.size(); ++i) {
129 const std::string& token = args[i];
130 std::string flag = token;
131 std::optional<std::string> inline_value;
132
133 if (absl::StartsWith(token, "--")) {
134 auto eq_pos = token.find('=');
135 if (eq_pos != std::string::npos) {
136 flag = token.substr(0, eq_pos);
137 inline_value = token.substr(eq_pos + 1);
138 }
139 }
140
141 auto require_value =
142 [&](absl::string_view flag_name) -> absl::StatusOr<std::string> {
143 if (inline_value.has_value()) {
144 return *inline_value;
145 }
146 if (i + 1 >= args.size()) {
147 return absl::InvalidArgumentError(
148 absl::StrFormat("Flag %s requires a value", flag_name));
149 }
150 return args[++i];
151 };
152
153 if (flag == "--resource") {
154 ASSIGN_OR_RETURN(auto value, require_value("--resource"));
155 options.resource = std::move(value);
156 } else if (flag == "--format") {
157 ASSIGN_OR_RETURN(auto value, require_value("--format"));
158 options.format = std::move(value);
159 } else if (flag == "--output") {
160 ASSIGN_OR_RETURN(auto value, require_value("--output"));
161 options.output_path = std::move(value);
162 } else if (flag == "--version") {
163 ASSIGN_OR_RETURN(auto value, require_value("--version"));
164 options.version = std::move(value);
165 } else if (flag == "--last-updated") {
166 ASSIGN_OR_RETURN(auto value, require_value("--last-updated"));
167 options.last_updated = std::move(value);
168 } else {
169 return absl::InvalidArgumentError(
170 absl::StrFormat("Unknown flag for agent describe: %s", token));
171 }
172 }
173
174 options.format = absl::AsciiStrToLower(options.format);
175 if (options.format != "json" && options.format != "yaml") {
176 return absl::InvalidArgumentError("--format must be either json or yaml");
177 }
178
179 return options;
180}
181
182} // namespace
183
184absl::Status HandleRunCommand(const std::vector<std::string>& arg_vec,
185 Rom& rom) {
186 if (arg_vec.size() < 2 || arg_vec[0] != "--prompt") {
187 return absl::InvalidArgumentError("Usage: agent run --prompt <prompt>");
188 }
189 std::string prompt = arg_vec[1];
190
191 RETURN_IF_ERROR(EnsureRomLoaded(rom, "agent run --prompt \"<prompt>\""));
192
193 // Get commands from the AI service
194 auto ai_service = CreateAIService(); // Use service factory
195 auto response_or = ai_service->GenerateResponse(prompt);
196 if (!response_or.ok()) {
197 return response_or.status();
198 }
199 AgentResponse response = std::move(response_or.value());
200 if (response.commands.empty()) {
201 return absl::FailedPreconditionError(
202 "Agent response did not include any executable commands.");
203 }
204
205 std::string provider = absl::GetFlag(FLAGS_ai_provider);
206
208 request.prompt = prompt;
209 request.response = &response;
210 request.rom = &rom;
211 request.sandbox_label = "agent-run";
212 request.ai_provider = std::move(provider);
213
214 ASSIGN_OR_RETURN(auto proposal_result,
216
217 const auto& metadata = proposal_result.metadata;
218 std::filesystem::path proposal_dir = metadata.log_path.parent_path();
219
220 std::cout
221 << "āœ… Agent successfully planned and executed changes in a sandbox."
222 << std::endl;
223 std::cout << " Proposal ID: " << metadata.id << std::endl;
224 std::cout << " Sandbox ROM: " << metadata.sandbox_rom_path << std::endl;
225 std::cout << " Proposal dir: " << proposal_dir << std::endl;
226 std::cout << " Diff file: " << metadata.diff_path << std::endl;
227 std::cout << " Log file: " << metadata.log_path << std::endl;
228 std::cout << " Proposal JSON: " << proposal_result.proposal_json_path
229 << std::endl;
230 std::cout << " Commands executed: "
231 << proposal_result.executed_commands << std::endl;
232 std::cout << " Tile16 changes: " << proposal_result.change_count
233 << std::endl;
234 std::cout << "\nTo review the changes, run:\n";
235 std::cout << " z3ed agent diff --proposal-id " << metadata.id << std::endl;
236 std::cout << "\nTo accept the changes, run:\n";
237 std::cout << " z3ed agent accept --proposal-id " << metadata.id << std::endl;
238
239 return absl::OkStatus();
240}
241
242absl::Status HandlePlanCommand(const std::vector<std::string>& arg_vec) {
243 if (arg_vec.size() < 2 || arg_vec[0] != "--prompt") {
244 return absl::InvalidArgumentError("Usage: agent plan --prompt <prompt>");
245 }
246 std::string prompt = arg_vec[1];
247
248 auto ai_service = CreateAIService(); // Use service factory
249 auto response_or = ai_service->GenerateResponse(prompt);
250 if (!response_or.ok()) {
251 return response_or.status();
252 }
253 std::vector<std::string> commands = response_or.value().commands;
254
255 // Create a proposal from the commands
256 Tile16ProposalGenerator generator;
257 auto proposal_or =
258 generator.GenerateFromCommands(prompt, commands, "ollama", nullptr);
259 if (!proposal_or.ok()) {
260 return proposal_or.status();
261 }
262 auto proposal = proposal_or.value();
263
264 auto& registry = ProposalRegistry::Instance();
265 auto plans_dir = registry.RootDirectory() / "plans";
266 std::error_code ec;
267 std::filesystem::create_directories(plans_dir, ec);
268 if (ec) {
269 return absl::InternalError(absl::StrCat("Failed to create plans directory: ", ec.message()));
270 }
271
272 auto plan_path = plans_dir / (proposal.id + ".json");
273 auto save_status = generator.SaveProposal(proposal, plan_path.string());
274 if (!save_status.ok()) {
275 return save_status;
276 }
277
278 std::cout << "AI Agent Plan (Proposal ID: " << proposal.id << "):\n";
279 std::cout << proposal.ToJson() << std::endl;
280 std::cout << "\nāœ… Plan saved to: " << plan_path.string() << std::endl;
281
282 return absl::OkStatus();
283}
284
285absl::Status HandleDiffCommand(Rom& rom, const std::vector<std::string>& args) {
286 std::optional<std::string> proposal_id;
287 for (size_t i = 0; i < args.size(); ++i) {
288 const std::string& token = args[i];
289 if (absl::StartsWith(token, "--proposal-id=")) {
290 proposal_id = token.substr(14);
291 } else if (token == "--proposal-id" && i + 1 < args.size()) {
292 proposal_id = args[i + 1];
293 ++i;
294 }
295 }
296
297 auto& registry = ProposalRegistry::Instance();
298 absl::StatusOr<ProposalRegistry::ProposalMetadata> proposal_or;
299 if (proposal_id.has_value()) {
300 proposal_or = registry.GetProposal(proposal_id.value());
301 } else {
302 proposal_or = registry.GetLatestPendingProposal();
303 }
304
305 if (proposal_or.ok()) {
306 const auto& proposal = proposal_or.value();
307
308 std::cout << "\n=== Proposal Diff ===\n";
309 std::cout << "Proposal ID: " << proposal.id << "\n";
310 std::cout << "Sandbox ID: " << proposal.sandbox_id << "\n";
311 std::cout << "Prompt: " << proposal.prompt << "\n";
312 std::cout << "Description: " << proposal.description << "\n";
313 std::cout << "Status: ";
314 switch (proposal.status) {
316 std::cout << "Pending";
317 break;
319 std::cout << "Accepted";
320 break;
322 std::cout << "Rejected";
323 break;
324 }
325 std::cout << "\n";
326 std::cout << "Created: " << absl::FormatTime(proposal.created_at) << "\n";
327 std::cout << "Commands Executed: " << proposal.commands_executed << "\n";
328 std::cout << "Bytes Changed: " << proposal.bytes_changed << "\n\n";
329
330 if (!proposal.sandbox_rom_path.empty()) {
331 std::cout << "Sandbox ROM: " << proposal.sandbox_rom_path << "\n";
332 }
333 std::cout << "Proposal directory: "
334 << proposal.log_path.parent_path() << "\n";
335 std::cout << "Diff file: " << proposal.diff_path << "\n";
336 std::cout << "Log file: " << proposal.log_path << "\n\n";
337
338 if (std::filesystem::exists(proposal.diff_path)) {
339 std::cout << "--- Diff Content ---\n";
340 std::ifstream diff_file(proposal.diff_path);
341 if (diff_file.is_open()) {
342 std::string line;
343 while (std::getline(diff_file, line)) {
344 std::cout << line << "\n";
345 }
346 } else {
347 std::cout << "(Unable to read diff file)\n";
348 }
349 } else {
350 std::cout << "(No diff file found)\n";
351 }
352
353 std::cout << "\n--- Execution Log ---\n";
354 if (std::filesystem::exists(proposal.log_path)) {
355 std::ifstream log_file(proposal.log_path);
356 if (log_file.is_open()) {
357 std::string line;
358 int line_count = 0;
359 while (std::getline(log_file, line)) {
360 std::cout << line << "\n";
361 line_count++;
362 if (line_count > 50) {
363 std::cout << "... (log truncated, see " << proposal.log_path
364 << " for full output)\n";
365 break;
366 }
367 }
368 } else {
369 std::cout << "(Unable to read log file)\n";
370 }
371 } else {
372 std::cout << "(No log file found)\n";
373 }
374
375 std::cout << "\n=== Next Steps ===\n";
376 std::cout << "To accept changes: z3ed agent commit\n";
377 std::cout << "To reject changes: z3ed agent revert\n";
378 std::cout << "To review in GUI: yaze --proposal=" << proposal.id << "\n";
379
380 return absl::OkStatus();
381 }
382
383 if (rom.is_loaded()) {
384 auto sandbox_or = RomSandboxManager::Instance().ActiveSandbox();
385 if (!sandbox_or.ok()) {
386 return absl::NotFoundError(
387 "No pending proposals found and no active sandbox. Run 'z3ed agent "
388 "run' first.");
389 }
390 // TODO: Use new CommandHandler system for RomDiff
391 // Reference: src/app/rom.cc (Rom comparison methods)
392 auto status = absl::UnimplementedError("RomDiff not yet implemented in new CommandHandler system");
393 if (!status.ok()) {
394 return status;
395 }
396 } else {
397 return absl::AbortedError("No ROM loaded.");
398 }
399 return absl::OkStatus();
400}
401
402absl::Status HandleLearnCommand(const std::vector<std::string>& args) {
404 static bool initialized = false;
405
406 if (!initialized) {
407 auto status = learn_service.Initialize();
408 if (!status.ok()) {
409 std::cerr << "Failed to initialize learned knowledge service: "
410 << status.message() << std::endl;
411 return status;
412 }
413 initialized = true;
414 }
415
416 if (args.empty()) {
417 // Show usage
418 std::cout << "\nUsage: z3ed agent learn [options]\n\n";
419 std::cout << "Options:\n";
420 std::cout << " --preference <key>=<value> Set a preference\n";
421 std::cout << " --get-preference <key> Get a preference value\n";
422 std::cout << " --list-preferences List all preferences\n";
423 std::cout << " --pattern <type> --data <json> Learn a ROM pattern\n";
424 std::cout << " --query-patterns <type> Query learned patterns\n";
425 std::cout << " --project <name> --context <text> Save project context\n";
426 std::cout << " --get-project <name> Get project context\n";
427 std::cout << " --list-projects List all projects\n";
428 std::cout << " --memory <topic> --summary <text> Store conversation memory\n";
429 std::cout << " --search-memories <query> Search memories\n";
430 std::cout << " --recent-memories [limit] Show recent memories\n";
431 std::cout << " --export <file> Export all data to JSON\n";
432 std::cout << " --import <file> Import data from JSON\n";
433 std::cout << " --stats Show statistics\n";
434 std::cout << " --clear Clear all learned data\n";
435 return absl::OkStatus();
436 }
437
438 // Parse arguments
439 std::string command = args[0];
440
441 if (command == "--preference" && args.size() >= 2) {
442 std::string pref = args[1];
443 size_t eq_pos = pref.find('=');
444 if (eq_pos == std::string::npos) {
445 return absl::InvalidArgumentError("Preference must be in format key=value");
446 }
447 std::string key = pref.substr(0, eq_pos);
448 std::string value = pref.substr(eq_pos + 1);
449 auto status = learn_service.SetPreference(key, value);
450 if (status.ok()) {
451 std::cout << "āœ“ Preference '" << key << "' set to '" << value << "'\n";
452 }
453 return status;
454 }
455
456 if (command == "--get-preference" && args.size() >= 2) {
457 auto value = learn_service.GetPreference(args[1]);
458 if (value) {
459 std::cout << args[1] << " = " << *value << "\n";
460 } else {
461 std::cout << "Preference '" << args[1] << "' not found\n";
462 }
463 return absl::OkStatus();
464 }
465
466 if (command == "--list-preferences") {
467 auto prefs = learn_service.GetAllPreferences();
468 if (prefs.empty()) {
469 std::cout << "No preferences stored.\n";
470 } else {
471 std::cout << "\n=== Stored Preferences ===\n";
472 for (const auto& [key, value] : prefs) {
473 std::cout << " " << key << " = " << value << "\n";
474 }
475 }
476 return absl::OkStatus();
477 }
478
479 if (command == "--stats") {
480 auto stats = learn_service.GetStats();
481 std::cout << "\n=== Learned Knowledge Statistics ===\n";
482 std::cout << " Preferences: " << stats.preference_count << "\n";
483 std::cout << " ROM Patterns: " << stats.pattern_count << "\n";
484 std::cout << " Projects: " << stats.project_count << "\n";
485 std::cout << " Memories: " << stats.memory_count << "\n";
486 std::cout << " First learned: " << absl::FormatTime(absl::FromUnixMillis(stats.first_learned_at)) << "\n";
487 std::cout << " Last updated: " << absl::FormatTime(absl::FromUnixMillis(stats.last_updated_at)) << "\n";
488 return absl::OkStatus();
489 }
490
491 if (command == "--export" && args.size() >= 2) {
492 auto json = learn_service.ExportToJSON();
493 if (!json.ok()) {
494 return json.status();
495 }
496 std::ofstream file(args[1]);
497 if (!file.is_open()) {
498 return absl::InternalError("Failed to open file for writing");
499 }
500 file << *json;
501 std::cout << "āœ“ Exported learned data to " << args[1] << "\n";
502 return absl::OkStatus();
503 }
504
505 if (command == "--import" && args.size() >= 2) {
506 std::ifstream file(args[1]);
507 if (!file.is_open()) {
508 return absl::NotFoundError("File not found: " + args[1]);
509 }
510 std::stringstream buffer;
511 buffer << file.rdbuf();
512 auto status = learn_service.ImportFromJSON(buffer.str());
513 if (status.ok()) {
514 std::cout << "āœ“ Imported learned data from " << args[1] << "\n";
515 }
516 return status;
517 }
518
519 if (command == "--clear") {
520 auto status = learn_service.ClearAll();
521 if (status.ok()) {
522 std::cout << "āœ“ All learned data cleared\n";
523 }
524 return status;
525 }
526
527 if (command == "--list-projects") {
528 auto projects = learn_service.GetAllProjects();
529 if (projects.empty()) {
530 std::cout << "No projects stored.\n";
531 } else {
532 std::cout << "\n=== Stored Projects ===\n";
533 for (const auto& proj : projects) {
534 std::cout << " " << proj.project_name << "\n";
535 std::cout << " ROM Hash: " << proj.rom_hash.substr(0, 16) << "...\n";
536 std::cout << " Last Accessed: " << absl::FormatTime(absl::FromUnixMillis(proj.last_accessed)) << "\n";
537 }
538 }
539 return absl::OkStatus();
540 }
541
542 if (command == "--recent-memories") {
543 int limit = 10;
544 if (args.size() >= 2) {
545 limit = std::stoi(args[1]);
546 }
547 auto memories = learn_service.GetRecentMemories(limit);
548 if (memories.empty()) {
549 std::cout << "No memories stored.\n";
550 } else {
551 std::cout << "\n=== Recent Memories ===\n";
552 for (const auto& mem : memories) {
553 std::cout << " Topic: " << mem.topic << "\n";
554 std::cout << " Summary: " << mem.summary << "\n";
555 std::cout << " Facts: " << mem.key_facts.size() << " key facts\n";
556 std::cout << " Created: " << absl::FormatTime(absl::FromUnixMillis(mem.created_at)) << "\n";
557 std::cout << "\n";
558 }
559 }
560 return absl::OkStatus();
561 }
562
563 return absl::InvalidArgumentError("Unknown learn command. Use 'z3ed agent learn' for usage.");
564}
565
566absl::Status HandleListCommand() {
567 auto& registry = ProposalRegistry::Instance();
568 auto proposals = registry.ListProposals();
569
570 if (proposals.empty()) {
571 std::cout << "No proposals found.\n";
572 std::cout
573 << "Run 'z3ed agent run --prompt \"...\"' to create a proposal.\n";
574 return absl::OkStatus();
575 }
576
577 std::cout << "\n=== Agent Proposals ===\n\n";
578
579 for (const auto& proposal : proposals) {
580 std::cout << "ID: " << proposal.id << "\n";
581 std::cout << " Status: ";
582 switch (proposal.status) {
584 std::cout << "Pending";
585 break;
587 std::cout << "Accepted";
588 break;
590 std::cout << "Rejected";
591 break;
592 }
593 std::cout << "\n";
594 std::cout << " Created: " << absl::FormatTime(proposal.created_at) << "\n";
595 std::cout << " Prompt: " << proposal.prompt << "\n";
596 std::cout << " Commands: " << proposal.commands_executed << "\n";
597 std::cout << " Bytes Changed: " << proposal.bytes_changed << "\n";
598 std::cout << "\n";
599 }
600
601 std::cout << "Total: " << proposals.size() << " proposal(s)\n";
602 std::cout << "\nUse 'z3ed agent diff --proposal-id=<id>' to view details.\n";
603
604 return absl::OkStatus();
605}
606
607absl::Status HandleCommitCommand(Rom& rom) {
608 if (rom.is_loaded()) {
609 auto status = rom.SaveToFile({.save_new = false});
610 if (!status.ok()) {
611 return status;
612 }
613 std::cout << "āœ… Changes committed successfully." << std::endl;
614 } else {
615 return absl::AbortedError("No ROM loaded.");
616 }
617 return absl::OkStatus();
618}
619
620absl::Status HandleRevertCommand(Rom& rom) {
621 if (rom.is_loaded()) {
622 auto status = rom.LoadFromFile(rom.filename());
623 if (!status.ok()) {
624 return status;
625 }
626 std::cout << "āœ… Changes reverted successfully." << std::endl;
627 } else {
628 return absl::AbortedError("No ROM loaded.");
629 }
630 return absl::OkStatus();
631}
632
633absl::Status HandleDescribeCommand(const std::vector<std::string>& arg_vec) {
634 ASSIGN_OR_RETURN(auto options, ParseDescribeArgs(arg_vec));
635
636 const auto& catalog = ResourceCatalog::Instance();
637 std::optional<ResourceSchema> resource_schema;
638 if (options.resource.has_value()) {
639 auto resource_or = catalog.GetResource(*options.resource);
640 if (!resource_or.ok()) {
641 return resource_or.status();
642 }
643 resource_schema = resource_or.value();
644 }
645
646 std::string payload;
647 if (options.format == "json") {
648 if (resource_schema.has_value()) {
649 payload = catalog.SerializeResource(*resource_schema);
650 } else {
651 payload = catalog.SerializeResources(catalog.AllResources());
652 }
653 } else {
654 std::string last_updated =
655 options.last_updated.has_value()
656 ? *options.last_updated
657 : absl::FormatTime("%Y-%m-%d", absl::Now(), absl::LocalTimeZone());
658 if (resource_schema.has_value()) {
659 std::vector<ResourceSchema> schemas{*resource_schema};
660 payload = catalog.SerializeResourcesAsYaml(schemas, options.version,
661 last_updated);
662 } else {
663 payload = catalog.SerializeResourcesAsYaml(catalog.AllResources(),
664 options.version, last_updated);
665 }
666 }
667
668 if (options.output_path.has_value()) {
669 std::ofstream out(*options.output_path, std::ios::binary | std::ios::trunc);
670 if (!out.is_open()) {
671 return absl::InternalError(absl::StrFormat(
672 "Failed to open %s for writing", *options.output_path));
673 }
674 out << payload;
675 out.close();
676 if (!out) {
677 return absl::InternalError(absl::StrFormat("Failed to write schema to %s",
678 *options.output_path));
679 }
680 std::cout << absl::StrFormat("Wrote %s schema to %s", options.format,
681 *options.output_path)
682 << std::endl;
683 return absl::OkStatus();
684 }
685
686 std::cout << payload << std::endl;
687 return absl::OkStatus();
688}
689
690absl::Status HandleChatCommand(Rom& rom) {
691 RETURN_IF_ERROR(EnsureRomLoaded(rom, "agent chat"));
692
693 // Try to load project and labels automatically
694 auto _ = TryLoadProjectAndLabels(rom); // Ignore errors - we'll use defaults
695
696 tui::ChatTUI chat_tui(&rom);
697 chat_tui.Run();
698 return absl::OkStatus();
699}
700
701absl::Status HandleSimpleChatCommand(const std::vector<std::string>& arg_vec,
702 Rom* rom, bool quiet) {
703 RETURN_IF_ERROR(EnsureRomLoaded(*rom, "agent simple-chat"));
704
705 auto _ = TryLoadProjectAndLabels(*rom);
706
707 std::optional<std::string> batch_file;
708 std::optional<std::string> single_message;
709 bool verbose = false;
710 bool vim_mode = false;
711 std::optional<std::string> format_option;
712
713 for (size_t i = 0; i < arg_vec.size(); ++i) {
714 const std::string& arg = arg_vec[i];
715 if (absl::StartsWith(arg, "--file=")) {
716 batch_file = arg.substr(7);
717 } else if (arg == "--file" && i + 1 < arg_vec.size()) {
718 batch_file = arg_vec[++i];
719 } else if (absl::StartsWith(arg, "--format=")) {
720 format_option = arg.substr(9);
721 } else if (arg == "--format" && i + 1 < arg_vec.size()) {
722 format_option = arg_vec[++i];
723 } else if (arg == "--json") {
724 format_option = "json";
725 } else if (arg == "--markdown" || arg == "--md") {
726 format_option = "markdown";
727 } else if (arg == "--compact" || arg == "--raw") {
728 format_option = "compact";
729 } else if (arg == "--verbose" || arg == "-v") {
730 verbose = true;
731 } else if (arg == "--vim") {
732 vim_mode = true;
733 } else if (!absl::StartsWith(arg, "--") && !single_message.has_value()) {
734 single_message = arg;
735 }
736 }
737
738 agent::AgentConfig config;
739 config.verbose = verbose;
740 config.enable_vim_mode = vim_mode;
741 if (format_option.has_value()) {
742 std::string normalized = absl::AsciiStrToLower(*format_option);
743 if (normalized == "json") {
745 } else if (normalized == "markdown" || normalized == "md") {
747 } else if (normalized == "compact" || normalized == "raw") {
749 } else if (normalized == "text" || normalized == "friendly" ||
750 normalized == "pretty") {
752 } else {
753 return absl::InvalidArgumentError(
754 absl::StrCat("Unsupported chat format: ", *format_option,
755 ". Supported formats: text, markdown, json, compact"));
756 }
757 }
758
759 SimpleChatSession session;
760 session.SetConfig(config);
761 session.SetRomContext(rom);
762
763 if (batch_file.has_value()) {
764 std::ifstream file(*batch_file);
765 if (!file.is_open()) {
766 return absl::NotFoundError(absl::StrCat("Failed to open file: ", *batch_file));
767 }
768 if (!quiet) {
769 std::cout << "Running batch session from: " << *batch_file << std::endl;
770 std::cout << "----------------------------------------\n\n";
771 }
772 std::string line;
773 int line_num = 0;
774 while (std::getline(file, line)) {
775 line_num++;
776 std::string trimmed_line = std::string(absl::StripAsciiWhitespace(line));
777 if (trimmed_line.empty() || absl::StartsWith(trimmed_line, "#")) {
778 continue;
779 }
780 if (!quiet) {
781 std::cout << "Input [" << line_num << "]: " << trimmed_line << std::endl;
782 }
783 std::string response;
784 auto status = session.SendAndWaitForResponse(trimmed_line, &response);
785 if (!status.ok()) {
786 std::cerr << "Error processing line " << line_num << ": " << status.message() << std::endl;
787 continue;
788 }
789 std::cout << response << "\n";
790 if (!quiet) {
791 std::cout << "\n";
792 }
793 }
794 return absl::OkStatus();
795 } else if (single_message.has_value()) {
796 std::string response;
797 auto status = session.SendAndWaitForResponse(*single_message, &response);
798 if (!status.ok()) {
799 return status;
800 }
801 std::cout << response << "\n";
802 return absl::OkStatus();
803 } else {
804 return session.RunInteractive();
805 }
806}
807
808absl::Status HandleAcceptCommand(const std::vector<std::string>& arg_vec,
809 Rom& rom) {
810 std::optional<std::string> proposal_id;
811 for (size_t i = 0; i < arg_vec.size(); ++i) {
812 const std::string& token = arg_vec[i];
813 if (absl::StartsWith(token, "--proposal-id=")) {
814 proposal_id = token.substr(14);
815 break;
816 }
817 if (token == "--proposal-id" && i + 1 < arg_vec.size()) {
818 proposal_id = arg_vec[i + 1];
819 break;
820 }
821 }
822
823 if (!proposal_id.has_value() || proposal_id->empty()) {
824 return absl::InvalidArgumentError(
825 "Usage: agent accept --proposal-id <proposal_id>");
826 }
827
828 auto& registry = ProposalRegistry::Instance();
829 ASSIGN_OR_RETURN(auto metadata, registry.GetProposal(*proposal_id));
830
831 if (metadata.status == ProposalRegistry::ProposalStatus::kAccepted) {
832 std::cout << "Proposal '" << *proposal_id << "' is already accepted."
833 << std::endl;
834 return absl::OkStatus();
835 }
836
837 if (metadata.sandbox_rom_path.empty()) {
838 return absl::FailedPreconditionError(absl::StrCat(
839 "Proposal '", *proposal_id,
840 "' is missing sandbox ROM metadata. Cannot accept."));
841 }
842
843 if (!std::filesystem::exists(metadata.sandbox_rom_path)) {
844 return absl::NotFoundError(absl::StrCat(
845 "Sandbox ROM not found at ", metadata.sandbox_rom_path.string()));
846 }
847
849 EnsureRomLoaded(rom, "agent accept --proposal-id <proposal_id>"));
850
851 Rom sandbox_rom;
852 auto sandbox_load_status = sandbox_rom.LoadFromFile(
853 metadata.sandbox_rom_path.string(), RomLoadOptions::CliDefaults());
854 if (!sandbox_load_status.ok()) {
855 return absl::InternalError(absl::StrCat(
856 "Failed to load sandbox ROM: ", sandbox_load_status.message()));
857 }
858
859 if (rom.size() != sandbox_rom.size()) {
860 rom.Expand(static_cast<int>(sandbox_rom.size()));
861 }
862
863 auto copy_status = rom.WriteVector(0, sandbox_rom.vector());
864 if (!copy_status.ok()) {
865 return absl::InternalError(absl::StrCat(
866 "Failed to copy sandbox ROM data: ", copy_status.message()));
867 }
868
869 auto save_status = rom.SaveToFile({.save_new = false});
870 if (!save_status.ok()) {
871 return absl::InternalError(absl::StrCat(
872 "Failed to save changes to main ROM: ", save_status.message()));
873 }
874
875 RETURN_IF_ERROR(registry.UpdateStatus(
877 RETURN_IF_ERROR(registry.AppendLog(
878 *proposal_id,
879 absl::StrCat("Proposal accepted and applied to ", rom.filename())));
880
881 if (!metadata.sandbox_id.empty()) {
882 auto remove_status =
883 RomSandboxManager::Instance().RemoveSandbox(metadata.sandbox_id);
884 if (!remove_status.ok()) {
885 std::cerr << "Warning: Failed to remove sandbox '" << metadata.sandbox_id
886 << "': " << remove_status.message() << "\n";
887 }
888 }
889
890 std::cout << "āœ… Proposal '" << *proposal_id << "' accepted and applied to '"
891 << rom.filename() << "'." << std::endl;
892 std::cout << " Source sandbox ROM: " << metadata.sandbox_rom_path
893 << std::endl;
894
895 return absl::OkStatus();
896}
897
898} // namespace agent
899} // namespace cli
900} // namespace yaze
The Rom class is used to load, save, and modify Rom data.
Definition rom.h:71
absl::Status LoadFromFile(const std::string &filename, bool z3_load=true)
Definition rom.cc:289
auto filename() const
Definition rom.h:208
auto vector() const
Definition rom.h:207
absl::Status WriteVector(int addr, std::vector< uint8_t > data)
Definition rom.cc:777
void Expand(int size)
Definition rom.h:102
absl::Status SaveToFile(const SaveSettings &settings)
Definition rom.cc:536
auto size() const
Definition rom.h:202
core::ResourceLabelManager * resource_label()
Definition rom.h:220
bool is_loaded() const
Definition rom.h:197
static ProposalRegistry & Instance()
static const ResourceCatalog & Instance()
absl::Status RemoveSandbox(const std::string &id)
absl::StatusOr< SandboxMetadata > ActiveSandbox() const
static RomSandboxManager & Instance()
Generates and manages tile16 editing proposals.
absl::StatusOr< Tile16Proposal > GenerateFromCommands(const std::string &prompt, const std::vector< std::string > &commands, const std::string &ai_service, Rom *rom)
Generate a tile16 proposal from an AI-generated command list.
absl::Status SaveProposal(const Tile16Proposal &proposal, const std::string &path)
Save a proposal to a JSON file for later review.
Manages persistent learned information across agent sessions.
std::optional< std::string > GetPreference(const std::string &key) const
std::vector< ProjectContext > GetAllProjects() const
std::vector< ConversationMemory > GetRecentMemories(int limit=10) const
absl::Status ImportFromJSON(const std::string &json_data)
absl::Status SetPreference(const std::string &key, const std::string &value)
absl::StatusOr< std::string > ExportToJSON() const
std::map< std::string, std::string > GetAllPreferences() const
Simple text-based chat session for AI agent interaction.
void SetConfig(const AgentConfig &config)
absl::Status SendAndWaitForResponse(const std::string &message, std::string *response_out=nullptr)
ABSL_DECLARE_FLAG(std::string, rom)
#define RETURN_IF_ERROR(expression)
Definition macro.h:53
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:61
absl::Status EnsureRomLoaded(Rom &rom, const std::string &command)
absl::StatusOr< DescribeOptions > ParseDescribeArgs(const std::vector< std::string > &args)
absl::Status HandleSimpleChatCommand(const std::vector< std::string > &arg_vec, Rom *rom, bool quiet)
absl::Status HandleLearnCommand(const std::vector< std::string > &args)
absl::StatusOr< ProposalCreationResult > CreateProposalFromAgentResponse(const ProposalCreationRequest &request)
absl::Status HandleAcceptCommand(const std::vector< std::string > &arg_vec, Rom &rom)
absl::Status HandleCommitCommand(Rom &rom)
absl::Status HandleRunCommand(const std::vector< std::string > &arg_vec, Rom &rom)
absl::Status HandleDescribeCommand(const std::vector< std::string > &args)
absl::Status HandleDiffCommand(Rom &rom, const std::vector< std::string > &args)
absl::Status HandleChatCommand(Rom &rom)
absl::Status HandleRevertCommand(Rom &rom)
absl::Status HandleListCommand()
absl::Status HandlePlanCommand(const std::vector< std::string > &args)
std::unique_ptr< AIService > CreateAIService()
Main namespace for the application.
static RomLoadOptions CliDefaults()
Definition rom.cc:49
std::vector< std::string > commands
Definition common.h:26
std::unordered_map< std::string, std::unordered_map< std::string, std::string > > labels_
Definition project.h:235
Modern project structure with comprehensive settings consolidation.
Definition project.h:78
absl::Status Open(const std::string &project_path)
Definition project.cc:115
std::unordered_map< std::string, std::unordered_map< std::string, std::string > > resource_labels
Definition project.h:100
std::string labels_filename
Definition project.h:94
absl::Status InitializeEmbeddedLabels()
Definition project.cc:887