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