20#include <emscripten.h>
21#include <emscripten/bind.h>
23#include "absl/status/status.h"
24#include "absl/strings/ascii.h"
25#include "absl/strings/match.h"
26#include "absl/strings/numbers.h"
27#include "absl/strings/str_cat.h"
28#include "absl/strings/str_join.h"
29#include "absl/strings/str_split.h"
30#include "absl/strings/string_view.h"
31#include "absl/types/span.h"
41#include "nlohmann/json.hpp"
50constexpr char kTerminalUploadPath[] =
"/.yaze/roms/terminal_upload.sfc";
55 std::unique_ptr<yaze::cli::BrowserAIService> ai_service;
56 std::string last_output;
58 std::string model =
"gemini-2.5-flash";
61 bool initialized =
false;
70 void SetupAIService() {
71 yaze::cli::BrowserAIConfig config;
73 config.api_key = api_key;
75 config.api_base = api_base;
76 config.verbose =
false;
77 auto http_client = std::make_unique<yaze::net::EmscriptenHttpClient>();
78 ai_service = std::make_unique<yaze::cli::BrowserAIService>(
79 config, std::move(http_client));
81 ai_service->SetRomContext(rom);
85 void ConfigureAI(
const char* provider_value,
const char* model_value,
86 const char* api_base_value,
const char* api_key_value) {
88 provider = provider_value;
94 api_base = api_base_value;
97 api_key = api_key_value;
105 return manager ? manager->GetCurrentRom() :
nullptr;
113 const char* Return(std::string output) {
114 last_output = std::move(output);
115 return last_output.c_str();
127 return g_bridge.ai_service.get();
131 return g_bridge.GetActiveRom();
139using Args = std::vector<std::string>;
141EM_JS(
void, z3ed_print_to_terminal, (
const char* text), {
142 if (window.z3edTerminal) {
143 window.z3edTerminal.print(UTF8ToString(text));
145 console.log(UTF8ToString(text));
149EM_JS(
void, z3ed_error_to_terminal, (
const char* text), {
150 if (window.z3edTerminal) {
151 window.z3edTerminal.printError(UTF8ToString(text));
153 console.error(UTF8ToString(text));
158Args ParseCommand(absl::string_view command) {
159 return absl::StrSplit(command, absl::ByAnyChar(
" \t"), absl::SkipEmpty());
162std::string ErrorJson(absl::string_view message) {
163 return nlohmann::json{{
"error", message}}.dump();
166std::optional<int> ParseHexId(absl::string_view text) {
168 if (!absl::SimpleHexAtoi(text, &value)) {
174std::string ExecuteRegistryCommand(
const std::string& name,
const Args& args) {
177 name, args, g_bridge.GetActiveRom(), &output);
179 return absl::StrCat(
"Command failed: ", status.message());
181 return output.empty() ?
"Command executed successfully" : output;
184std::string HandleHelp(
const Args& args) {
186 if (args.size() < 2) {
187 return registry.GenerateCompleteHelp(kTerminalStyle);
189 if (registry.HasCommand(args[1])) {
190 return registry.GenerateHelp(args[1], kTerminalStyle);
192 return registry.GenerateCategoryHelp(args[1], kTerminalStyle);
195std::string HandleRom(
const Args& args) {
196 if (args.size() < 2) {
197 return absl::StrCat(
"Usage: rom load <path> | rom info\n",
200 const std::string& subcommand = args[1];
201 if (subcommand ==
"load") {
202 if (args.size() < 3) {
203 return "Usage: rom load <path>";
205 yaze::app::wasm::TriggerRomLoad(args[2]);
206 return absl::StrCat(
"Requesting load for: ", args[2],
207 ". Check application log.");
209 if (subcommand ==
"info") {
211 if (rom ==
nullptr) {
212 return "No ROM loaded.";
214 return absl::StrCat(
"ROM Info:\n Size: ",
rom->
size(),
215 " bytes\n Title: ",
rom->
title(),
"\n");
220 if (!invocation.ok()) {
221 return std::string(invocation.status().message());
223 return ExecuteRegistryCommand(invocation->command, invocation->args);
226std::string HandleAi(
const Args& args) {
227 if (!g_bridge.ai_service) {
228 g_bridge.SetupAIService();
230 if (!g_bridge.ai_service) {
231 return "AI service unavailable";
233 if (args.size() < 2) {
234 return "AI command requires a prompt. Usage: ai <prompt>";
237 const std::string prompt = absl::StrJoin(args.begin() + 1, args.end(),
" ");
238 auto response = g_bridge.ai_service->GenerateResponse(prompt);
239 if (!response.ok()) {
240 return absl::StrCat(
"AI error: ", response.status().message());
242 return response->text_response;
245std::string HandleEditor(
const Args& args) {
247 if (!editor_manager) {
248 return "Error: Editor manager not available";
251 if (args.size() > 2 && args[1] ==
"switch") {
252 const std::string target = absl::AsciiStrToLower(args[2]);
254 const std::string
name =
256 if (name == target) {
257 editor_manager->SwitchToEditor(
262 return absl::StrCat(
"Unknown editor: ", args[2]);
265 if (args.size() > 3 && args[1] ==
"card") {
267 if (current_editor &&
269 return "Panel visibility is now controlled via Layout Designer. Use "
270 "'editor layout' commands.";
272 return "Panel control not supported for current editor";
275 if (args.size() > 2 && args[1] ==
"debug" && args[2] ==
"toggle") {
276 return "Debug controls are now accessed via the Layout Designer. "
277 "Use the canvas debug panel or layout commands.";
280 return "Usage: editor switch <name>";
283std::string HandleDungeon(
const Args& args) {
285 if (!editor_manager) {
286 return "Error: Editor manager not available";
290 if (!current_editor ||
292 return "Error: Dungeon editor is not active. Use 'editor switch dungeon' "
295 auto* dungeon_editor =
298 if (args.size() > 2 && args[1] ==
"room") {
299 std::optional<int> room_id = ParseHexId(args[2]);
301 return "Invalid room ID (hex required)";
303 dungeon_editor->FocusRoom(*room_id);
304 return absl::StrCat(
"Focused room ", args[2]);
307 if (args.size() > 2 && args[1] ==
"select_object") {
308 std::optional<int> object_id = ParseHexId(args[2]);
310 return "Invalid object ID (hex required)";
312 dungeon_editor->SelectObject(*object_id);
313 return absl::StrCat(
"Selected object ", args[2]);
316 if (args.size() > 2 && args[1] ==
"agent_mode") {
317 const bool enabled = args[2] ==
"on" || args[2] ==
"true";
318 dungeon_editor->SetAgentMode(enabled);
319 return absl::StrCat(
"Agent mode ", enabled ?
"enabled" :
"disabled");
322 return "Usage: dungeon room <hex> | select_object <hex> | agent_mode "
326using BuiltinHandler = std::string (*)(
const Args&);
328struct BuiltinCommand {
329 absl::string_view
name;
330 BuiltinHandler handler;
334constexpr BuiltinCommand kBuiltinCommands[] = {
335 {
"help", &HandleHelp}, {
"rom", &HandleRom}, {
"ai", &HandleAi},
336 {
"editor", &HandleEditor}, {
"dungeon", &HandleDungeon},
339std::string ProcessCommandInternal(absl::string_view command_str) {
340 const Args args = ParseCommand(command_str);
342 return "No command provided";
345 for (
const BuiltinCommand& builtin : kBuiltinCommands) {
346 if (args[0] == builtin.name) {
347 return builtin.handler(args);
352 if (registry.HasCommand(args[0])) {
353 return ExecuteRegistryCommand(args[0], Args(args.begin() + 1, args.end()));
355 if (!registry.GetCommandsInCategory(args[0]).empty()) {
356 return registry.GenerateCategoryHelp(args[0], kTerminalStyle);
359 return absl::StrCat(
"Unknown command: ", args[0],
360 "\nType 'help' for available commands");
363std::vector<std::string> GetCompletionsInternal(absl::string_view partial) {
364 const Args words = ParseCommand(partial);
365 const bool completing_first_word =
366 words.empty() || (words.size() == 1 && !absl::EndsWith(partial,
" "));
368 std::set<std::string> completions;
369 if (completing_first_word) {
370 const absl::string_view prefix =
371 words.empty() ? absl::string_view() : words[0];
372 auto add = [&](absl::string_view candidate) {
373 if (absl::StartsWith(candidate, prefix)) {
374 completions.emplace(candidate);
378 for (
const BuiltinCommand& builtin : kBuiltinCommands) {
381 for (
const std::string& category : registry.GetCategories()) {
383 for (
const std::string& command :
384 registry.GetCommandsInCategory(
category)) {
388 }
else if (words[0] ==
"rom") {
389 const absl::string_view prefix =
390 words.size() > 1 ? absl::string_view(words[1]) : absl::string_view();
391 std::vector<absl::string_view> subcommands = {
"load",
"info"};
393 subcommands.push_back(name);
395 for (absl::string_view subcommand : subcommands) {
396 if (absl::StartsWith(subcommand, prefix)) {
397 completions.insert(absl::StrCat(
"rom ", subcommand));
402 return {completions.begin(), completions.end()};
416 g_bridge.Initialize();
419 return g_bridge.Return(
"Invalid command");
422 const char* output = g_bridge.Return(ProcessCommandInternal(command));
423 z3ed_print_to_terminal(output);
433const char* Z3edGetCompletions(
const char* partial) {
434 g_bridge.Initialize();
437 return g_bridge.Return(
"[]");
439 return g_bridge.Return(
440 nlohmann::json(GetCompletionsInternal(partial)).dump());
448void Z3edSetApiKey(
const char* api_key) {
449 g_bridge.ConfigureAI(
nullptr,
nullptr,
nullptr, api_key ? api_key :
"");
460void Z3edConfigureAI(
const char* provider,
const char* model,
461 const char* api_base,
const char* api_key) {
462 g_bridge.Initialize();
464 model ? model :
"", api_base ? api_base :
"",
465 api_key ? api_key :
"");
474 g_bridge.Initialize();
475 return g_bridge.initialized ? 1 : 0;
485int Z3edLoadRomData(
const uint8_t* data,
size_t size) {
486 if (!data || size == 0) {
487 z3ed_error_to_terminal(
"Invalid ROM data");
492 std::ofstream file(kTerminalUploadPath, std::ios::binary);
493 if (!file || !file.write(
reinterpret_cast<const char*
>(data),
494 static_cast<std::streamsize
>(size))) {
495 z3ed_error_to_terminal(
"Failed to write to VFS");
500 yaze::app::wasm::TriggerRomLoad(kTerminalUploadPath);
502 z3ed_print_to_terminal(
"ROM uploaded to VFS. Loading...");
511const char* Z3edGetRomInfo() {
513 if (rom ==
nullptr) {
514 return g_bridge.Return(ErrorJson(
"No ROM loaded"));
517 return g_bridge.Return(nlohmann::json{
531const char* Z3edQueryResource(
const char* query) {
532 g_bridge.Initialize();
535 return g_bridge.Return(ErrorJson(
"Invalid query"));
539 if (rom ==
nullptr) {
540 return g_bridge.Return(ErrorJson(
"No ROM loaded"));
543 constexpr char kResourceSearchCommand[] =
"resource-search";
545 if (!registry.HasCommand(kResourceSearchCommand)) {
546 return g_bridge.Return(ErrorJson(
"Resource query failed"));
549 const Args args = {
"--query", query,
"--format",
"json"};
551 absl::Status status =
552 registry.Execute(kResourceSearchCommand, args, rom, &output);
554 return g_bridge.Return(ErrorJson(
"Resource query failed"));
556 if (output.empty()) {
557 return g_bridge.Return(nlohmann::json{
558 {
"status",
"success"},
559 {
"message",
"Query executed but no output returned."},
563 return g_bridge.Return(std::move(output));
570 emscripten::allow_raw_pointers());
571 emscripten::function(
"getCompletions", &Z3edGetCompletions,
572 emscripten::allow_raw_pointers());
573 emscripten::function(
"setApiKey", &Z3edSetApiKey,
574 emscripten::allow_raw_pointers());
575 emscripten::function(
"configureAI", &Z3edConfigureAI,
576 emscripten::allow_raw_pointers());
577 emscripten::function(
"isReady", &Z3edIsReady);
578 emscripten::function(
"getRomInfo", &Z3edGetRomInfo,
579 emscripten::allow_raw_pointers());
580 emscripten::function(
"queryResource", &Z3edQueryResource,
581 emscripten::allow_raw_pointers());
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
static CommandRegistry & Instance()
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.
DungeonEditorV2 - Simplified dungeon editor using component delegation.
EM_JS(void, CallJsAiDriver,(const char *history_json), { if(window.yaze &&window.yaze.ai &&window.yaze.ai.processAgentRequest) { window.yaze.ai.processAgentRequest(UTF8ToString(history_json));} else { console.error("AI Driver not found in window.yaze.ai.processAgentRequest");} })
yaze::editor::EditorManager * GetGlobalEditorManager()
constexpr char kProviderGemini[]
absl::StatusOr< CommandInvocation > ResolveRomSubcommand(absl::Span< const std::string > args)
std::string RomSubcommandUsage()
BrowserAIService * GetGlobalBrowserAIService()
std::vector< absl::string_view > RomSubcommandNames()
Rom * rom()
Get the current ROM instance.
Editor * current_editor()
Get the currently active editor.
void add(const std::string &id, const std::string &key, const std::string &desc)
constexpr std::array< const char *, 15 > kEditorNames
const char * Z3edProcessCommand(const char *command)
EMSCRIPTEN_BINDINGS(yaze_debug_inspector)