#include "cli/service/resources/resource_catalog.h" #include #include "absl/strings/str_cat.h" #include "absl/strings/str_join.h" #include "absl/strings/string_view.h" namespace yaze { namespace cli { namespace { ResourceSchema MakePaletteSchema() { ResourceSchema schema; schema.resource = "palette"; schema.description = "Palette manipulation commands covering export, import, and color editing."; ResourceAction export_action; export_action.name = "export"; export_action.synopsis = "z3ed palette export --group --id --to "; export_action.stability = "experimental"; export_action.arguments = { ResourceArgument{"--group", "integer", true, "Palette group id (0-31)."}, ResourceArgument{"--id", "integer", true, "Palette index inside the group."}, ResourceArgument{"--to", "path", true, "Destination file path for binary export."}, }; export_action.effects = { "Reads ROM palette buffer and writes binary palette data to disk."}; ResourceAction import_action; import_action.name = "import"; import_action.synopsis = "z3ed palette import --group --id --from "; import_action.stability = "experimental"; import_action.arguments = { ResourceArgument{"--group", "integer", true, "Palette group id (0-31)."}, ResourceArgument{"--id", "integer", true, "Palette index inside the group."}, ResourceArgument{"--from", "path", true, "Source binary palette file."}, }; import_action.effects = { "Writes imported palette bytes into ROM buffer and marks project dirty."}; schema.actions = {export_action, import_action}; return schema; } ResourceSchema MakeRomSchema() { ResourceSchema schema; schema.resource = "rom"; schema.description = "ROM validation, diffing, and snapshot helpers."; ResourceAction info_action; info_action.name = "info"; info_action.synopsis = "z3ed rom info --rom "; info_action.stability = "stable"; info_action.arguments = { ResourceArgument{"--rom", "path", true, "Path to ROM file configured via global flag."}, }; info_action.effects = { "Reads ROM from disk and displays basic information (title, size, filename)."}; info_action.returns = { {"title", "string", "ROM internal title from header."}, {"size", "integer", "ROM file size in bytes."}, {"filename", "string", "Full path to the ROM file."}}; ResourceAction validate_action; validate_action.name = "validate"; validate_action.synopsis = "z3ed rom validate --rom "; validate_action.stability = "stable"; validate_action.arguments = { ResourceArgument{"--rom", "path", true, "Path to ROM file configured via global flag."}, }; validate_action.effects = { "Reads ROM from disk, verifies checksum, and reports header status."}; validate_action.returns = { {"report", "object", "Structured validation summary with checksum and header results."}}; ResourceAction diff_action; diff_action.name = "diff"; diff_action.synopsis = "z3ed rom diff "; diff_action.stability = "beta"; diff_action.arguments = { ResourceArgument{"rom_a", "path", true, "Reference ROM path."}, ResourceArgument{"rom_b", "path", true, "Candidate ROM path."}, }; diff_action.effects = { "Reads two ROM images, compares bytes, and streams differences to stdout."}; diff_action.returns = { {"differences", "integer", "Count of mismatched bytes between ROMs."}}; ResourceAction generate_action; generate_action.name = "generate-golden"; generate_action.synopsis = "z3ed rom generate-golden "; generate_action.stability = "experimental"; generate_action.arguments = { ResourceArgument{"rom_file", "path", true, "Source ROM to snapshot."}, ResourceArgument{"golden_file", "path", true, "Output path for golden image."}, }; generate_action.effects = { "Writes out exact ROM image for tooling baselines and diff workflows."}; generate_action.returns = { {"artifact", "path", "Absolute path to the generated golden image."}}; schema.actions = {info_action, validate_action, diff_action, generate_action}; return schema; } ResourceSchema MakePatchSchema() { ResourceSchema schema; schema.resource = "patch"; schema.description = "Patch authoring and application commands covering BPS and Asar flows."; ResourceAction apply_action; apply_action.name = "apply"; apply_action.synopsis = "z3ed patch apply "; apply_action.stability = "beta"; apply_action.arguments = { ResourceArgument{"rom_file", "path", true, "Source ROM image that will receive the patch."}, ResourceArgument{"bps_patch", "path", true, "BPS patch to apply to the ROM."}, }; apply_action.effects = { "Loads ROM from disk, applies a BPS patch, and writes `patched.sfc`."}; apply_action.returns = { {"artifact", "path", "Absolute path to the patched ROM image produced on success."}}; ResourceAction asar_action; asar_action.name = "apply-asar"; asar_action.synopsis = "z3ed patch apply-asar "; asar_action.stability = "prototype"; asar_action.arguments = { ResourceArgument{"patch.asm", "path", true, "Assembly patch consumed by the bundled Asar runtime."}, ResourceArgument{"--rom", "path", false, "ROM path supplied via global --rom flag."}, }; asar_action.effects = { "Invokes Asar against the active ROM buffer and applies assembled changes."}; asar_action.returns = { {"log", "string", "Assembler diagnostics emitted during application."}}; ResourceAction create_action; create_action.name = "create"; create_action.synopsis = "z3ed patch create --source --target --out "; create_action.stability = "experimental"; create_action.arguments = { ResourceArgument{"--source", "path", true, "Baseline ROM used when computing the patch."}, ResourceArgument{"--target", "path", true, "Modified ROM to diff against the baseline."}, ResourceArgument{"--out", "path", true, "Output path for the generated BPS patch."}, }; create_action.effects = { "Compares source and target images to synthesize a distributable BPS patch."}; create_action.returns = { {"artifact", "path", "File system path to the generated patch."}}; schema.actions = {apply_action, asar_action, create_action}; return schema; } ResourceSchema MakeOverworldSchema() { ResourceSchema schema; schema.resource = "overworld"; schema.description = "Overworld tile inspection and manipulation commands."; ResourceAction get_tile; get_tile.name = "get-tile"; get_tile.synopsis = "z3ed overworld get-tile --map --x --y "; get_tile.stability = "stable"; get_tile.arguments = { ResourceArgument{"--map", "integer", true, "Overworld map identifier (0-63)."}, ResourceArgument{"--x", "integer", true, "Tile x coordinate."}, ResourceArgument{"--y", "integer", true, "Tile y coordinate."}, }; get_tile.returns = { {"tile", "integer", "Tile id located at the supplied coordinates."}}; ResourceAction set_tile; set_tile.name = "set-tile"; set_tile.synopsis = "z3ed overworld set-tile --map --x --y --tile "; set_tile.stability = "experimental"; set_tile.arguments = { ResourceArgument{"--map", "integer", true, "Overworld map identifier (0-63)."}, ResourceArgument{"--x", "integer", true, "Tile x coordinate."}, ResourceArgument{"--y", "integer", true, "Tile y coordinate."}, ResourceArgument{"--tile", "integer", true, "Tile id to write."}, }; set_tile.effects = { "Mutates overworld tile map and enqueues render invalidation."}; schema.actions = {get_tile, set_tile}; return schema; } ResourceSchema MakeDungeonSchema() { ResourceSchema schema; schema.resource = "dungeon"; schema.description = "Dungeon room export and inspection utilities."; ResourceAction export_action; export_action.name = "export"; export_action.synopsis = "z3ed dungeon export "; export_action.stability = "prototype"; export_action.arguments = { ResourceArgument{"room_id", "integer", true, "Dungeon room identifier to inspect."}, }; export_action.effects = { "Loads the active ROM via --rom and prints metadata for the requested room."}; export_action.returns = { {"metadata", "object", "Structured room summary including blockset, spriteset, palette, and layout."}}; ResourceAction list_objects_action; list_objects_action.name = "list-objects"; list_objects_action.synopsis = "z3ed dungeon list-objects "; list_objects_action.stability = "prototype"; list_objects_action.arguments = { ResourceArgument{"room_id", "integer", true, "Dungeon room identifier whose objects should be listed."}, }; list_objects_action.effects = { "Streams parsed dungeon object records for the requested room to stdout."}; list_objects_action.returns = { {"objects", "array", "Collection of tile object records with ids, coordinates, and layers."}}; schema.actions = {export_action, list_objects_action}; return schema; } ResourceSchema MakeAgentSchema() { ResourceSchema schema; schema.resource = "agent"; schema.description = "Agent workflow helpers including planning, diffing, listing, and schema discovery."; ResourceAction describe_action; describe_action.name = "describe"; describe_action.synopsis = "z3ed agent describe --resource "; describe_action.stability = "prototype"; describe_action.arguments = { ResourceArgument{"--resource", "string", false, "Optional resource name to filter results."}, }; describe_action.returns = { {"schema", "object", "JSON schema describing resource arguments and semantics."}}; ResourceAction list_action; list_action.name = "list"; list_action.synopsis = "z3ed agent list"; list_action.stability = "prototype"; list_action.arguments = {}; list_action.effects = {{"reads", "proposal_registry"}}; list_action.returns = { {"proposals", "array", "List of all proposals with ID, status, prompt, and metadata."}}; ResourceAction diff_action; diff_action.name = "diff"; diff_action.synopsis = "z3ed agent diff [--proposal-id ]"; diff_action.stability = "prototype"; diff_action.arguments = { ResourceArgument{"--proposal-id", "string", false, "Optional proposal ID to view specific proposal. Defaults to latest pending."}, }; diff_action.effects = {{"reads", "proposal_registry"}, {"reads", "sandbox"}}; diff_action.returns = { {"diff", "string", "Unified diff showing changes to ROM."}, {"log", "string", "Execution log of commands run."}, {"metadata", "object", "Proposal metadata including status and timestamps."}}; schema.actions = {describe_action, list_action, diff_action}; return schema; } } // namespace const ResourceCatalog& ResourceCatalog::Instance() { static ResourceCatalog* instance = new ResourceCatalog(); return *instance; } ResourceCatalog::ResourceCatalog() : resources_({MakeRomSchema(), MakePatchSchema(), MakePaletteSchema(), MakeOverworldSchema(), MakeDungeonSchema(), MakeAgentSchema()}) {} absl::StatusOr ResourceCatalog::GetResource(absl::string_view name) const { for (const auto& resource : resources_) { if (resource.resource == name) { return resource; } } return absl::NotFoundError(absl::StrCat("Resource not found: ", name)); } const std::vector& ResourceCatalog::AllResources() const { return resources_; } std::string ResourceCatalog::SerializeResource(const ResourceSchema& schema) const { return SerializeResources({schema}); } std::string ResourceCatalog::SerializeResources(const std::vector& schemas) const { std::vector entries; entries.reserve(schemas.size()); for (const auto& resource : schemas) { std::vector action_entries; action_entries.reserve(resource.actions.size()); for (const auto& action : resource.actions) { std::vector arg_entries; arg_entries.reserve(action.arguments.size()); for (const auto& arg : action.arguments) { arg_entries.push_back(absl::StrCat( "{\"flag\":\"", EscapeJson(arg.flag), "\",\"type\":\"", EscapeJson(arg.type), "\",\"required\":", arg.required ? "true" : "false", ",\"description\":\"", EscapeJson(arg.description), "\"}")); } std::vector effect_entries; effect_entries.reserve(action.effects.size()); for (const auto& effect : action.effects) { effect_entries.push_back(absl::StrCat("\"", EscapeJson(effect), "\"")); } std::vector return_entries; return_entries.reserve(action.returns.size()); for (const auto& ret : action.returns) { return_entries.push_back(absl::StrCat( "{\"field\":\"", EscapeJson(ret.field), "\",\"type\":\"", EscapeJson(ret.type), "\",\"description\":\"", EscapeJson(ret.description), "\"}")); } action_entries.push_back(absl::StrCat( "{\"name\":\"", EscapeJson(action.name), "\",\"synopsis\":\"", EscapeJson(action.synopsis), "\",\"stability\":\"", EscapeJson(action.stability), "\",\"arguments\":[", absl::StrJoin(arg_entries, ","), "],", "\"effects\":[", absl::StrJoin(effect_entries, ","), "],", "\"returns\":[", absl::StrJoin(return_entries, ","), "]}")); } entries.push_back(absl::StrCat( "{\"resource\":\"", EscapeJson(resource.resource), "\",\"description\":\"", EscapeJson(resource.description), "\",\"actions\":[", absl::StrJoin(action_entries, ","), "]}")); } return absl::StrCat("{\"resources\":[", absl::StrJoin(entries, ","), "]}"); } std::string ResourceCatalog::EscapeJson(absl::string_view value) { std::string out; out.reserve(value.size()); for (char c : value) { switch (c) { case '\\': out += "\\\\"; break; case '\"': out += "\\\""; break; case '\n': out += "\\n"; break; case '\r': out += "\\r"; break; case '\t': out += "\\t"; break; default: out += c; break; } } return out; } std::string ResourceCatalog::SerializeResourcesAsYaml( const std::vector& schemas, absl::string_view version, absl::string_view last_updated) const { std::string out; absl::StrAppend(&out, "# Auto-generated resource catalogue\n"); absl::StrAppend(&out, "version: ", EscapeYaml(version), "\n"); absl::StrAppend(&out, "last_updated: ", EscapeYaml(last_updated), "\n"); absl::StrAppend(&out, "resources:\n"); for (const auto& resource : schemas) { absl::StrAppend(&out, " - name: ", EscapeYaml(resource.resource), "\n"); absl::StrAppend(&out, " description: ", EscapeYaml(resource.description), "\n"); if (resource.actions.empty()) { absl::StrAppend(&out, " actions: []\n"); continue; } absl::StrAppend(&out, " actions:\n"); for (const auto& action : resource.actions) { absl::StrAppend(&out, " - name: ", EscapeYaml(action.name), "\n"); absl::StrAppend(&out, " synopsis: ", EscapeYaml(action.synopsis), "\n"); absl::StrAppend(&out, " stability: ", EscapeYaml(action.stability), "\n"); if (action.arguments.empty()) { absl::StrAppend(&out, " args: []\n"); } else { absl::StrAppend(&out, " args:\n"); for (const auto& arg : action.arguments) { absl::StrAppend(&out, " - flag: ", EscapeYaml(arg.flag), "\n"); absl::StrAppend(&out, " type: ", EscapeYaml(arg.type), "\n"); absl::StrAppend(&out, " required: ", arg.required ? "true\n" : "false\n"); absl::StrAppend(&out, " description: ", EscapeYaml(arg.description), "\n"); } } if (action.effects.empty()) { absl::StrAppend(&out, " effects: []\n"); } else { absl::StrAppend(&out, " effects:\n"); for (const auto& effect : action.effects) { absl::StrAppend(&out, " - ", EscapeYaml(effect), "\n"); } } if (action.returns.empty()) { absl::StrAppend(&out, " returns: []\n"); } else { absl::StrAppend(&out, " returns:\n"); for (const auto& ret : action.returns) { absl::StrAppend(&out, " - field: ", EscapeYaml(ret.field), "\n"); absl::StrAppend(&out, " type: ", EscapeYaml(ret.type), "\n"); absl::StrAppend(&out, " description: ", EscapeYaml(ret.description), "\n"); } } } } return out; } std::string ResourceCatalog::EscapeYaml(absl::string_view value) { std::string out; out.reserve(value.size() + 2); out.push_back('"'); for (char c : value) { switch (c) { case '\\': out += "\\\\"; break; case '"': out += "\\\""; break; case '\n': out += "\\n"; break; case '\r': out += "\\r"; break; case '\t': out += "\\t"; break; default: out.push_back(c); break; } } out.push_back('"'); return out; } } // namespace cli } // namespace yaze