64 << status.message() <<
"\n";
65 std::cerr <<
"Use --help for usage information.\n";
70 return status.ok() ? EXIT_SUCCESS :
ReportFailure(status, style);
75 std::cout << absl::StrFormat(
" Version %d.%d.%d\n", YAZE_VERSION_MAJOR,
76 YAZE_VERSION_MINOR, YAZE_VERSION_PATCH);
77 std::cout <<
" Yet Another Zelda3 Editor - Command Line Interface\n";
78 std::cout <<
" https://github.com/scawful/yaze\n\n";
83 options.
style = style;
84#ifdef YAZE_ENABLE_AGENT_CLI
87#ifdef YAZE_HTTP_API_ENABLED
98 return std::vector<std::string>{*options.
help_target};
104 return std::vector<std::string>{};
110 const std::string saved_rom = absl::GetFlag(FLAGS_rom);
111 const std::vector<std::string> args = {
"--rom=self-test.sfc",
"rom-info",
114 const bool works = options.ok() &&
115 absl::GetFlag(FLAGS_rom) ==
"self-test.sfc" &&
116 options->positional ==
117 std::vector<std::string>{
"rom-info",
"--format=json"};
118 absl::SetFlag(&FLAGS_rom, saved_rom);
123 std::ostringstream out;
125 return !out.str().empty();
136 const Check checks[] = {
137 {
"Command registry populated", registry.
Count() > 0},
140 {
"Agent catalogue asset available",
143 {
"Overworld patch asset available",
145 "patches/Overworld/TailMapExpansion.asm")
150 << style.
Heading() <<
"=== z3ed Self-Test ===" << style.
Reset()
153 for (
const Check& check : checks) {
155 std::cout <<
" " << style.
Success() <<
"✓" << style.
Reset();
157 std::cout <<
" " << style.
Failure() <<
"✗" << style.
Reset();
160 std::cout <<
" " << check.name <<
"\n";
162#ifdef YAZE_HTTP_API_ENABLED
163 std::cout <<
" HTTP API: compiled in\n";
165 std::cout <<
" HTTP API: not compiled in\n";
168 const int passed =
static_cast<int>(std::size(checks)) - failed;
170 << style.
Heading() <<
"=== Results ===" << style.
Reset() <<
"\n";
171 std::cout <<
" Passed: " << style.
Success() << passed << style.
Reset()
173 std::cout <<
" Failed: " << style.
Failure() << failed << style.
Reset()
178 << style.
Success() <<
"All self-tests passed!" << style.
Reset()
183 << style.
Failure() <<
"Some self-tests failed." << style.
Reset()
188#ifdef YAZE_HTTP_API_ENABLED
189volatile std::sig_atomic_t g_shutdown_requested = 0;
191void RequestShutdown(
int) {
192 g_shutdown_requested = 1;
195void WaitForShutdownSignal() {
196 std::signal(SIGINT, RequestShutdown);
197 std::signal(SIGTERM, RequestShutdown);
198 while (g_shutdown_requested == 0) {
199 std::this_thread::sleep_for(std::chrono::milliseconds(200));
203bool IsLoopbackHost(absl::string_view host) {
204 return host ==
"localhost" || host ==
"127.0.0.1";
208class HttpApiSession {
210 absl::Status Start(
int port,
const std::string& host,
bool allow_commands) {
211 if (port <= 0 || port > 65535) {
212 return absl::InvalidArgumentError(
213 absl::StrFormat(
"--http-port must be 1-65535, got %d", port));
216 return absl::InvalidArgumentError(
"--http-host must not be empty");
218 server_ = std::make_unique<yaze::cli::api::HttpServer>();
219 if (render_service_) {
220 server_->SetRenderServiceSource(
221 [service = render_service_.get()] { return service; });
223 server_->SetCommandExecutionEnabled(allow_commands);
224 const bool loopback = IsLoopbackHost(host);
226 server_->SetBindAddress(host);
228 absl::Status status = server_->Start(port, loopback);
236 absl::Status LoadRenderService(
const std::string& rom_path) {
237 auto rom = std::make_unique<yaze::Rom>();
239 auto game_data = std::make_unique<yaze::zelda3::GameData>();
241 rom_ = std::move(rom);
242 game_data_ = std::move(game_data);
243 render_service_ = std::make_unique<yaze::app::service::RenderService>(
244 rom_.get(), game_data_.get());
245 return absl::OkStatus();
248 bool IsRunning()
const {
return server_ && server_->IsRunning(); }
251 std::unique_ptr<yaze::Rom> rom_;
252 std::unique_ptr<yaze::zelda3::GameData> game_data_;
253 std::unique_ptr<yaze::app::service::RenderService> render_service_;
256 std::unique_ptr<yaze::cli::api::HttpServer> server_;
262int main(
int argc,
char* argv[]) {
263 const AnsiStyle out_style = AnsiStyle::ForStream(stdout);
264 const AnsiStyle err_style = AnsiStyle::ForStream(stderr);
266 std::vector<std::string> args;
268 args.assign(argv + 1, argv + argc);
274 const GlobalOptions& options = *parsed;
276 if (options.show_version) {
280 if (options.self_test) {
284 CommandRegistry& registry = CommandRegistry::Instance();
285#ifdef YAZE_ENABLE_AGENT_CLI
289 if (options.export_schemas) {
290 std::cout << registry.ExportFunctionSchemas() <<
"\n";
296 if (std::optional<std::vector<std::string>> words =
HelpRequest(options)) {
297 return ExitCode(cli.PrintScopedHelp(*words), err_style);
299 if (options.list_commands) {
300 cli.PrintCommandSummary();
304#ifdef YAZE_HTTP_API_ENABLED
305 HttpApiSession http_api;
306 if (
const int port = absl::GetFlag(FLAGS_http_port); port != 0) {
307 const bool quiet = absl::GetFlag(FLAGS_quiet);
308 const std::string rom_path = absl::GetFlag(FLAGS_rom);
309 if (!rom_path.empty()) {
310 absl::Status status = http_api.LoadRenderService(rom_path);
311 if (!status.ok() && !quiet) {
312 std::cerr << err_style.Highlight() <<
"Warning:" << err_style.Reset()
313 <<
" HTTP render endpoints disabled: " << status.message()
318 const std::string host = absl::GetFlag(FLAGS_http_host);
319 const bool allow_commands = absl::GetFlag(FLAGS_http_allow_commands);
320 absl::Status status = http_api.Start(port, host, allow_commands);
323 << err_style.Failure() <<
"Warning:" << err_style.Reset()
324 <<
" Failed to start HTTP API server: " << status.message()
325 <<
"\nContinuing without HTTP API...\n\n";
327 if (!IsLoopbackHost(host)) {
328 std::cerr << err_style.Highlight() <<
"Warning:" << err_style.Reset()
329 <<
" HTTP API bound to " << host
330 <<
"; other machines can reach it.\n";
333 std::cout << out_style.Success() <<
"✓" << out_style.Reset()
334 <<
" HTTP API server started on " << host <<
":" << port
336 std::cout <<
" Health check: http://" << host <<
":" << port
337 <<
"/api/v1/health\n";
338 std::cout <<
" Models list: http://" << host <<
":" << port
339 <<
"/api/v1/models\n";
340 std::cout <<
" Command execution: "
343 :
"disabled (pass --http-allow-commands to enable)")
348 if (options.positional.empty() && http_api.IsRunning()) {
350 std::cout <<
"HTTP API server running. Press Ctrl+C to exit.\n";
352 WaitForShutdownSignal();
358 return ExitCode(cli.Run(options.positional), err_style);
int main(int argc, char *argv[])
ABSL_DECLARE_FLAG(bool, quiet)
int RunSelfTest(const AnsiStyle &style)
int ReportFailure(const absl::Status &status, const AnsiStyle &style)
void PrintVersion(const AnsiStyle &style)
int ExitCode(const absl::Status &status, const AnsiStyle &style)
bool OverviewRenders(CommandRegistry ®istry)
bool GlobalFlagParsingWorks()
std::optional< std::vector< std::string > > HelpRequest(const GlobalOptions &options)
Cli::Options MakeCliOptions(const AnsiStyle &style)
Rom * rom()
Get the current ROM instance.
::yaze::zelda3::GameData * game_data()
Get the current game data instance.