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