yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
cli_main.cc
Go to the documentation of this file.
1#include <cstdlib>
2#include <iostream>
3#include <optional>
4#include <string>
5#include <vector>
6
7#include "absl/flags/declare.h"
8#include "absl/flags/flag.h"
9#include "absl/strings/match.h"
10#include "absl/strings/str_format.h"
11#include "absl/strings/string_view.h"
12#include "cli/cli.h"
14#include "rom/rom.h"
15#ifndef __EMSCRIPTEN__
16#include "cli/tui/tui.h"
17#endif
18#include "cli/z3ed_ascii_logo.h"
19#include "yaze_config.h"
20
21#ifdef YAZE_HTTP_API_ENABLED
23#include "util/log.h"
24#endif
25
26// Define all CLI flags
27ABSL_FLAG(bool, tui, false, "Launch interactive Text User Interface");
28ABSL_DECLARE_FLAG(bool, quiet);
29ABSL_FLAG(bool, version, false, "Show version information");
30ABSL_FLAG(bool, self_test, false,
31 "Run self-test diagnostics to verify CLI functionality");
32#ifdef YAZE_HTTP_API_ENABLED
33ABSL_FLAG(int, http_port, 0,
34 "HTTP API server port (0 = disabled, default: 8080 when enabled)");
35ABSL_FLAG(std::string, http_host, "localhost",
36 "HTTP API server host (default: localhost)");
37#endif
38ABSL_DECLARE_FLAG(std::string, rom);
39ABSL_DECLARE_FLAG(std::string, ai_provider);
40ABSL_DECLARE_FLAG(std::string, ai_model);
41ABSL_DECLARE_FLAG(std::string, gemini_api_key);
42ABSL_DECLARE_FLAG(std::string, anthropic_api_key);
43ABSL_DECLARE_FLAG(std::string, ollama_host);
44ABSL_DECLARE_FLAG(std::string, gui_server_address);
45ABSL_DECLARE_FLAG(std::string, prompt_version);
46ABSL_DECLARE_FLAG(bool, use_function_calling);
47
48namespace {
49
51 std::cout << yaze::cli::GetColoredLogo() << "\n";
52 std::cout << absl::StrFormat(" Version %d.%d.%d\n", YAZE_VERSION_MAJOR,
53 YAZE_VERSION_MINOR, YAZE_VERSION_PATCH);
54 std::cout << " Yet Another Zelda3 Editor - Command Line Interface\n";
55 std::cout << " https://github.com/scawful/yaze\n\n";
56}
57
63 std::cout << "\n\033[1;36m=== z3ed Self-Test ===\033[0m\n\n";
64 int passed = 0;
65 int failed = 0;
66
67 auto run_test = [&](const char* name, bool condition) {
68 if (condition) {
69 std::cout << " \033[1;32m✓\033[0m " << name << "\n";
70 ++passed;
71 } else {
72 std::cout << " \033[1;31m✗\033[0m " << name << "\n";
73 ++failed;
74 }
75 };
76
77 // Test 1: Version info is available
78 run_test("Version info available",
79 YAZE_VERSION_MAJOR >= 0 && YAZE_VERSION_MINOR >= 0);
80
81 // Test 2: CLI instance can be created
82 {
83 bool cli_created = false;
84 try {
86 cli_created = true;
87 } catch (...) {
88 cli_created = false;
89 }
90 run_test("CLI instance creation", cli_created);
91 }
92
93 // Test 3: App context is accessible
94 run_test("App context accessible", true); // Always passes if we got here
95
96 // Test 4: ROM class can be instantiated
97 {
98 bool rom_ok = false;
99 try {
100 yaze::Rom test_rom;
101 rom_ok = true;
102 } catch (...) {
103 rom_ok = false;
104 }
105 run_test("ROM class instantiation", rom_ok);
106 }
107
108 // Test 5: Flag parsing works
109 run_test("Flag parsing functional", absl::GetFlag(FLAGS_self_test) == true);
110
111#ifdef YAZE_HTTP_API_ENABLED
112 // Test 6: HTTP API available (if compiled in)
113 run_test("HTTP API compiled in", true);
114#else
115 run_test("HTTP API not compiled (expected)", true);
116#endif
117
118 // Summary
119 std::cout << "\n\033[1;36m=== Results ===\033[0m\n";
120 std::cout << " Passed: \033[1;32m" << passed << "\033[0m\n";
121 std::cout << " Failed: \033[1;31m" << failed << "\033[0m\n";
122
123 if (failed == 0) {
124 std::cout << "\n\033[1;32mAll self-tests passed!\033[0m\n\n";
125 return EXIT_SUCCESS;
126 } else {
127 std::cout << "\n\033[1;31mSome self-tests failed.\033[0m\n\n";
128 return EXIT_FAILURE;
129 }
130}
131
133 auto& registry = yaze::cli::CommandRegistry::Instance();
134 auto categories = registry.GetCategories();
135
136 std::cout << yaze::cli::GetColoredLogo() << "\n";
137 std::cout
138 << " \033[1;37mYet Another Zelda3 Editor - AI-Powered CLI\033[0m\n\n";
139
140 std::cout << "\033[1;36mUSAGE:\033[0m\n";
141 std::cout << " z3ed [command] [flags]\n";
142 std::cout << " z3ed --tui # Interactive TUI mode\n";
143 std::cout << " z3ed help <command|category> # Scoped help\n";
144 std::cout << " z3ed --export-schemas # JSON schemas for agents\n";
145 std::cout << " z3ed --version # Show version\n\n";
146
147 std::cout << "\033[1;36mCATEGORIES:\033[0m\n";
148 for (const auto& category : categories) {
149 auto commands = registry.GetCommandsInCategory(category);
150 std::cout << " \033[1;33m" << category << "\033[0m (" << commands.size()
151 << " commands)\n";
152 }
153 std::cout << "\n";
154
155 std::cout << "\033[1;36mCOMMON FLAGS:\033[0m\n";
156 std::cout << " --rom=<path> Path to ROM file\n";
157 std::cout << " --tui Launch interactive TUI\n";
158 std::cout << " --quiet, -q Suppress output\n";
159 std::cout << " --ai_provider=<name> AI provider (auto, ollama, gemini, "
160 "openai,\n";
161 std::cout << " anthropic, mock)\n";
162 std::cout << " --ai_model=<name> Provider-specific model override\n";
163 std::cout << " --version Show version\n";
164 std::cout << " --self-test Run self-test diagnostics\n";
165 std::cout << " --help <scope> Show help for command or category\n";
166 std::cout << " --export-schemas Export command schemas as JSON\n";
167#ifdef YAZE_HTTP_API_ENABLED
168 std::cout << " --http-port=<port> HTTP API server port (0=disabled)\n";
169 std::cout
170 << " --http-host=<host> HTTP API server host (default: localhost)\n";
171#endif
172 std::cout << "\n";
173
174 std::cout << "\033[1;36mEXAMPLES:\033[0m\n";
175 std::cout << " z3ed agent simple-chat --rom=zelda3.sfc\n";
176 std::cout << " z3ed rom-info --rom=zelda3.sfc\n";
177 std::cout
178 << " z3ed message-search --rom=zelda3.sfc --query=\"Master Sword\"\n";
179 std::cout << " z3ed dungeon-export --rom=zelda3.sfc --id=1\n\n";
180
181 std::cout << "For detailed help: z3ed help <command>\n";
182 std::cout << "For all commands: z3ed --list-commands\n\n";
183}
184
186 std::vector<char*> positional;
187 bool show_help = false;
188 bool show_version = false;
189 bool list_commands = false;
190 bool self_test = false;
191 bool export_schemas = false;
192 std::optional<std::string> help_target;
193 std::optional<std::string> error;
194};
195
196ParsedGlobals ParseGlobalFlags(int argc, char* argv[]) {
197 ParsedGlobals result;
198 if (argc <= 0 || argv == nullptr) {
199 result.error = "Invalid argv provided";
200 return result;
201 }
202
203 result.positional.reserve(argc);
204 result.positional.push_back(argv[0]);
205
206 bool passthrough = false;
207 for (int i = 1; i < argc; ++i) {
208 char* current = argv[i];
209 absl::string_view token(current);
210
211 if (!passthrough) {
212 if (token == "--") {
213 passthrough = true;
214 continue;
215 }
216
217 // Help flags
218 if (absl::StartsWith(token, "--help=")) {
219 std::string target(token.substr(7));
220 if (!target.empty()) {
221 result.help_target = target;
222 } else {
223 result.show_help = true;
224 }
225 continue;
226 }
227 if (token == "--help" || token == "-h") {
228 if (i + 1 < argc && argv[i + 1][0] != '-') {
229 result.help_target = std::string(argv[++i]);
230 } else {
231 result.show_help = true;
232 }
233 continue;
234 }
235
236 // Version flag
237 if (token == "--version" || token == "-v") {
238 result.show_version = true;
239 continue;
240 }
241
242 // List commands
243 if (token == "--list-commands" || token == "--list") {
244 result.list_commands = true;
245 continue;
246 }
247
248 // Schema export
249 if (token == "--export-schemas" || token == "--export_schemas") {
250 result.export_schemas = true;
251 continue;
252 }
253
254 // Self-test mode
255 if (token == "--self-test" || token == "--selftest") {
256 result.self_test = true;
257 continue;
258 }
259
260 // TUI mode
261 if (token == "--tui" || token == "--interactive") {
262 absl::SetFlag(&FLAGS_tui, true);
263 continue;
264 }
265
266 // Quiet mode
267 if (token == "--quiet" || token == "-q") {
268 absl::SetFlag(&FLAGS_quiet, true);
269 continue;
270 }
271 if (absl::StartsWith(token, "--quiet=")) {
272 std::string value(token.substr(8));
273 absl::SetFlag(&FLAGS_quiet, value == "true" || value == "1");
274 continue;
275 }
276
277 // ROM path
278 if (absl::StartsWith(token, "--rom=")) {
279 absl::SetFlag(&FLAGS_rom, std::string(token.substr(6)));
280 continue;
281 }
282 if (token == "--rom") {
283 if (i + 1 >= argc) {
284 result.error = "--rom flag requires a value";
285 return result;
286 }
287 absl::SetFlag(&FLAGS_rom, std::string(argv[++i]));
288 continue;
289 }
290
291 // AI provider flags
292 if (absl::StartsWith(token, "--ai_provider=") ||
293 absl::StartsWith(token, "--ai-provider=")) {
294 size_t eq_pos = token.find('=');
295 absl::SetFlag(&FLAGS_ai_provider,
296 std::string(token.substr(eq_pos + 1)));
297 continue;
298 }
299 if (token == "--ai_provider" || token == "--ai-provider") {
300 if (i + 1 >= argc) {
301 result.error = "--ai-provider flag requires a value";
302 return result;
303 }
304 absl::SetFlag(&FLAGS_ai_provider, std::string(argv[++i]));
305 continue;
306 }
307
308 if (absl::StartsWith(token, "--ai_model=") ||
309 absl::StartsWith(token, "--ai-model=")) {
310 size_t eq_pos = token.find('=');
311 absl::SetFlag(&FLAGS_ai_model, std::string(token.substr(eq_pos + 1)));
312 continue;
313 }
314 if (token == "--ai_model" || token == "--ai-model") {
315 if (i + 1 >= argc) {
316 result.error = "--ai-model flag requires a value";
317 return result;
318 }
319 absl::SetFlag(&FLAGS_ai_model, std::string(argv[++i]));
320 continue;
321 }
322
323 if (absl::StartsWith(token, "--gemini_api_key=") ||
324 absl::StartsWith(token, "--gemini-api-key=")) {
325 size_t eq_pos = token.find('=');
326 absl::SetFlag(&FLAGS_gemini_api_key,
327 std::string(token.substr(eq_pos + 1)));
328 continue;
329 }
330 if (token == "--gemini_api_key" || token == "--gemini-api-key") {
331 if (i + 1 >= argc) {
332 result.error = "--gemini-api-key flag requires a value";
333 return result;
334 }
335 absl::SetFlag(&FLAGS_gemini_api_key, std::string(argv[++i]));
336 continue;
337 }
338
339 if (absl::StartsWith(token, "--anthropic_api_key=") ||
340 absl::StartsWith(token, "--anthropic-api-key=")) {
341 size_t eq_pos = token.find('=');
342 absl::SetFlag(&FLAGS_anthropic_api_key,
343 std::string(token.substr(eq_pos + 1)));
344 continue;
345 }
346 if (token == "--anthropic_api_key" || token == "--anthropic-api-key") {
347 if (i + 1 >= argc) {
348 result.error = "--anthropic-api-key flag requires a value";
349 return result;
350 }
351 absl::SetFlag(&FLAGS_anthropic_api_key, std::string(argv[++i]));
352 continue;
353 }
354
355 if (absl::StartsWith(token, "--gui_server_address=") ||
356 absl::StartsWith(token, "--gui-server-address=")) {
357 size_t eq_pos = token.find('=');
358 absl::SetFlag(&FLAGS_gui_server_address,
359 std::string(token.substr(eq_pos + 1)));
360 continue;
361 }
362 if (token == "--gui_server_address" || token == "--gui-server-address") {
363 if (i + 1 >= argc) {
364 result.error = "--gui-server-address flag requires a value";
365 return result;
366 }
367 absl::SetFlag(&FLAGS_gui_server_address, std::string(argv[++i]));
368 continue;
369 }
370
371 if (absl::StartsWith(token, "--ollama_host=") ||
372 absl::StartsWith(token, "--ollama-host=")) {
373 size_t eq_pos = token.find('=');
374 absl::SetFlag(&FLAGS_ollama_host,
375 std::string(token.substr(eq_pos + 1)));
376 continue;
377 }
378 if (token == "--ollama_host" || token == "--ollama-host") {
379 if (i + 1 >= argc) {
380 result.error = "--ollama-host flag requires a value";
381 return result;
382 }
383 absl::SetFlag(&FLAGS_ollama_host, std::string(argv[++i]));
384 continue;
385 }
386
387 if (absl::StartsWith(token, "--prompt_version=") ||
388 absl::StartsWith(token, "--prompt-version=")) {
389 size_t eq_pos = token.find('=');
390 absl::SetFlag(&FLAGS_prompt_version,
391 std::string(token.substr(eq_pos + 1)));
392 continue;
393 }
394 if (token == "--prompt_version" || token == "--prompt-version") {
395 if (i + 1 >= argc) {
396 result.error = "--prompt-version flag requires a value";
397 return result;
398 }
399 absl::SetFlag(&FLAGS_prompt_version, std::string(argv[++i]));
400 continue;
401 }
402
403 if (absl::StartsWith(token, "--use_function_calling=") ||
404 absl::StartsWith(token, "--use-function-calling=")) {
405 size_t eq_pos = token.find('=');
406 std::string value(token.substr(eq_pos + 1));
407 absl::SetFlag(&FLAGS_use_function_calling,
408 value == "true" || value == "1");
409 continue;
410 }
411 if (token == "--use_function_calling" ||
412 token == "--use-function-calling") {
413 if (i + 1 >= argc) {
414 result.error = "--use-function-calling flag requires a value";
415 return result;
416 }
417 std::string value(argv[++i]);
418 absl::SetFlag(&FLAGS_use_function_calling,
419 value == "true" || value == "1");
420 continue;
421 }
422
423#ifdef YAZE_HTTP_API_ENABLED
424 // HTTP server flags
425 if (absl::StartsWith(token, "--http-port=") ||
426 absl::StartsWith(token, "--http_port=")) {
427 size_t eq_pos = token.find('=');
428 try {
429 int port = std::stoi(std::string(token.substr(eq_pos + 1)));
430 absl::SetFlag(&FLAGS_http_port, port);
431 } catch (...) {
432 result.error = "--http-port requires an integer value";
433 return result;
434 }
435 continue;
436 }
437 if (token == "--http-port" || token == "--http_port") {
438 if (i + 1 >= argc) {
439 result.error = "--http-port flag requires a value";
440 return result;
441 }
442 try {
443 int port = std::stoi(std::string(argv[++i]));
444 absl::SetFlag(&FLAGS_http_port, port);
445 } catch (...) {
446 result.error = "--http-port requires an integer value";
447 return result;
448 }
449 continue;
450 }
451
452 if (absl::StartsWith(token, "--http-host=") ||
453 absl::StartsWith(token, "--http_host=")) {
454 size_t eq_pos = token.find('=');
455 absl::SetFlag(&FLAGS_http_host, std::string(token.substr(eq_pos + 1)));
456 continue;
457 }
458 if (token == "--http-host" || token == "--http_host") {
459 if (i + 1 >= argc) {
460 result.error = "--http-host flag requires a value";
461 return result;
462 }
463 absl::SetFlag(&FLAGS_http_host, std::string(argv[++i]));
464 continue;
465 }
466#endif
467 }
468
469 result.positional.push_back(current);
470 }
471
472 return result;
473}
474
475} // namespace
476
477int main(int argc, char* argv[]) {
478 // Parse global flags
479 ParsedGlobals globals = ParseGlobalFlags(argc, argv);
480
481 if (globals.error.has_value()) {
482 std::cerr << "Error: " << *globals.error << "\n";
483 std::cerr << "Use --help for usage information.\n";
484 return EXIT_FAILURE;
485 }
486
487 // Handle version flag
488 if (globals.show_version) {
489 PrintVersion();
490 return EXIT_SUCCESS;
491 }
492
493 // Handle self-test flag
494 if (globals.self_test) {
495 absl::SetFlag(&FLAGS_self_test, true); // Ensure flag is set for test
496 return RunSelfTest();
497 }
498
499 auto& registry = yaze::cli::CommandRegistry::Instance();
500
501 if (globals.export_schemas) {
502 std::cout << registry.ExportFunctionSchemas() << "\n";
503 return EXIT_SUCCESS;
504 }
505
506#ifdef YAZE_HTTP_API_ENABLED
507 // Start HTTP API server if requested
508 std::unique_ptr<yaze::cli::api::HttpServer> http_server;
509 int http_port = absl::GetFlag(FLAGS_http_port);
510
511 if (http_port > 0) {
512 std::string http_host = absl::GetFlag(FLAGS_http_host);
513 http_server = std::make_unique<yaze::cli::api::HttpServer>();
514
515 auto status = http_server->Start(http_port);
516 if (!status.ok()) {
517 std::cerr
518 << "\n\033[1;31mWarning:\033[0m Failed to start HTTP API server: "
519 << status.message() << "\n";
520 std::cerr << "Continuing without HTTP API...\n\n";
521 http_server.reset();
522 } else if (!absl::GetFlag(FLAGS_quiet)) {
523 std::cout << "\033[1;32m✓\033[0m HTTP API server started on " << http_host
524 << ":" << http_port << "\n";
525 std::cout << " Health check: http://" << http_host << ":" << http_port
526 << "/api/v1/health\n";
527 std::cout << " Models list: http://" << http_host << ":" << http_port
528 << "/api/v1/models\n\n";
529 }
530 } else if (http_port == 0 && !absl::GetFlag(FLAGS_quiet)) {
531 // Port 0 means explicitly disabled, only show message in verbose mode
532 }
533#endif
534
535 // Handle TUI mode
536#ifndef __EMSCRIPTEN__
537 if (absl::GetFlag(FLAGS_tui)) {
538 // Load ROM if specified before launching TUI
539 std::string rom_path = absl::GetFlag(FLAGS_rom);
540 if (!rom_path.empty()) {
541 auto status = yaze::cli::app_context.rom.LoadFromFile(rom_path);
542 if (!status.ok()) {
543 std::cerr << "\n\033[1;31mError:\033[0m Failed to load ROM: "
544 << status.message() << "\n";
545 // Continue to TUI anyway, user can load ROM from there
546 }
547 }
549 return EXIT_SUCCESS;
550 }
551#else
552 if (absl::GetFlag(FLAGS_tui)) {
553 std::cerr << "TUI mode is not available in WASM builds.\n";
554 return EXIT_FAILURE;
555 }
556#endif
557
558 // Create CLI instance
560
561 // Handle targeted help (command or category)
562 if (globals.help_target.has_value()) {
563 const std::string& target = *globals.help_target;
564 if (target == "all") {
565 std::cout << registry.GenerateCompleteHelp() << "\n";
566 } else if (registry.HasCommand(target)) {
567 std::cout << registry.GenerateHelp(target) << "\n";
568 } else if (!registry.GetCommandsInCategory(target).empty()) {
569 cli.PrintCategoryHelp(target);
570 } else {
571 std::cerr << "\n\033[1;31mError:\033[0m Unknown command or category '"
572 << target << "'\n";
573 std::cerr << "Use --list-commands for a full command list.\n";
574 return EXIT_FAILURE;
575 }
576 return EXIT_SUCCESS;
577 }
578
579 // Handle list commands
580 if (globals.list_commands) {
582 return EXIT_SUCCESS;
583 }
584
585 // Handle general help or no arguments
586 if (globals.show_help || globals.positional.size() <= 1) {
588 return EXIT_SUCCESS;
589 }
590
591 // Run CLI commands
592 auto status = cli.Run(static_cast<int>(globals.positional.size()),
593 globals.positional.data());
594
595 if (!status.ok()) {
596 std::cerr << "\n\033[1;31mError:\033[0m " << status.message() << "\n";
597 std::cerr << "Use --help for usage information.\n";
598 return EXIT_FAILURE;
599 }
600
601 return EXIT_SUCCESS;
602}
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
absl::Status LoadFromFile(const std::string &filename, const LoadOptions &options=LoadOptions::Defaults())
Definition rom.cc:75
static CommandRegistry & Instance()
absl::Status Run(int argc, char *argv[])
Definition cli.cc:28
void PrintCategoryHelp(const std::string &category) const
Definition cli.cc:276
void PrintCommandSummary() const
Definition cli.cc:280
int main(int argc, char *argv[])
Definition cli_main.cc:477
ABSL_FLAG(bool, tui, false, "Launch interactive Text User Interface")
ABSL_DECLARE_FLAG(bool, quiet)
int RunSelfTest()
Run self-test diagnostics to verify CLI functionality.
Definition cli_main.cc:62
ParsedGlobals ParseGlobalFlags(int argc, char *argv[])
Definition cli_main.cc:196
void ShowMain()
Definition tui.cc:974
std::string GetColoredLogo()
std::optional< std::string > help_target
Definition cli_main.cc:192