yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
cli.cc
Go to the documentation of this file.
1#include "cli/cli.h"
2
3#include <iostream>
4
5#include "absl/strings/str_cat.h"
6#include "absl/strings/str_format.h"
7#include "absl/strings/str_join.h"
8#include "absl/strings/match.h"
11#include "cli/z3ed_ascii_logo.h"
12
13namespace yaze {
14namespace cli {
15
16// Forward declaration
17namespace handlers {
18#ifdef YAZE_ENABLE_AGENT_CLI
19absl::Status HandleAgentCommand(const std::vector<std::string>& args);
20#endif
21}
22
24 // Commands are now managed by CommandRegistry singleton
25}
26
27absl::Status ModernCLI::Run(int argc, char* argv[]) {
28 if (argc < 2) {
29 ShowHelp();
30 return absl::OkStatus();
31 }
32
33 std::vector<std::string> args;
34 for (int i = 1; i < argc; ++i) {
35 args.push_back(argv[i]);
36 }
37
38 auto& registry = CommandRegistry::Instance();
39
40 auto show_rom_subcommand_help = [&]() {
41 std::cout << "\n\033[1;36mROM subcommands:\033[0m\n";
42 std::cout << " z3ed rom info --rom=<path>\n";
43 std::cout << " z3ed rom validate --rom=<path>\n";
44 std::cout << " z3ed rom read --address=0x1000 --length=16 --rom=<path>\n";
45 std::cout << " z3ed rom write --address=0x1000 --value=0xFF --rom=<path>\n";
46 std::cout << " z3ed rom diff --rom_a=<a> --rom_b=<b>\n";
47 std::cout << " z3ed rom compare --rom=<path> --baseline=<path>\n";
48 std::cout << " z3ed rom doctor --rom=<path>\n\n";
49 std::cout << "Equivalent direct commands are available as `rom-*` entries:\n";
50 std::cout << registry.GenerateCategoryHelp("rom") << "\n";
51 };
52
53 auto show_debug_subcommand_help = []() {
54 std::cout << "\n\033[1;36mDebug subcommands:\033[0m\n";
55 std::cout << " z3ed debug state [--backend=mesen|grpc]\n";
56 std::cout << " z3ed debug sprites [--backend=mesen]\n";
57 std::cout << " z3ed debug cpu [--backend=mesen]\n";
58 std::cout << " z3ed debug mem read --address=<addr> [--length=<n>]\n";
59 std::cout << " z3ed debug mem write --address=<addr> --value=<hex>\n";
60 std::cout << " z3ed debug disasm --address=<addr> [--count=<n>]\n";
61 std::cout << " z3ed debug trace --address=<addr> [--count=<n>]\n";
62 std::cout << " z3ed debug breakpoint --address=<addr>\n";
63 std::cout << " z3ed debug control --action=<pause|resume|step|reset>\n";
64 std::cout << " z3ed debug reset [--backend=mesen|grpc]\n\n";
65 std::cout << "Tip: use `z3ed <debug-command> --help` for command-specific flags.\n";
66 };
67
68 if (args[0] == "help") {
69 if (args.size() > 1) {
70 const std::string& target = args[1];
71 if (target == "all") {
72 std::cout << registry.GenerateCompleteHelp() << "\n";
73 } else if (registry.HasCommand(target)) {
74 std::cout << registry.GenerateHelp(target) << "\n";
75 } else {
76 ShowCategoryHelp(target);
77 }
78 } else {
79 ShowHelp();
80 }
81 return absl::OkStatus();
82 }
83
84 // Special case: "agent" command routes to HandleAgentCommand
85 if (args[0] == "agent") {
86 if (args.size() < 2 || args[1] == "--help" || args[1] == "-h") {
87#ifdef YAZE_ENABLE_AGENT_CLI
88 std::cout << "\n\033[1;36mAgent command:\033[0m\n";
89 std::cout << " z3ed agent <subcommand> [flags]\n";
90 std::cout << " z3ed agent --help\n";
91 std::cout << "\nUse `z3ed agent <subcommand> --help` for scoped details.\n";
92#else
93 std::cout << "\n\033[1;36mAgent command:\033[0m\n";
94 std::cout << " This build was compiled without agent CLI support.\n";
95 std::cout << " Rebuild with `YAZE_ENABLE_AGENT_CLI` to enable `z3ed agent`.\n";
96#endif
97 return absl::OkStatus();
98 }
99#ifdef YAZE_ENABLE_AGENT_CLI
100 std::vector<std::string> agent_args(args.begin() + 1, args.end());
101 return handlers::HandleAgentCommand(agent_args);
102#else
103 return absl::FailedPreconditionError(
104 "Agent CLI is disabled in this build");
105#endif
106 }
107
108 // Special case: "rom" subcommands (rom read/write/info/validate/etc.)
109 if (args[0] == "rom") {
110 if (args.size() < 2 || args[1] == "--help" || args[1] == "-h") {
111 show_rom_subcommand_help();
112 return absl::OkStatus();
113 }
114
115 const std::string& sub = args[1];
116 std::string mapped;
117 if (sub == "read") {
118 mapped = "rom-read";
119 } else if (sub == "write") {
120 mapped = "rom-write";
121 } else if (sub == "info") {
122 mapped = "rom-info";
123 } else if (sub == "validate") {
124 mapped = "rom-validate";
125 } else if (sub == "diff") {
126 mapped = "rom-diff";
127 } else if (sub == "compare") {
128 mapped = "rom-compare";
129 } else if (sub == "doctor") {
130 mapped = "rom-doctor";
131 } else {
132 std::cerr << "\n\033[1;31mError:\033[0m Unknown rom subcommand: " << sub
133 << "\n";
134 show_rom_subcommand_help();
135 return absl::InvalidArgumentError(
136 absl::StrCat("Unknown rom subcommand: ", sub));
137 }
138
139 std::vector<std::string> command_args(args.begin() + 2, args.end());
140 if (registry.HasCommand(mapped)) {
141 return registry.Execute(mapped, command_args, nullptr);
142 }
143 return absl::NotFoundError(
144 absl::StrCat("Command not found for rom subcommand: ", mapped));
145 }
146
147 // Special case: "debug" subcommands (Mesen2 primary, gRPC optional)
148 if (args[0] == "debug") {
149 if (args.size() < 2 || args[1] == "--help" || args[1] == "-h") {
150 show_debug_subcommand_help();
151 return absl::OkStatus();
152 }
153
154 std::string backend = "mesen";
155 std::vector<std::string> filtered_args;
156 filtered_args.reserve(args.size());
157 filtered_args.push_back(args[0]);
158 for (size_t i = 1; i < args.size(); ++i) {
159 const std::string& token = args[i];
160 if (absl::StartsWith(token, "--backend=") ||
161 absl::StartsWith(token, "--debug-backend=")) {
162 backend = token.substr(token.find('=') + 1);
163 continue;
164 }
165 if (token == "--backend" || token == "--debug-backend") {
166 if (i + 1 < args.size()) {
167 backend = args[i + 1];
168 ++i;
169 continue;
170 }
171 }
172 filtered_args.push_back(token);
173 }
174
175 const std::string& topic = filtered_args[1];
176 std::string mapped;
177
178 auto require_grpc = [&]() -> absl::Status {
179 return absl::FailedPreconditionError(
180 "Requested gRPC backend, but this debug subcommand is not supported");
181 };
182
183 if (topic == "state" || topic == "gamestate") {
184 mapped = (backend == "grpc") ? "emulator-get-state" : "mesen-gamestate";
185 } else if (topic == "sprites") {
186 if (backend == "grpc") return require_grpc();
187 mapped = "mesen-sprites";
188 } else if (topic == "cpu") {
189 if (backend == "grpc") return require_grpc();
190 mapped = "mesen-cpu";
191 } else if (topic == "mem" || topic == "memory") {
192 if (filtered_args.size() < 3) {
193 show_debug_subcommand_help();
194 return absl::InvalidArgumentError(
195 "debug mem requires read/write subcommand");
196 }
197 const std::string& action = filtered_args[2];
198 if (action == "read") {
199 mapped =
200 (backend == "grpc") ? "emulator-read-memory" : "mesen-memory-read";
201 filtered_args.erase(filtered_args.begin() + 2);
202 } else if (action == "write") {
203 mapped =
204 (backend == "grpc") ? "emulator-write-memory" : "mesen-memory-write";
205 filtered_args.erase(filtered_args.begin() + 2);
206 } else {
207 show_debug_subcommand_help();
208 return absl::InvalidArgumentError(
209 "debug mem subcommand must be read or write");
210 }
211 } else if (topic == "disasm") {
212 if (backend == "grpc") return require_grpc();
213 mapped = "mesen-disasm";
214 } else if (topic == "trace") {
215 if (backend == "grpc") return require_grpc();
216 mapped = "mesen-trace";
217 } else if (topic == "breakpoint" || topic == "bp") {
218 if (backend == "grpc") return require_grpc();
219 mapped = "mesen-breakpoint";
220 } else if (topic == "control") {
221 if (backend == "grpc") return require_grpc();
222 mapped = "mesen-control";
223 } else if (topic == "reset") {
224 mapped = (backend == "grpc") ? "emulator-reset" : "mesen-control";
225 if (backend != "grpc") {
226 filtered_args.push_back("--action=reset");
227 }
228 } else {
229 show_debug_subcommand_help();
230 return absl::InvalidArgumentError(
231 absl::StrCat("Unknown debug subcommand: ", topic));
232 }
233
234 std::vector<std::string> command_args(filtered_args.begin() + 2,
235 filtered_args.end());
236 if (registry.HasCommand(mapped)) {
237 return registry.Execute(mapped, command_args, nullptr);
238 }
239 return absl::NotFoundError(
240 absl::StrCat("Command not found for debug subcommand: ", mapped));
241 }
242
243 std::string command_name = args[0];
244 std::vector<std::string> command_args(args.begin() + 1, args.end());
245
246 if (registry.HasCommand(command_name)) {
247 return registry.Execute(command_name, command_args, nullptr);
248 }
249
250 if (!registry.GetCommandsInCategory(command_name).empty()) {
251 ShowCategoryHelp(command_name);
252 return absl::OkStatus();
253 }
254
255 return absl::NotFoundError(absl::StrCat("Unknown command: ", command_name));
256}
257
259 auto& registry = CommandRegistry::Instance();
260 auto categories = registry.GetCategories();
261
262 std::cout << yaze::cli::GetColoredLogo() << "\n";
263 std::cout << "Z3ED - AI-Powered ROM Editor CLI\n\n";
264 std::cout << "Categories:\n";
265#ifdef YAZE_ENABLE_AGENT_CLI
266 std::cout << " agent - AI conversational agent + debugging tools\n";
267#endif
268 for (const auto& category : categories) {
269 auto commands = registry.GetCommandsInCategory(category);
270 std::cout << " " << category << " - " << commands.size() << " commands\n";
271 }
272 std::cout << "\nTotal: " << registry.Count() << " commands\n";
273 std::cout << "Use `z3ed help <command|category>` for scoped help.\n";
274 std::cout << "Use `z3ed --list-commands` for the full command list.\n";
275}
276
277void ModernCLI::ShowCategoryHelp(const std::string& category) const {
278 auto& registry = CommandRegistry::Instance();
279 auto commands = registry.GetCommandsInCategory(category);
280
281 std::cout << "Category: " << category << "\n\n";
282 if (commands.empty()) {
283 std::cout << " No commands in this category.\n";
284 return;
285 }
286
287 for (const auto& cmd_name : commands) {
288 auto* metadata = registry.GetMetadata(cmd_name);
289 if (!metadata) {
290 continue;
291 }
292
293 std::cout << " " << cmd_name << " - " << metadata->description << "\n";
294 if (!metadata->usage.empty()) {
295 std::cout << " Usage: " << metadata->usage << "\n";
296 }
297
298 std::string requirements;
299 if (metadata->requires_rom) {
300 requirements += "ROM ";
301 }
302 if (metadata->requires_grpc) {
303 requirements += "gRPC ";
304 }
305 if (!requirements.empty()) {
306 std::cout << " Requires: " << requirements << "\n";
307 }
308 std::cout << "\n";
309 }
310}
311
313 auto& registry = CommandRegistry::Instance();
314 auto categories = registry.GetCategories();
315
316 std::cout << "Z3ED Command Summary\n\n";
317 for (const auto& category : categories) {
318 auto commands = registry.GetCommandsInCategory(category);
319 for (const auto& cmd_name : commands) {
320 auto* metadata = registry.GetMetadata(cmd_name);
321 if (metadata) {
322 std::cout << " " << cmd_name << " [" << metadata->category
323 << "] - " << metadata->description << "\n";
324 }
325 }
326 }
327 std::cout << "\nTotal: " << registry.Count() << " commands across "
328 << categories.size() << " categories\n";
329}
330
332 const_cast<ModernCLI*>(this)->ShowHelp();
333}
334
335void ModernCLI::PrintCategoryHelp(const std::string& category) const {
336 const_cast<ModernCLI*>(this)->ShowCategoryHelp(category);
337}
338
340 const_cast<ModernCLI*>(this)->ShowCommandSummary();
341}
342
343} // namespace cli
344} // namespace yaze
static CommandRegistry & Instance()
absl::Status Run(int argc, char *argv[])
Definition cli.cc:27
void PrintCategoryHelp(const std::string &category) const
Definition cli.cc:335
void PrintCommandSummary() const
Definition cli.cc:339
void ShowCategoryHelp(const std::string &category) const
Definition cli.cc:277
void ShowCommandSummary() const
Definition cli.cc:312
void PrintTopLevelHelp() const
Definition cli.cc:331
absl::Status HandleAgentCommand(const std::vector< std::string > &arg_vec)
Unified agent command handler using CommandRegistry.
Definition agent.cc:113
std::string GetColoredLogo()