yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
command_registry.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <sstream>
5
6#include "absl/strings/str_cat.h"
7#include "absl/strings/str_format.h"
8#include "absl/strings/str_join.h"
10
11namespace yaze {
12namespace cli {
13
14namespace {
15std::string EscapeJson(const std::string& input) {
16 std::string escaped;
17 escaped.reserve(input.size());
18 for (char c : input) {
19 switch (c) {
20 case '\\\\':
21 escaped.append("\\\\");
22 break;
23 case '\"':
24 escaped.append("\\\"");
25 break;
26 case '\n':
27 escaped.append("\\n");
28 break;
29 case '\r':
30 escaped.append("\\r");
31 break;
32 case '\t':
33 escaped.append("\\t");
34 break;
35 default:
36 escaped.push_back(c);
37 }
38 }
39 return escaped;
40}
41
42void AppendStringArray(std::ostringstream& out,
43 const std::vector<std::string>& values) {
44 out << "[";
45 for (size_t i = 0; i < values.size(); ++i) {
46 if (i > 0) out << ", ";
47 out << "\"" << EscapeJson(values[i]) << "\"";
48 }
49 out << "]";
50}
51} // namespace
52
54 static CommandRegistry instance;
55 static bool initialized = false;
56 if (!initialized) {
57 instance.RegisterAllCommands();
58 initialized = true;
59 }
60 return instance;
61}
62
64 std::unique_ptr<resources::CommandHandler> handler,
65 const CommandMetadata& metadata) {
66 std::string name = handler->GetName();
67
68 // Store metadata
69 metadata_[name] = metadata;
70
71 // Register aliases
72 for (const auto& alias : metadata.aliases) {
73 aliases_[alias] = name;
74 }
75
76 // Store handler
77 handlers_[name] = std::move(handler);
78}
79
80resources::CommandHandler* CommandRegistry::Get(const std::string& name) const {
81 // Check direct name
82 auto it = handlers_.find(name);
83 if (it != handlers_.end()) {
84 return it->second.get();
85 }
86
87 // Check aliases
88 auto alias_it = aliases_.find(name);
89 if (alias_it != aliases_.end()) {
90 auto handler_it = handlers_.find(alias_it->second);
91 if (handler_it != handlers_.end()) {
92 return handler_it->second.get();
93 }
94 }
95
96 return nullptr;
97}
98
100 const std::string& name) const {
101 // Resolve alias first
102 std::string canonical_name = name;
103 auto alias_it = aliases_.find(name);
104 if (alias_it != aliases_.end()) {
105 canonical_name = alias_it->second;
106 }
107
108 auto it = metadata_.find(canonical_name);
109 return (it != metadata_.end()) ? &it->second : nullptr;
110}
111
113 const std::string& category) const {
114 std::vector<std::string> result;
115 for (const auto& [name, metadata] : metadata_) {
116 if (metadata.category == category) {
117 result.push_back(name);
118 }
119 }
120 return result;
121}
122
123std::vector<std::string> CommandRegistry::GetCategories() const {
124 std::vector<std::string> categories;
125 for (const auto& [_, metadata] : metadata_) {
126 if (std::find(categories.begin(), categories.end(), metadata.category) ==
127 categories.end()) {
128 categories.push_back(metadata.category);
129 }
130 }
131 return categories;
132}
133
134std::vector<std::string> CommandRegistry::GetAgentCommands() const {
135 std::vector<std::string> result;
136 for (const auto& [name, metadata] : metadata_) {
137 if (metadata.available_to_agent) {
138 result.push_back(name);
139 }
140 }
141 return result;
142}
143
145 std::ostringstream out;
146 out << "{\n \"commands\": [\n";
147
148 bool first = true;
149 for (const auto& [_, metadata] : metadata_) {
150 if (!first) out << ",\n";
151 first = false;
152
153 out << " {\n";
154 out << " \"name\": \"" << EscapeJson(metadata.name) << "\",\n";
155 out << " \"category\": \"" << EscapeJson(metadata.category) << "\",\n";
156 out << " \"description\": \"" << EscapeJson(metadata.description) << "\",\n";
157 out << " \"usage\": \"" << EscapeJson(metadata.usage) << "\",\n";
158 out << " \"available_to_agent\": "
159 << (metadata.available_to_agent ? "true" : "false") << ",\n";
160 out << " \"requires_rom\": " << (metadata.requires_rom ? "true" : "false")
161 << ",\n";
162 out << " \"requires_grpc\": "
163 << (metadata.requires_grpc ? "true" : "false") << ",\n";
164 if (!metadata.todo_reference.empty()) {
165 out << " \"todo_reference\": \""
166 << EscapeJson(metadata.todo_reference) << "\",\n";
167 } else {
168 out << " \"todo_reference\": \"\",\n";
169 }
170 out << " \"aliases\": ";
171 AppendStringArray(out, metadata.aliases);
172 out << ",\n";
173 out << " \"examples\": ";
174 AppendStringArray(out, metadata.examples);
175 out << "\n";
176 out << " }";
177 }
178
179 out << "\n ]\n}";
180 return out.str();
181}
182
183std::string CommandRegistry::GenerateHelp(const std::string& name) const {
184 auto* metadata = GetMetadata(name);
185 if (!metadata) {
186 return absl::StrFormat("Command '%s' not found", name);
187 }
188
189 std::ostringstream help;
190 help << "\n\033[1;36m" << metadata->name << "\033[0m - "
191 << metadata->description << "\n\n";
192 help << "\033[1;33mUsage:\033[0m\n";
193 help << " " << metadata->usage << "\n\n";
194
195 if (!metadata->examples.empty()) {
196 help << "\033[1;33mExamples:\033[0m\n";
197 for (const auto& example : metadata->examples) {
198 help << " " << example << "\n";
199 }
200 help << "\n";
201 }
202
203 if (metadata->requires_rom) {
204 help << "\033[1;33mRequires:\033[0m ROM file (--rom=<path>)\n";
205 }
206 if (metadata->requires_grpc) {
207 help << "\033[1;33mRequires:\033[0m YAZE running with gRPC enabled\n";
208 }
209
210 if (!metadata->aliases.empty()) {
211 help << "\n\033[1;33mAliases:\033[0m "
212 << absl::StrJoin(metadata->aliases, ", ") << "\n";
213 }
214
215 return help.str();
216}
217
219 const std::string& category) const {
220 auto commands = GetCommandsInCategory(category);
221 if (commands.empty()) {
222 return absl::StrFormat("No commands in category '%s'", category);
223 }
224
225 std::ostringstream help;
226 help << "\n\033[1;36m" << category << " commands:\033[0m\n\n";
227
228 for (const auto& cmd : commands) {
229 auto* metadata = GetMetadata(cmd);
230 if (metadata) {
231 help << " \033[1;33m" << cmd << "\033[0m\n";
232 help << " " << metadata->description << "\n";
233 if (!metadata->usage.empty()) {
234 help << " Usage: " << metadata->usage << "\n";
235 }
236 help << "\n";
237 }
238 }
239
240 return help.str();
241}
242
244 std::ostringstream help;
245 help << "\n\033[1;36mAll z3ed Commands:\033[0m\n\n";
246
247 auto categories = GetCategories();
248 for (const auto& category : categories) {
249 help << GenerateCategoryHelp(category);
250 }
251
252 return help.str();
253}
254
255absl::Status CommandRegistry::Execute(const std::string& name,
256 const std::vector<std::string>& args,
257 Rom* rom_context,
258 std::string* captured_output) {
259 auto* handler = Get(name);
260 if (!handler) {
261 return absl::NotFoundError(absl::StrFormat("Command '%s' not found", name));
262 }
263
264 return handler->Run(args, rom_context, captured_output);
265}
266
267bool CommandRegistry::HasCommand(const std::string& name) const {
268 return Get(name) != nullptr;
269}
270
272 // Get all command handlers from factory
273 auto all_handlers = handlers::CreateAllCommandHandlers();
274
275 for (auto& handler : all_handlers) {
276 std::string name = handler->GetName();
277
278 // Build metadata from handler
279 auto descriptor = handler->Describe();
280 CommandMetadata metadata;
281 metadata.name = name;
282 metadata.usage = handler->GetUsage();
283 metadata.available_to_agent = true; // Most commands available to agent
284 metadata.requires_rom = handler->RequiresRom();
285 metadata.requires_grpc = false;
286
287 // Categorize and enhance metadata based on command type
288 if (name.find("resource-") == 0) {
289 metadata.category = "resource";
290 metadata.description = "Resource inspection and search";
291 if (name == "resource-list") {
292 metadata.examples = {"z3ed resource-list --type=dungeon --format=json",
293 "z3ed resource-list --type=sprite --format=table"};
294 }
295 } else if (name.find("dungeon-") == 0) {
296 metadata.category = "dungeon";
297 metadata.description = "Dungeon inspection and editing";
298 if (name == "dungeon-describe-room") {
299 metadata.examples = {
300 "z3ed dungeon-describe-room --room=5 --format=json"};
301 }
302 } else if (name.find("overworld-") == 0) {
303 metadata.category = "overworld";
304 metadata.description = "Overworld inspection and editing";
305 if (name == "overworld-find-tile") {
306 metadata.examples = {
307 "z3ed overworld-find-tile --tile=0x42 --format=json"};
308 }
309 } else if (name.find("emulator-") == 0) {
310 metadata.category = "emulator";
311 metadata.description = "Emulator control and debugging";
312 metadata.requires_grpc = true;
313 if (name == "emulator-set-breakpoint") {
314 metadata.examples = {
315 "z3ed emulator-set-breakpoint --address=0x83D7 --description='NMI "
316 "handler'"};
317 }
318 } else if (name.find("gui-") == 0) {
319 metadata.category = "gui";
320 metadata.description = "GUI automation";
321 metadata.requires_grpc = true;
322 } else if (name.find("hex-") == 0) {
323 metadata.category = "graphics";
324 metadata.description = "Hex data manipulation";
325 } else if (name.find("palette-") == 0) {
326 metadata.category = "graphics";
327 metadata.description = "Palette operations";
328 } else if (name.find("sprite-") == 0) {
329 metadata.category = "graphics";
330 metadata.description = "Sprite operations";
331 } else if (name.find("message-") == 0 || name.find("dialogue-") == 0) {
332 metadata.category = "game";
333 metadata.description = name.find("message-") == 0 ? "Message inspection"
334 : "Dialogue inspection";
335 } else if (name.find("music-") == 0) {
336 metadata.category = "game";
337 metadata.description = "Music/audio inspection";
338 } else if (name == "simple-chat" || name == "chat") {
339 metadata.category = "agent";
340 metadata.description = "AI conversational agent";
341 metadata.available_to_agent = false; // Meta-command
342 metadata.requires_rom = false;
343 metadata.examples = {
344 "z3ed simple-chat --rom=zelda3.sfc",
345 "z3ed simple-chat \"What dungeons exist?\" --rom=zelda3.sfc"};
346 } else if (name.find("tools-") == 0) {
347 metadata.category = "tools";
348 if (name == "tools-list") {
349 metadata.description = "List available test helper tools";
350 metadata.requires_rom = false;
351 metadata.available_to_agent = true;
352 } else if (name == "tools-harness-state") {
353 metadata.description = "Generate WRAM state for test harnesses";
354 metadata.examples = {
355 "z3ed tools-harness-state --rom=zelda3.sfc --output=state.h"};
356 } else if (name == "tools-extract-values") {
357 metadata.description = "Extract vanilla ROM values";
358 metadata.examples = {"z3ed tools-extract-values --rom=zelda3.sfc"};
359 } else if (name == "tools-extract-golden") {
360 metadata.description = "Extract comprehensive golden data for testing";
361 metadata.examples = {
362 "z3ed tools-extract-golden --rom=zelda3.sfc --output=golden.h"};
363 } else if (name == "tools-patch-v3") {
364 metadata.description = "Create v3 ZSCustomOverworld patched ROM";
365 metadata.examples = {
366 "z3ed tools-patch-v3 --rom=zelda3.sfc --output=patched.sfc"};
367 } else {
368 metadata.description = "Test helper tool";
369 }
370 } else {
371 metadata.category = "misc";
372 metadata.description = "Miscellaneous command";
373 }
374
375 // Prefer handler-provided summary if present
376 if (!descriptor.summary.empty() &&
377 descriptor.summary != "Command summary not provided.") {
378 metadata.description = descriptor.summary;
379 }
380
381 // Keep TODO reference if supplied by handler
382 if (!descriptor.todo_reference.empty() &&
383 descriptor.todo_reference != "todo#unassigned") {
384 metadata.todo_reference = descriptor.todo_reference;
385 }
386
387 Register(std::move(handler), metadata);
388 }
389}
390
391} // namespace cli
392} // 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:24
Single source of truth for all z3ed commands.
std::map< std::string, std::string > aliases_
std::vector< std::string > GetCommandsInCategory(const std::string &category) const
Get all commands in a category.
static CommandRegistry & Instance()
std::string GenerateCategoryHelp(const std::string &category) const
Generate category help text.
std::string ExportFunctionSchemas() const
Export function schemas for AI tool calling (JSON)
const CommandMetadata * GetMetadata(const std::string &name) const
Get command metadata.
std::string GenerateCompleteHelp() const
Generate complete help text (all commands)
std::vector< std::string > GetAgentCommands() const
Get all commands available to AI agents.
std::map< std::string, CommandMetadata > metadata_
absl::Status Execute(const std::string &name, const std::vector< std::string > &args, Rom *rom_context=nullptr, std::string *captured_output=nullptr)
Execute a command by name.
std::map< std::string, std::unique_ptr< resources::CommandHandler > > handlers_
resources::CommandHandler * Get(const std::string &name) const
Get a command handler by name or alias.
bool HasCommand(const std::string &name) const
Check if command exists.
std::vector< std::string > GetCategories() const
Get all categories.
void Register(std::unique_ptr< resources::CommandHandler > handler, const CommandMetadata &metadata)
Register a command handler.
std::string GenerateHelp(const std::string &name) const
Generate help text for a command.
Base class for CLI command handlers.
void AppendStringArray(std::ostringstream &out, const std::vector< std::string > &values)
std::vector< std::unique_ptr< resources::CommandHandler > > CreateAllCommandHandlers()
Factory function to create all command handlers (CLI + agent)