feat(build-system): enhance CMake configuration and introduce new utility files

- Refactored CMakeLists.txt to streamline project configuration and improve readability.
- Introduced new utility functions in `utils.cmake` for setting compiler flags and managing dependencies.
- Added `dependencies.cmake` to centralize third-party dependency management, enhancing modularity.
- Updated CI workflows to include new build options and improved logging for better feedback during configuration.
- Implemented precompiled headers in various libraries to speed up compilation times.

Benefits:
- Improved maintainability and clarity of the build system.
- Enhanced build performance through precompiled headers.
- Streamlined dependency management for easier integration of third-party libraries.
This commit is contained in:
scawful
2025-10-11 02:44:17 -04:00
parent 31d0337b11
commit f54949bdd8
56 changed files with 2673 additions and 4872 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,288 +1,77 @@
include(app/core/core_library.cmake)
include(app/editor/editor_library.cmake)
include(app/gfx/gfx_library.cmake)
include(app/gui/gui_library.cmake)
include(app/zelda3/zelda3_library.cmake)
# This file defines the main `yaze` application executable.
if (APPLE)
add_executable(
yaze
MACOSX_BUNDLE
app/main.cc
# Bundled Resources
${YAZE_RESOURCE_FILES}
)
add_executable(yaze MACOSX_BUNDLE app/main.cc ${YAZE_RESOURCE_FILES})
# Add the app icon to the macOS bundle
set(ICON_FILE "${CMAKE_SOURCE_DIR}/assets/yaze.icns")
target_sources(yaze PRIVATE ${ICON_FILE})
set_source_files_properties(${ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
# Set macOS bundle properties
set_target_properties(yaze PROPERTIES
MACOSX_BUNDLE_ICON_FILE "yaze.icns"
MACOSX_BUNDLE_BUNDLE_NAME "Yaze"
MACOSX_BUNDLE_EXECUTABLE_NAME "yaze"
MACOSX_BUNDLE_GUI_IDENTIFIER "com.scawful.yaze"
MACOSX_BUNDLE_INFO_STRING "Yet Another Zelda3 Editor"
MACOSX_BUNDLE_LONG_VERSION_STRING "${PROJECT_VERSION}"
MACOSX_BUNDLE_SHORT_VERSION_STRING "${PROJECT_VERSION}"
MACOSX_BUNDLE_BUNDLE_VERSION "${PROJECT_VERSION}"
MACOSX_BUNDLE_COPYRIGHT "Copyright © 2024 scawful. All rights reserved."
)
else()
# Windows/Linux builds
add_executable(
yaze
app/main.cc
)
# Add asset files for Windows/Linux builds
add_executable(yaze app/main.cc)
if(WIN32 OR UNIX)
target_sources(yaze PRIVATE ${YAZE_RESOURCE_FILES})
# Set up asset deployment for Visual Studio
if(WIN32)
foreach(ASSET_FILE ${YAZE_RESOURCE_FILES})
file(RELATIVE_PATH ASSET_REL_PATH "${CMAKE_SOURCE_DIR}/assets" ${ASSET_FILE})
get_filename_component(ASSET_DIR ${ASSET_REL_PATH} DIRECTORY)
set_source_files_properties(${ASSET_FILE}
PROPERTIES
VS_DEPLOYMENT_CONTENT 1
VS_DEPLOYMENT_LOCATION "assets/${ASSET_DIR}"
)
endforeach()
endif()
endif()
endif()
target_include_directories(
yaze PUBLIC
${CMAKE_SOURCE_DIR}/src/lib/
${CMAKE_SOURCE_DIR}/src/app/
${CMAKE_SOURCE_DIR}/src/lib/asar/src
${CMAKE_SOURCE_DIR}/src/lib/asar/src/asar
${CMAKE_SOURCE_DIR}/src/lib/asar/src/asar-dll-bindings/c
${CMAKE_SOURCE_DIR}/incl/
${CMAKE_SOURCE_DIR}/src/
${CMAKE_SOURCE_DIR}/src/lib/imgui_test_engine
${CMAKE_SOURCE_DIR}/third_party/httplib
${SDL2_INCLUDE_DIR}
${CMAKE_CURRENT_BINARY_DIR}
target_include_directories(yaze PUBLIC
${CMAKE_SOURCE_DIR}/src
${CMAKE_SOURCE_DIR}/incl
${PROJECT_BINARY_DIR}
)
target_sources(yaze PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/yaze_config.h)
set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/yaze_config.h PROPERTIES GENERATED TRUE)
# 4) Tell the IDE its generated
set_source_files_properties(
${CMAKE_CURRENT_BINARY_DIR}/yaze_config.h
PROPERTIES GENERATED TRUE
# Link modular libraries
target_link_libraries(yaze PRIVATE
yaze_editor
yaze_emulator
yaze_agent
absl::failure_signal_handler
absl::flags
absl::flags_parse
)
# (Optional) put it under a neat filter in VS Solution Explorer
source_group(TREE ${CMAKE_CURRENT_BINARY_DIR}
FILES ${CMAKE_CURRENT_BINARY_DIR}/yaze_config.h)
# Conditionally add PNG include dirs if available
if(PNG_FOUND)
target_include_directories(yaze PUBLIC ${PNG_INCLUDE_DIRS})
if(YAZE_BUILD_TESTS AND TARGET yaze_test_support)
target_link_libraries(yaze PRIVATE yaze_test_support)
endif()
# Conditionally link nfd if available
if(YAZE_HAS_NFD)
target_link_libraries(yaze PRIVATE nfd)
target_compile_definitions(yaze PRIVATE YAZE_ENABLE_NFD=1)
else()
target_compile_definitions(yaze PRIVATE YAZE_ENABLE_NFD=0)
endif()
if(YAZE_USE_MODULAR_BUILD)
set(_yaze_modular_links yaze_editor)
if(TARGET yaze_agent)
list(APPEND _yaze_modular_links yaze_agent)
endif()
if(YAZE_BUILD_EMU AND TARGET yaze_emulator)
list(APPEND _yaze_modular_links yaze_emulator)
endif()
# Link once against the editor library and allow its PUBLIC dependencies
# (core, gfx, util, absl, etc.) to propagate transitively. This avoids
# duplicate static archives on the link line while keeping absl symbols
# available for main and other entry points.
target_link_libraries(yaze PRIVATE ${_yaze_modular_links})
else()
target_link_libraries(yaze PRIVATE yaze_core)
endif()
# Enable policy framework in main yaze target
target_compile_definitions(yaze PRIVATE YAZE_ENABLE_POLICY_FRAMEWORK=1)
# Increase stack size on Windows to prevent stack overflow during asset loading
# Windows default is 1MB, macOS/Linux is typically 8MB
# LoadAssets() loads 223 graphics sheets and initializes multiple editors
# Platform-specific settings
if(WIN32)
if(MSVC)
# Set Windows subsystem to WINDOWS (GUI app, no console)
# But keep main() as entry point (SDL_MAIN_HANDLED is set globally)
target_link_options(yaze PRIVATE
/STACK:8388608 # 8MB stack
/SUBSYSTEM:WINDOWS # Windows GUI subsystem
/ENTRY:mainCRTStartup # Use main() instead of WinMain()
)
message(STATUS "Configuring yaze as Windows GUI application with main() entry point")
target_link_options(yaze PRIVATE /STACK:8388608 /SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup)
elseif(MINGW OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
target_link_options(yaze PRIVATE
-Wl,--stack,8388608 # 8MB stack
-Wl,--subsystem,windows # Windows GUI subsystem
-Wl,-emain # Use main() as entry point
)
message(STATUS "Configuring yaze as Windows GUI application with main() entry point (MinGW)")
target_link_options(yaze PRIVATE -Wl,--stack,8388608 -Wl,--subsystem,windows -Wl,-emain)
endif()
endif()
# Conditionally link ImGui Test Engine
if(YAZE_ENABLE_UI_TESTS)
if(TARGET ImGuiTestEngine)
target_include_directories(yaze PUBLIC ${CMAKE_SOURCE_DIR}/src/lib/imgui_test_engine)
target_link_libraries(yaze PUBLIC ImGuiTestEngine)
target_compile_definitions(yaze PRIVATE
YAZE_ENABLE_IMGUI_TEST_ENGINE=1
${IMGUI_TEST_ENGINE_DEFINITIONS})
else()
target_compile_definitions(yaze PRIVATE YAZE_ENABLE_IMGUI_TEST_ENGINE=0)
endif()
else()
target_compile_definitions(yaze PRIVATE YAZE_ENABLE_IMGUI_TEST_ENGINE=0)
endif()
# Link Google Test if available for integrated testing (but NOT gtest_main to avoid main() conflicts)
if(YAZE_BUILD_TESTS AND TARGET gtest)
target_link_libraries(yaze PRIVATE gtest)
target_compile_definitions(yaze PRIVATE YAZE_ENABLE_GTEST=1)
target_compile_definitions(yaze PRIVATE YAZE_ENABLE_TESTING=1)
else()
target_compile_definitions(yaze PRIVATE YAZE_ENABLE_GTEST=0)
target_compile_definitions(yaze PRIVATE YAZE_ENABLE_TESTING=0)
endif()
# Conditionally link PNG if available
if(PNG_FOUND)
target_link_libraries(yaze PUBLIC ${PNG_LIBRARIES})
endif()
if (APPLE)
target_link_libraries(yaze PUBLIC ${COCOA_LIBRARY})
endif()
# Post-build step to copy assets to output directory
if(APPLE)
# macOS: Copy to bundle Resources
target_link_libraries(yaze PUBLIC "-framework Cocoa")
endif()
# Post-build asset copying for non-macOS platforms
if(NOT APPLE)
add_custom_command(TARGET yaze POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory
$<TARGET_FILE_DIR:yaze>/../Resources/agent
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/assets/agent
$<TARGET_FILE_DIR:yaze>/../Resources/agent
COMMENT "Copying agent assets to macOS bundle"
)
elseif(NOT APPLE)
# Add post-build commands directly to the yaze target
# Copy fonts
add_custom_command(TARGET yaze POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory
$<TARGET_FILE_DIR:yaze>/assets/font
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/assets/font
$<TARGET_FILE_DIR:yaze>/assets/font
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/assets/font $<TARGET_FILE_DIR:yaze>/assets/font
COMMENT "Copying font assets"
)
# Copy themes
add_custom_command(TARGET yaze POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory
$<TARGET_FILE_DIR:yaze>/assets/themes
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/assets/themes
$<TARGET_FILE_DIR:yaze>/assets/themes
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/assets/themes $<TARGET_FILE_DIR:yaze>/assets/themes
COMMENT "Copying theme assets"
)
# Copy agent assets (system prompts, etc.)
if(EXISTS ${CMAKE_SOURCE_DIR}/assets/agent)
add_custom_command(TARGET yaze POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory
$<TARGET_FILE_DIR:yaze>/assets/agent
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/assets/agent
$<TARGET_FILE_DIR:yaze>/assets/agent
COMMENT "Copying agent assets (prompts, schemas)"
)
endif()
# Copy other assets if they exist
if(EXISTS ${CMAKE_SOURCE_DIR}/assets/layouts)
add_custom_command(TARGET yaze POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory
$<TARGET_FILE_DIR:yaze>/assets/layouts
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/assets/layouts
$<TARGET_FILE_DIR:yaze>/assets/layouts
COMMENT "Copying layout assets"
)
endif()
if(EXISTS ${CMAKE_SOURCE_DIR}/assets/lib)
add_custom_command(TARGET yaze POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory
$<TARGET_FILE_DIR:yaze>/assets/lib
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/assets/lib
$<TARGET_FILE_DIR:yaze>/assets/lib
COMMENT "Copying library assets"
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/assets/agent $<TARGET_FILE_DIR:yaze>/assets/agent
COMMENT "Copying agent assets"
)
endif()
endif()
# ============================================================================
# Optional gRPC Support for ImGuiTestHarness
# ============================================================================
if(YAZE_WITH_GRPC)
message(STATUS "Adding gRPC ImGuiTestHarness to yaze target")
target_include_directories(yaze PRIVATE
${CMAKE_SOURCE_DIR}/third_party/json/include)
target_compile_definitions(yaze PRIVATE YAZE_WITH_JSON)
if(NOT YAZE_USE_MODULAR_BUILD)
# Generate C++ code from .proto using the helper function from cmake/grpc.cmake
target_add_protobuf(yaze
${CMAKE_SOURCE_DIR}/src/protos/imgui_test_harness.proto
${CMAKE_SOURCE_DIR}/src/protos/canvas_automation.proto)
# Add service implementation sources
target_sources(yaze PRIVATE
${CMAKE_SOURCE_DIR}/src/app/core/service/imgui_test_harness_service.cc
${CMAKE_SOURCE_DIR}/src/app/core/service/imgui_test_harness_service.h
${CMAKE_SOURCE_DIR}/src/app/core/service/screenshot_utils.cc
${CMAKE_SOURCE_DIR}/src/app/core/service/screenshot_utils.h
${CMAKE_SOURCE_DIR}/src/app/core/service/widget_discovery_service.cc
${CMAKE_SOURCE_DIR}/src/app/core/service/widget_discovery_service.h
${CMAKE_SOURCE_DIR}/src/app/core/testing/test_recorder.cc
${CMAKE_SOURCE_DIR}/src/app/core/testing/test_recorder.h
${CMAKE_SOURCE_DIR}/src/app/core/testing/test_script_parser.cc
${CMAKE_SOURCE_DIR}/src/app/core/testing/test_script_parser.h)
endif()
# Link gRPC libraries
target_link_libraries(yaze PRIVATE
grpc++
grpc++_reflection
libprotobuf)
message(STATUS "✓ gRPC ImGuiTestHarness integrated")
message(STATUS "✓ AI Agent services integrated into yaze GUI")
endif()

View File

@@ -1,10 +1,9 @@
set(
YAZE_APP_CORE_SRC
app/core/asar_wrapper.cc
app/core/controller.cc
app/emu/emulator.cc
app/core/project.cc
app/core/window.cc
app/core/asar_wrapper.cc
)
if (WIN32 OR MINGW OR (UNIX AND NOT APPLE))
@@ -16,13 +15,37 @@ endif()
if(APPLE)
list(APPEND YAZE_APP_CORE_SRC
app/platform/file_dialog.mm
app/platform/app_delegate.mm
app/platform/font_loader.cc
app/platform/font_loader.mm
app/platform/asset_loader.cc
)
set(YAZE_APPLE_OBJCXX_SRC
app/platform/file_dialog.mm
app/platform/app_delegate.mm
app/platform/font_loader.mm
)
add_library(yaze_core_objcxx OBJECT ${YAZE_APPLE_OBJCXX_SRC})
set_target_properties(yaze_core_objcxx PROPERTIES
OBJCXX_STANDARD 20
OBJCXX_STANDARD_REQUIRED ON
)
target_include_directories(yaze_core_objcxx PUBLIC
${CMAKE_SOURCE_DIR}/src
${CMAKE_SOURCE_DIR}/src/app
${CMAKE_SOURCE_DIR}/src/lib
${CMAKE_SOURCE_DIR}/src/lib/imgui
${CMAKE_SOURCE_DIR}/src/lib/asar/src
${CMAKE_SOURCE_DIR}/src/lib/asar/src/asar
${CMAKE_SOURCE_DIR}/src/lib/asar/src/asar-dll-bindings/c
${CMAKE_SOURCE_DIR}/incl
${SDL2_INCLUDE_DIR}
${PROJECT_BINARY_DIR}
)
target_link_libraries(yaze_core_objcxx PUBLIC ${ABSL_TARGETS} yaze_util)
target_compile_definitions(yaze_core_objcxx PUBLIC MACOS)
find_library(COCOA_LIBRARY Cocoa)
if(NOT COCOA_LIBRARY)
message(FATAL_ERROR "Cocoa not found")
@@ -48,6 +71,11 @@ endif()
add_library(yaze_core_lib STATIC
app/rom.cc
${YAZE_APP_CORE_SRC}
$<$<BOOL:${APPLE}>:$<TARGET_OBJECTS:yaze_core_objcxx>>
)
target_precompile_headers(yaze_core_lib PRIVATE
"$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_SOURCE_DIR}/src/yaze_pch.h>"
)
target_include_directories(yaze_core_lib PUBLIC

View File

@@ -17,7 +17,8 @@ namespace {
constexpr absl::Duration kTestCompletionTimeout = absl::Seconds(10);
constexpr absl::Duration kPollInterval = absl::Milliseconds(50);
const char* HarnessStatusToString(HarnessTestStatus status) {
#if defined(YAZE_WITH_GRPC)
const char* HarnessStatusToString(test::HarnessTestStatus status) {
switch (status) {
case HarnessTestStatus::kQueued:
return "queued";
@@ -31,9 +32,10 @@ const char* HarnessStatusToString(HarnessTestStatus status) {
return "timeout";
case HarnessTestStatus::kUnspecified:
default:
return "unknown";
return "unspecified";
}
}
#endif // defined(YAZE_WITH_GRPC)
} // namespace
@@ -152,7 +154,11 @@ absl::StatusOr<TestRecorder::StopRecordingSummary> TestRecorder::StopLocked(
script_step.region = step.region;
script_step.format = step.format;
script_step.expect_success = step.success;
#if defined(YAZE_WITH_GRPC)
script_step.expect_status = ::yaze::test::HarnessStatusToString(step.final_status);
#else
script_step.expect_status.clear();
#endif
if (!step.final_error_message.empty()) {
script_step.expect_message = step.final_error_message;
} else {
@@ -194,6 +200,9 @@ absl::Status TestRecorder::PopulateFinalStatusLocked() {
return absl::FailedPreconditionError("TestManager unavailable");
}
#if !defined(YAZE_WITH_GRPC)
return absl::OkStatus();
#else
for (auto& step : steps_) {
if (step.test_id.empty()) {
continue;
@@ -201,7 +210,7 @@ absl::Status TestRecorder::PopulateFinalStatusLocked() {
const absl::Time deadline = absl::Now() + kTestCompletionTimeout;
while (absl::Now() < deadline) {
absl::StatusOr<HarnessTestExecution> execution =
absl::StatusOr<test::HarnessTestExecution> execution =
test_manager_->GetHarnessTestExecution(step.test_id);
if (!execution.ok()) {
absl::SleepFor(kPollInterval);
@@ -213,8 +222,8 @@ absl::Status TestRecorder::PopulateFinalStatusLocked() {
step.assertion_failures = execution->assertion_failures;
step.metrics = execution->metrics;
if (execution->status == HarnessTestStatus::kQueued ||
execution->status == HarnessTestStatus::kRunning) {
if (execution->status == test::HarnessTestStatus::kQueued ||
execution->status == test::HarnessTestStatus::kRunning) {
absl::SleepFor(kPollInterval);
continue;
}
@@ -223,6 +232,7 @@ absl::Status TestRecorder::PopulateFinalStatusLocked() {
}
return absl::OkStatus();
#endif // defined(YAZE_WITH_GRPC)
}
std::string TestRecorder::GenerateRecordingId() {

View File

@@ -52,7 +52,9 @@ class TestRecorder {
std::vector<std::string> assertion_failures;
std::string expected_value;
std::string actual_value;
#if defined(YAZE_WITH_GRPC)
HarnessTestStatus final_status = HarnessTestStatus::kUnspecified;
#endif
std::string final_error_message;
std::map<std::string, int32_t> metrics;
absl::Time captured_at = absl::InfinitePast();

View File

@@ -38,9 +38,9 @@ AgentUITheme AgentUITheme::FromCurrentTheme() {
);
// Content colors
theme.json_text_color = ImVec4(0.78f, 0.83f, 0.90f, 1.0f);
theme.command_text_color = ImVec4(1.0f, 0.647f, 0.0f, 1.0f);
theme.code_bg_color = ImVec4(0.08f, 0.08f, 0.10f, 0.95f);
theme.json_text_color = ConvertColorToImVec4(current.text_secondary);
theme.command_text_color = ConvertColorToImVec4(current.accent);
theme.code_bg_color = ConvertColorToImVec4(current.code_background);
theme.text_secondary_color = ConvertColorToImVec4(current.text_secondary);

View File

@@ -1,46 +1,55 @@
set(
YAZE_APP_EDITOR_SRC
app/editor/editor_manager.cc
app/editor/ui/menu_builder.cc
app/editor/ui/editor_selection_dialog.cc
app/editor/ui/welcome_screen.cc
app/editor/ui/workspace_manager.cc
app/editor/system/user_settings.cc
app/editor/ui/background_renderer.cc
app/editor/dungeon/dungeon_editor_v2.cc
app/editor/dungeon/dungeon_room_selector.cc
app/editor/dungeon/dungeon_canvas_viewer.cc
app/editor/dungeon/dungeon_object_selector.cc
app/editor/dungeon/dungeon_toolset.cc
app/editor/dungeon/dungeon_object_interaction.cc
app/editor/dungeon/dungeon_room_loader.cc
app/editor/dungeon/dungeon_usage_tracker.cc
app/editor/dungeon/object_editor_card.cc
app/editor/overworld/overworld_editor.cc
app/editor/overworld/scratch_space.cc
app/editor/sprite/sprite_editor.cc
app/editor/music/music_editor.cc
app/editor/message/message_editor.cc
app/editor/message/message_data.cc
app/editor/message/message_preview.cc
app/editor/agent/agent_chat_history_codec.cc
app/editor/agent/agent_chat_history_popup.cc
app/editor/agent/agent_chat_widget.cc
app/editor/agent/agent_collaboration_coordinator.cc
app/editor/agent/agent_editor.cc
app/editor/agent/agent_ui_theme.cc
app/editor/agent/automation_bridge.cc
app/editor/agent/network_collaboration_coordinator.cc
app/editor/code/assembly_editor.cc
app/editor/code/memory_editor.cc
app/editor/code/project_file_editor.cc
app/editor/graphics/screen_editor.cc
app/editor/dungeon/dungeon_canvas_viewer.cc
app/editor/dungeon/dungeon_editor_v2.cc
app/editor/dungeon/dungeon_object_interaction.cc
app/editor/dungeon/dungeon_object_selector.cc
app/editor/dungeon/dungeon_room_loader.cc
app/editor/dungeon/dungeon_room_selector.cc
app/editor/dungeon/dungeon_toolset.cc
app/editor/dungeon/dungeon_usage_tracker.cc
app/editor/dungeon/object_editor_card.cc
app/editor/editor_manager.cc
app/editor/graphics/gfx_group_editor.cc
app/editor/graphics/graphics_editor.cc
app/editor/graphics/palette_editor.cc
app/editor/overworld/tile16_editor.cc
app/editor/overworld/map_properties.cc
app/editor/graphics/gfx_group_editor.cc
app/editor/graphics/screen_editor.cc
app/editor/message/message_data.cc
app/editor/message/message_editor.cc
app/editor/message/message_preview.cc
app/editor/music/music_editor.cc
app/editor/overworld/entity.cc
app/editor/overworld/map_properties.cc
app/editor/overworld/overworld_editor.cc
app/editor/overworld/overworld_entity_renderer.cc
app/editor/system/settings_editor.cc
app/editor/overworld/scratch_space.cc
app/editor/overworld/tile16_editor.cc
app/editor/sprite/sprite_editor.cc
app/editor/system/command_manager.cc
app/editor/system/command_palette.cc
app/editor/system/extension_manager.cc
app/editor/system/shortcut_manager.cc
app/editor/system/popup_manager.cc
app/editor/system/proposal_drawer.cc
app/editor/agent/agent_chat_history_codec.cc
app/editor/system/settings_editor.cc
app/editor/system/shortcut_manager.cc
app/editor/system/user_settings.cc
app/editor/ui/background_renderer.cc
app/editor/ui/editor_selection_dialog.cc
app/editor/ui/menu_builder.cc
app/editor/ui/menu_manager.cc
app/editor/ui/welcome_screen.cc
app/editor/ui/workspace_manager.cc
)
if(YAZE_WITH_GRPC)
@@ -76,12 +85,7 @@ endif()
add_library(yaze_editor STATIC ${YAZE_APP_EDITOR_SRC})
target_precompile_headers(yaze_editor PRIVATE
<array>
<cstdint>
<memory>
<set>
<string>
<vector>
"$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_SOURCE_DIR}/src/yaze_pch.h>"
)
target_include_directories(yaze_editor PUBLIC

View File

@@ -1337,9 +1337,71 @@ void EditorManager::BuildModernMenu() {
menu_builder_.Draw();
}
void EditorManager::DrawMenuBarExtras() {
auto* current_rom = GetCurrentRom();
std::string version_text = absl::StrFormat("v%s", version_.c_str());
float version_width = ImGui::CalcTextSize(version_text.c_str()).x;
float session_rom_area_width = 280.0f;
SameLine(ImGui::GetWindowWidth() - version_width - 10 - session_rom_area_width);
if (GetActiveSessionCount() > 1) {
if (ImGui::SmallButton(absl::StrFormat("%s%zu", ICON_MD_TAB, GetActiveSessionCount()).c_str())) {
ShowSessionSwitcher();
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Sessions: %zu active\nClick to switch", GetActiveSessionCount());
}
ImGui::SameLine();
}
if (current_rom && current_rom->is_loaded()) {
if (ImGui::SmallButton(ICON_MD_APPS)) {
ShowEditorSelection();
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(ICON_MD_DASHBOARD " Editor Selection (Ctrl+E)");
}
ImGui::SameLine();
if (ImGui::SmallButton(ICON_MD_DISPLAY_SETTINGS)) {
ShowDisplaySettings();
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(ICON_MD_TUNE " Display Settings");
}
ImGui::SameLine();
ImGui::Separator();
ImGui::SameLine();
}
if (current_rom && current_rom->is_loaded()) {
std::string rom_display = current_rom->title();
if (rom_display.length() > 22) {
rom_display = rom_display.substr(0, 19) + "...";
}
if (ImGui::SmallButton(absl::StrFormat("%s%s", rom_display.c_str(), current_rom->dirty() ? "*" : "").c_str())) {
ImGui::OpenPopup("ROM Details");
}
} else {
ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "No ROM");
ImGui::SameLine();
}
SameLine(ImGui::GetWindowWidth() - version_width - 10);
ImGui::Text("%s", version_text.c_str());
}
void EditorManager::ShowSessionSwitcher() { show_session_switcher_ = true; }
void EditorManager::ShowEditorSelection() { show_editor_selection_ = true; }
void EditorManager::ShowDisplaySettings() { if (popup_manager_) popup_manager_->Show("Display Settings"); }
void EditorManager::DrawMenuBar() {
static bool show_display_settings = false;
static bool save_as_menu = false;
std::string version_text = absl::StrFormat("v%s", version_.c_str());
float version_width = ImGui::CalcTextSize(version_text.c_str()).x;
if (BeginMenuBar()) {
BuildModernMenu();
@@ -1347,173 +1409,7 @@ void EditorManager::DrawMenuBar() {
// Inline ROM selector and status
status_ = DrawRomSelector();
// Calculate proper right-side positioning
std::string version_text = absl::StrFormat("v%s", version_.c_str());
float version_width = CalcTextSize(version_text.c_str()).x;
// Allocate space for ROM status and sessions (wider for better ROM title display)
float session_rom_area_width = 280.0f; // Increased for wider ROM title
SameLine(GetWindowWidth() - version_width - 10 - session_rom_area_width);
// Multi-session indicator
if (sessions_.size() > 1) {
if (SmallButton(absl::StrFormat("%s%zu", ICON_MD_TAB, sessions_.size())
.c_str())) {
show_session_switcher_ = true;
}
if (IsItemHovered()) {
SetTooltip("Sessions: %zu active\nClick to switch", sessions_.size());
}
SameLine();
}
// Editor selection and display settings quick buttons (when ROM loaded)
if (current_rom_ && current_rom_->is_loaded()) {
// Editor selection button
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.3f, 0.3f, 0.4f, 0.8f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.4f, 0.4f, 0.5f, 1.0f));
if (SmallButton(ICON_MD_APPS)) {
show_editor_selection_ = true;
}
ImGui::PopStyleColor(2);
if (IsItemHovered()) {
SetTooltip(ICON_MD_DASHBOARD " Editor Selection (Ctrl+E)");
}
SameLine();
// Display settings button
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.3f, 0.3f, 0.4f, 0.8f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.4f, 0.4f, 0.5f, 1.0f));
if (SmallButton(ICON_MD_DISPLAY_SETTINGS)) {
show_display_settings = !show_display_settings;
}
ImGui::PopStyleColor(2);
if (IsItemHovered()) {
SetTooltip(ICON_MD_TUNE " Display Settings");
}
SameLine();
ImGui::Separator();
SameLine();
}
// Compact ROM status with metadata popup
if (current_rom_ && current_rom_->is_loaded()) {
// Truncate long ROM titles for wider display
std::string rom_display = current_rom_->title();
if (rom_display.length() > 22) {
rom_display = rom_display.substr(0, 19) + "...";
}
ImVec4 status_color =
current_rom_->dirty() ? ImVec4(1.0f, 0.5f, 0.0f, 1.0f)
: // Orange for modified
ImVec4(0.0f, 0.8f, 0.0f, 1.0f); // Green for clean
// Compact ROM status button
if (SmallButton(absl::StrFormat("%s%s", rom_display.c_str(),
current_rom_->dirty() ? "*" : "")
.c_str())) {
ImGui::OpenPopup("ROM Details");
}
// Enhanced tooltip on hover
if (IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::Text("%s ROM Information", ICON_MD_INFO);
ImGui::Separator();
ImGui::Text("%s Title: %s", ICON_MD_TITLE,
current_rom_->title().c_str());
ImGui::Text("%s File: %s", ICON_MD_FOLDER_OPEN,
current_rom_->filename().c_str());
ImGui::Text("%s Size: %.1f MB (%zu bytes)", ICON_MD_STORAGE,
current_rom_->size() / 1048576.0f, current_rom_->size());
ImGui::Text("%s Status: %s",
current_rom_->dirty() ? ICON_MD_EDIT : ICON_MD_CHECK_CIRCLE,
current_rom_->dirty() ? "Modified" : "Clean");
ImGui::Text("%s Click for detailed view", ICON_MD_LAUNCH);
ImGui::EndTooltip();
}
// Detailed ROM popup
if (ImGui::BeginPopup("ROM Details")) {
ImGui::Text("%s ROM Detailed Information", ICON_MD_INFO);
ImGui::Separator();
ImGui::Spacing();
// Basic info with icons
if (ImGui::BeginTable("ROMDetailsTable", 2,
ImGuiTableFlags_SizingFixedFit)) {
ImGui::TableSetupColumn("Property", ImGuiTableColumnFlags_WidthFixed,
120);
ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("%s Title", ICON_MD_TITLE);
ImGui::TableNextColumn();
ImGui::Text("%s", current_rom_->title().c_str());
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("%s File", ICON_MD_FOLDER_OPEN);
ImGui::TableNextColumn();
ImGui::Text("%s", current_rom_->filename().c_str());
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("%s Size", ICON_MD_STORAGE);
ImGui::TableNextColumn();
ImGui::Text("%.1f MB (%zu bytes)", current_rom_->size() / 1048576.0f,
current_rom_->size());
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("%s Status", current_rom_->dirty()
? ICON_MD_EDIT
: ICON_MD_CHECK_CIRCLE);
ImGui::TableNextColumn();
ImGui::TextColored(status_color, "%s",
current_rom_->dirty() ? "Modified" : "Clean");
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("%s Session", ICON_MD_TAB);
ImGui::TableNextColumn();
size_t current_session_idx = GetCurrentSessionIndex();
ImGui::Text("Session %zu of %zu", current_session_idx + 1,
sessions_.size());
ImGui::EndTable();
}
ImGui::Spacing();
ImGui::Separator();
// Quick actions
ImGui::Text("%s Quick Actions", ICON_MD_FLASH_ON);
if (ImGui::Button(absl::StrFormat("%s Save ROM", ICON_MD_SAVE).c_str(),
ImVec2(120, 0))) {
status_ = SaveRom();
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button(
absl::StrFormat("%s Switch Session", ICON_MD_SWITCH_ACCOUNT)
.c_str(),
ImVec2(120, 0))) {
show_session_switcher_ = true;
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
SameLine();
} else {
TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "No ROM");
SameLine();
}
DrawMenuBarExtras();
// Version display on far right
SameLine(GetWindowWidth() - version_width - 10);

View File

@@ -122,6 +122,11 @@ class EditorManager {
auto emulator() -> emu::Emulator& { return emulator_; }
auto quit() const { return quit_; }
auto version() const { return version_; }
void DrawMenuBarExtras();
MenuBuilder& menu_builder() { return menu_builder_; }
void ShowSessionSwitcher();
void ShowEditorSelection();
void ShowDisplaySettings();
absl::Status SetCurrentRom(Rom* rom);
auto GetCurrentRom() -> Rom* { return current_rom_; }

View File

@@ -11,63 +11,13 @@ MenuManager::MenuManager(EditorManager* editor_manager)
: editor_manager_(editor_manager) {}
void MenuManager::BuildAndDraw() {
if (!editor_manager_) {
return;
}
if (ImGui::BeginMenuBar()) {
editor_manager_->BuildModernMenu(); // This contains the menu_builder_ logic
editor_manager_->menu_builder_.Draw();
// This is the logic from the second half of DrawMenuBar
auto* current_rom = editor_manager_->GetCurrentRom();
std::string version_text = absl::StrFormat("v%s", editor_manager_->version().c_str());
float version_width = ImGui::CalcTextSize(version_text.c_str()).x;
float session_rom_area_width = 280.0f;
ImGui::SameLine(ImGui::GetWindowWidth() - version_width - 10 - session_rom_area_width);
if (editor_manager_->GetActiveSessionCount() > 1) {
if (ImGui::SmallButton(absl::StrFormat("%s%zu", ICON_MD_TAB, editor_manager_->GetActiveSessionCount()).c_str())) {
editor_manager_->show_session_switcher_ = true;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Sessions: %zu active\nClick to switch", editor_manager_->GetActiveSessionCount());
}
ImGui::SameLine();
}
if (current_rom && current_rom->is_loaded()) {
if (ImGui::SmallButton(ICON_MD_APPS)) {
editor_manager_->show_editor_selection_ = true;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(ICON_MD_DASHBOARD " Editor Selection (Ctrl+E)");
}
ImGui::SameLine();
if (ImGui::SmallButton(ICON_MD_DISPLAY_SETTINGS)) {
editor_manager_->popup_manager_->Show("Display Settings");
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(ICON_MD_TUNE " Display Settings");
}
ImGui::SameLine();
ImGui::Separator();
ImGui::SameLine();
}
if (current_rom && current_rom->is_loaded()) {
std::string rom_display = current_rom->title();
if (rom_display.length() > 22) {
rom_display = rom_display.substr(0, 19) + "...";
}
ImVec4 status_color = current_rom->dirty() ? ImVec4(1.0f, 0.5f, 0.0f, 1.0f) : ImVec4(0.0f, 0.8f, 0.0f, 1.0f);
if (ImGui::SmallButton(absl::StrFormat("%s%s", rom_display.c_str(), current_rom->dirty() ? "*" : "").c_str())) {
ImGui::OpenPopup("ROM Details");
}
// ... (rest of the popup logic from DrawMenuBar)
} else {
ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "No ROM");
ImGui::SameLine();
}
ImGui::SameLine(ImGui::GetWindowWidth() - version_width - 10);
ImGui::Text("%s", version_text.c_str());
editor_manager_->BuildModernMenu();
editor_manager_->DrawMenuBarExtras();
ImGui::EndMenuBar();
}
}

View File

@@ -16,7 +16,6 @@ class MenuManager {
private:
EditorManager* editor_manager_;
MenuBuilder menu_builder_;
};
} // namespace editor

View File

@@ -1,76 +1,55 @@
# Yaze Emulator Standalone Application (skip in minimal builds)
if (NOT YAZE_MINIMAL_BUILD AND APPLE)
add_executable(
yaze_emu
MACOSX_BUNDLE
app/main.cc
app/rom.cc
app/platform/app_delegate.mm
${YAZE_APP_EMU_SRC}
${YAZE_APP_CORE_SRC}
${YAZE_APP_EDITOR_SRC}
${YAZE_APP_GFX_SRC}
${YAZE_APP_ZELDA3_SRC}
${YAZE_UTIL_SRC}
${YAZE_GUI_SRC}
${IMGUI_SRC}
# CLI service sources (needed for ProposalDrawer)
cli/service/planning/proposal_registry.cc
cli/service/rom/rom_sandbox_manager.cc
)
target_link_libraries(yaze_emu PUBLIC ${COCOA_LIBRARY})
elseif(NOT YAZE_MINIMAL_BUILD)
add_executable(
yaze_emu
app/rom.cc
app/emu/emu.cc
${YAZE_APP_EMU_SRC}
${YAZE_APP_CORE_SRC}
${YAZE_APP_EDITOR_SRC}
${YAZE_APP_GFX_SRC}
${YAZE_APP_ZELDA3_SRC}
${YAZE_UTIL_SRC}
${YAZE_GUI_SRC}
${IMGUI_SRC}
# CLI service sources (needed for ProposalDrawer)
cli/service/planning/proposal_registry.cc
cli/service/rom/rom_sandbox_manager.cc
)
endif()
# This file defines the yaze_emu standalone executable.
# Note: The yaze_emulator library is ALWAYS built (via emu_library.cmake)
# because it's used by the main yaze app and test suites.
# This file only controls the STANDALONE emulator executable.
# Only configure emulator target if it was created
if(NOT YAZE_MINIMAL_BUILD)
target_include_directories(
yaze_emu PUBLIC
${CMAKE_SOURCE_DIR}/src/lib/
${CMAKE_SOURCE_DIR}/src/app/
${CMAKE_SOURCE_DIR}/src/lib/asar/src
${CMAKE_SOURCE_DIR}/src/lib/asar/src/asar
${CMAKE_SOURCE_DIR}/src/lib/asar/src/asar-dll-bindings/c
${CMAKE_SOURCE_DIR}/incl/
${CMAKE_SOURCE_DIR}/src/
${CMAKE_SOURCE_DIR}/src/lib/imgui_test_engine
${PNG_INCLUDE_DIRS}
${SDL2_INCLUDE_DIR}
${CMAKE_CURRENT_BINARY_DIR}
if(YAZE_BUILD_EMU AND NOT YAZE_MINIMAL_BUILD)
if(APPLE)
add_executable(yaze_emu MACOSX_BUNDLE app/emu/emu.cc app/platform/app_delegate.mm)
target_link_libraries(yaze_emu PUBLIC "-framework Cocoa")
else()
add_executable(yaze_emu app/emu/emu.cc)
endif()
target_include_directories(yaze_emu PUBLIC
${CMAKE_SOURCE_DIR}/src
${CMAKE_SOURCE_DIR}/incl
${PROJECT_BINARY_DIR}
)
target_link_libraries(
yaze_emu PUBLIC
${ABSL_TARGETS}
${SDL_TARGETS}
${PNG_LIBRARIES}
${CMAKE_DL_LIBS}
ImGui
asar-static
target_link_libraries(yaze_emu PRIVATE
yaze_editor
yaze_emulator
yaze_agent
yaze_test_support
absl::flags
absl::flags_parse
absl::failure_signal_handler
)
# Conditionally link ImGui Test Engine
if(YAZE_ENABLE_UI_TESTS)
target_link_libraries(yaze_emu PUBLIC ImGuiTestEngine)
target_compile_definitions(yaze_emu PRIVATE YAZE_ENABLE_IMGUI_TEST_ENGINE=1)
else()
target_compile_definitions(yaze_emu PRIVATE YAZE_ENABLE_IMGUI_TEST_ENGINE=0)
endif()
# Headless Emulator Test Harness
add_executable(yaze_emu_test emu_test.cc)
target_include_directories(yaze_emu_test PRIVATE
${CMAKE_SOURCE_DIR}/src
${CMAKE_SOURCE_DIR}/incl
${PROJECT_BINARY_DIR}
)
target_link_libraries(yaze_emu_test PRIVATE
yaze_emulator
yaze_util
absl::flags
absl::flags_parse
absl::status
absl::strings
absl::str_format
)
message(STATUS "✓ yaze_emu_test: Headless emulator test harness configured")
message(STATUS "✓ yaze_emu: Standalone emulator executable configured")
else()
message(STATUS "○ Standalone emulator builds disabled (YAZE_BUILD_EMU=OFF or YAZE_MINIMAL_BUILD=ON)")
message(STATUS " Note: yaze_emulator library still available for main app and tests")
endif()

View File

@@ -13,6 +13,10 @@
add_library(yaze_emulator STATIC ${YAZE_APP_EMU_SRC})
target_precompile_headers(yaze_emulator PRIVATE
"$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_SOURCE_DIR}/src/yaze_pch.h>"
)
target_include_directories(yaze_emulator PUBLIC
${CMAKE_SOURCE_DIR}/src
${CMAKE_SOURCE_DIR}/src/app

View File

@@ -36,9 +36,7 @@ set(
add_library(yaze_gfx STATIC ${YAZE_APP_GFX_SRC})
target_precompile_headers(yaze_gfx PRIVATE
<vector>
<string>
<memory>
"$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_SOURCE_DIR}/src/yaze_pch.h>"
)
target_include_directories(yaze_gfx PUBLIC

View File

@@ -1,33 +1,34 @@
set(
YAZE_GUI_SRC
app/gui/canvas.cc
app/gui/canvas/bpp_format_ui.cc
app/gui/canvas/canvas_automation_api.cc
app/gui/canvas/canvas_context_menu.cc
app/gui/canvas/canvas_interaction_handler.cc
app/gui/canvas/canvas_modals.cc
app/gui/canvas/canvas_performance_integration.cc
app/gui/canvas/canvas_usage_tracker.cc
app/gui/canvas/canvas_utils.cc
app/gui/color.cc
app/gui/editor_card_manager.cc
app/gui/editor_layout.cc
app/gui/input.cc
app/gui/modules/asset_browser.cc
app/gui/modules/text_editor.cc
app/gui/widgets/agent_chat_widget.cc
app/gui/widgets/dungeon_object_emulator_preview.cc
app/gui/widgets/collaboration_panel.cc
app/gui/canvas.cc
app/gui/canvas/canvas_utils.cc
app/gui/widgets/palette_widget.cc
app/gui/widgets/palette_editor_widget.cc
app/gui/input.cc
app/gui/style.cc
app/gui/color.cc
app/gui/theme_manager.cc
app/gui/canvas/bpp_format_ui.cc
app/gui/widgets/widget_id_registry.cc
app/gui/widgets/widget_auto_register.cc
app/gui/widgets/widget_state_capture.cc
app/gui/ui_helpers.cc
app/gui/editor_layout.cc
app/gui/editor_card_manager.cc
# Canvas system components
app/gui/canvas/canvas_modals.cc
app/gui/canvas/canvas_context_menu.cc
app/gui/canvas/canvas_usage_tracker.cc
app/gui/canvas/canvas_performance_integration.cc
app/gui/canvas/canvas_interaction_handler.cc
app/gui/canvas/canvas_automation_api.cc
app/gui/widgets/agent_chat_widget.cc
app/gui/widgets/collaboration_panel.cc
app/gui/widgets/dungeon_object_emulator_preview.cc
app/gui/widgets/palette_editor_widget.cc
app/gui/widgets/palette_widget.cc
app/gui/widgets/tile_selector_widget.cc
app/gui/widgets/widget_auto_register.cc
app/gui/widgets/widget_id_registry.cc
app/gui/widgets/widget_measurement.cc
app/gui/widgets/widget_state_capture.cc
# Canvas system components
)
# ==============================================================================
@@ -47,12 +48,7 @@ set(
add_library(yaze_gui STATIC ${YAZE_GUI_SRC})
target_precompile_headers(yaze_gui PRIVATE
<array>
<memory>
<set>
<string>
<string_view>
<vector>
"$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_SOURCE_DIR}/src/yaze_pch.h>"
)
target_include_directories(yaze_gui PUBLIC

View File

@@ -67,6 +67,8 @@ void EnhancedTheme::ApplyToImGui() const {
colors[ImGuiCol_Tab] = ConvertColorToImVec4(tab);
colors[ImGuiCol_TabHovered] = ConvertColorToImVec4(tab_hovered);
colors[ImGuiCol_TabSelected] = ConvertColorToImVec4(tab_active);
colors[ImGuiCol_TabUnfocused] = ConvertColorToImVec4(tab_unfocused);
colors[ImGuiCol_TabUnfocusedActive] = ConvertColorToImVec4(tab_unfocused_active);
colors[ImGuiCol_DockingPreview] = ConvertColorToImVec4(docking_preview);
colors[ImGuiCol_DockingEmptyBg] = ConvertColorToImVec4(docking_empty_bg);
@@ -170,6 +172,8 @@ void ThemeManager::CreateFallbackYazeClassic() {
theme.tab = RGBA(46, 66, 46); // alttpDarkGreen
theme.tab_hovered = RGBA(71, 92, 71); // alttpMidGreen
theme.tab_active = RGBA(89, 119, 89); // TabActive
theme.tab_unfocused = RGBA(37, 52, 37); // Darker version of tab
theme.tab_unfocused_active = RGBA(62, 83, 62); // Darker version of tab_active
// Complete all remaining ImGui colors from original ColorsYaze() function
theme.title_bg = RGBA(71, 92, 71); // alttpMidGreen
@@ -875,6 +879,8 @@ void ThemeManager::ApplyClassicYazeTheme() {
classic_theme.tab = RGBA(46, 66, 46); // alttpDarkGreen
classic_theme.tab_hovered = RGBA(71, 92, 71); // alttpMidGreen
classic_theme.tab_active = RGBA(89, 119, 89); // TabActive
classic_theme.tab_unfocused = RGBA(37, 52, 37); // Darker version of tab
classic_theme.tab_unfocused_active = RGBA(62, 83, 62); // Darker version of tab_active
// Complete all remaining ImGui colors from original ColorsYaze() function
classic_theme.title_bg = RGBA(71, 92, 71); // alttpMidGreen
@@ -1432,6 +1438,8 @@ void ThemeManager::ShowSimpleThemeEditor(bool* p_open) {
{"Tab", &edit_theme.tab},
{"Tab Hovered", &edit_theme.tab_hovered},
{"Tab Active", &edit_theme.tab_active},
{"Tab Unfocused", &edit_theme.tab_unfocused},
{"Tab Unfocused Active", &edit_theme.tab_unfocused_active},
{"Tab Dimmed", &edit_theme.tab_dimmed},
{"Tab Dimmed Selected", &edit_theme.tab_dimmed_selected},
{"Title Background", &edit_theme.title_bg},

View File

@@ -108,6 +108,8 @@ struct EnhancedTheme {
Color tree_lines;
// Additional ImGui colors for complete coverage
Color tab_unfocused;
Color tab_unfocused_active;
Color tab_dimmed;
Color tab_dimmed_selected;
Color tab_dimmed_selected_overline;

View File

@@ -23,6 +23,10 @@ endif()
add_library(yaze_net STATIC ${YAZE_NET_SRC})
target_precompile_headers(yaze_net PRIVATE
"$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_SOURCE_DIR}/src/yaze_pch.h>"
)
target_include_directories(yaze_net PUBLIC
${CMAKE_SOURCE_DIR}/src
${CMAKE_SOURCE_DIR}/src/lib

View File

@@ -4,6 +4,10 @@
#import "util/file_util.h"
#import "app/editor/editor.h"
#import "app/rom.h"
#include <span>
#include <vector>
using std::span;
#if defined(__APPLE__) && defined(__MACH__)
/* Apple OSX and iOS (Darwin). */

View File

@@ -18,16 +18,13 @@ set(YAZE_TEST_SOURCES
add_library(yaze_test_support STATIC ${YAZE_TEST_SOURCES})
target_precompile_headers(yaze_test_support PRIVATE
<memory>
<set>
<string>
<string_view>
<vector>
"$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_SOURCE_DIR}/src/yaze_pch.h>"
)
target_include_directories(yaze_test_support PUBLIC
${CMAKE_SOURCE_DIR}/src
${CMAKE_SOURCE_DIR}/incl
${CMAKE_SOURCE_DIR}/src/lib
${PROJECT_BINARY_DIR}
)
@@ -41,11 +38,17 @@ target_link_libraries(yaze_test_support PUBLIC
yaze_common
)
# Link agent library if gRPC is enabled (for z3ed test suites)
# Link agent library if available (for z3ed test suites)
# yaze_agent contains all the CLI service code (tile16_proposal_generator, gui_automation_client, etc.)
if(YAZE_WITH_GRPC)
if(TARGET yaze_agent)
target_link_libraries(yaze_test_support PUBLIC yaze_agent)
message(STATUS "✓ z3ed test suites enabled (YAZE_WITH_GRPC=ON)")
if(YAZE_WITH_GRPC)
message(STATUS "✓ z3ed test suites enabled with gRPC support")
else()
message(STATUS "✓ z3ed test suites enabled (without gRPC)")
endif()
else()
message(STATUS "○ z3ed test suites disabled (yaze_agent not built)")
endif()
message(STATUS "✓ yaze_test_support library configured")

View File

@@ -1675,9 +1675,9 @@ absl::Status TestManager::TestRomDataIntegrity(Rom* rom) {
});
}
#if defined(YAZE_WITH_GRPC)
std::string TestManager::RegisterHarnessTest(const std::string& name,
const std::string& category) {
#if defined(YAZE_WITH_GRPC)
absl::MutexLock lock(&harness_history_mutex_);
std::string test_id = absl::StrCat("harness_", absl::ToUnixMicros(absl::Now()), "_", harness_history_.size());
HarnessTestExecution execution;
@@ -1693,12 +1693,15 @@ std::string TestManager::RegisterHarnessTest(const std::string& name,
aggregate.latest_execution = execution;
harness_history_order_.push_back(test_id);
return test_id;
}
#else
std::string TestManager::RegisterHarnessTest(const std::string& name,
const std::string& category) {
(void)name;
(void)category;
return {};
#endif
}
#endif
#if defined(YAZE_WITH_GRPC)
void TestManager::MarkHarnessTestRunning(const std::string& test_id) {
@@ -1852,10 +1855,12 @@ void TestManager::CaptureFailureContext(const std::string& test_id) {
if (harness_listener_) {
harness_listener_->OnHarnessTestUpdated(execution);
}
#else
(void)test_id;
#endif
}
#else
void TestManager::CaptureFailureContext(const std::string& test_id) {
(void)test_id;
}
#endif
#if defined(YAZE_WITH_GRPC)
void TestManager::TrimHarnessHistoryLocked() {
@@ -1870,14 +1875,6 @@ absl::Status TestManager::ReplayLastPlan() {
return absl::FailedPreconditionError("Harness plan replay not available");
}
absl::Status TestManager::ShowHarnessDashboard() {
return absl::OkStatus();
}
absl::Status TestManager::ShowHarnessActiveTests() {
return absl::OkStatus();
}
void TestManager::SetHarnessListener(HarnessListener* listener) {
absl::MutexLock lock(&mutex_);
harness_listener_ = listener;
@@ -1886,15 +1883,24 @@ void TestManager::SetHarnessListener(HarnessListener* listener) {
absl::Status TestManager::ReplayLastPlan() {
return absl::UnimplementedError("Harness features require YAZE_WITH_GRPC");
}
#endif
absl::Status TestManager::ShowHarnessDashboard() {
// These methods are always available, but may return unimplemented without GRPC
#if defined(YAZE_WITH_GRPC)
return absl::OkStatus();
#else
return absl::UnimplementedError("Harness features require YAZE_WITH_GRPC");
#endif
}
absl::Status TestManager::ShowHarnessActiveTests() {
#if defined(YAZE_WITH_GRPC)
return absl::OkStatus();
#else
return absl::UnimplementedError("Harness features require YAZE_WITH_GRPC");
}
#endif
}
void TestManager::RecordPlanSummary(const std::string& summary) {
(void)summary;

View File

@@ -299,8 +299,16 @@ class TestManager {
void SetHarnessListener(HarnessListener* listener);
absl::Status ReplayLastPlan();
#else
// Stub implementations when GRPC is not available
std::string RegisterHarnessTest(const std::string& name,
const std::string& category);
void CaptureFailureContext(const std::string& test_id);
absl::Status ReplayLastPlan();
#endif
// These methods are always available
absl::Status ShowHarnessDashboard();
absl::Status ShowHarnessActiveTests();
void RecordPlanSummary(const std::string& summary);

View File

@@ -1,21 +1,22 @@
set(
YAZE_APP_ZELDA3_SRC
app/zelda3/hyrule_magic.cc
app/zelda3/zelda3_labels.cc
app/zelda3/overworld/overworld_map.cc
app/zelda3/overworld/overworld.cc
app/zelda3/screen/inventory.cc
app/zelda3/screen/title_screen.cc
app/zelda3/screen/dungeon_map.cc
app/zelda3/sprite/sprite.cc
app/zelda3/sprite/sprite_builder.cc
app/zelda3/music/tracker.cc
app/zelda3/dungeon/room.cc
app/zelda3/dungeon/room_object.cc
app/zelda3/dungeon/object_parser.cc
app/zelda3/dungeon/object_drawer.cc
app/zelda3/dungeon/dungeon_editor_system.cc
app/zelda3/dungeon/dungeon_object_editor.cc
app/zelda3/dungeon/object_drawer.cc
app/zelda3/dungeon/object_parser.cc
app/zelda3/dungeon/room.cc
app/zelda3/dungeon/room_layout.cc
app/zelda3/dungeon/room_object.cc
app/zelda3/hyrule_magic.cc
app/zelda3/music/tracker.cc
app/zelda3/overworld/overworld.cc
app/zelda3/overworld/overworld_map.cc
app/zelda3/screen/dungeon_map.cc
app/zelda3/screen/inventory.cc
app/zelda3/screen/title_screen.cc
app/zelda3/sprite/sprite.cc
app/zelda3/sprite/sprite_builder.cc
app/zelda3/zelda3_labels.cc
)
# ==============================================================================
@@ -34,12 +35,7 @@ set(
add_library(yaze_zelda3 STATIC ${YAZE_APP_ZELDA3_SRC})
target_precompile_headers(yaze_zelda3 PRIVATE
<array>
<memory>
<set>
<string>
<string_view>
<vector>
"$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_SOURCE_DIR}/src/yaze_pch.h>"
)
target_include_directories(yaze_zelda3 PUBLIC

View File

@@ -1,69 +1,3 @@
include(FetchContent)
function(_yaze_ensure_yaml_cpp _out_target)
if(TARGET yaml-cpp::yaml-cpp)
set(${_out_target} yaml-cpp::yaml-cpp PARENT_SCOPE)
return()
endif()
if(TARGET yaml-cpp)
set(${_out_target} yaml-cpp PARENT_SCOPE)
return()
endif()
find_package(yaml-cpp CONFIG QUIET)
if(TARGET yaml-cpp::yaml-cpp)
set(${_out_target} yaml-cpp::yaml-cpp PARENT_SCOPE)
return()
elseif(TARGET yaml-cpp)
set(${_out_target} yaml-cpp PARENT_SCOPE)
return()
endif()
message(STATUS "yaml-cpp not found via package config, fetching from source")
FetchContent_Declare(yaml-cpp
GIT_REPOSITORY https://github.com/jbeder/yaml-cpp.git
GIT_TAG 0.8.0
)
FetchContent_GetProperties(yaml-cpp)
if(NOT yaml-cpp_POPULATED)
FetchContent_Populate(yaml-cpp)
set(_yaml_cpp_cmakelists "${yaml-cpp_SOURCE_DIR}/CMakeLists.txt")
if(EXISTS "${_yaml_cpp_cmakelists}")
file(READ "${_yaml_cpp_cmakelists}" _yaml_cpp_cmake_contents)
if(_yaml_cpp_cmake_contents MATCHES "cmake_minimum_required\\(VERSION 3\\.4\\)")
string(REPLACE "cmake_minimum_required(VERSION 3.4)"
"cmake_minimum_required(VERSION 3.5)"
_yaml_cpp_cmake_contents "${_yaml_cpp_cmake_contents}")
file(WRITE "${_yaml_cpp_cmakelists}" "${_yaml_cpp_cmake_contents}")
endif()
endif()
set(YAML_CPP_BUILD_TESTS OFF CACHE BOOL "Disable yaml-cpp tests" FORCE)
set(YAML_CPP_BUILD_CONTRIB OFF CACHE BOOL "Disable yaml-cpp contrib" FORCE)
set(YAML_CPP_BUILD_TOOLS OFF CACHE BOOL "Disable yaml-cpp tools" FORCE)
set(YAML_CPP_INSTALL OFF CACHE BOOL "Disable yaml-cpp install" FORCE)
set(YAML_CPP_FORMAT_SOURCE OFF CACHE BOOL "Disable yaml-cpp format target" FORCE)
add_subdirectory(${yaml-cpp_SOURCE_DIR} ${yaml-cpp_BINARY_DIR} EXCLUDE_FROM_ALL)
if(NOT TARGET yaml-cpp)
message(FATAL_ERROR "yaml-cpp target was not created after fetching")
endif()
# Ensure the fetched target exposes its public headers
target_include_directories(yaml-cpp PUBLIC ${yaml-cpp_SOURCE_DIR}/include)
endif()
set(${_out_target} yaml-cpp PARENT_SCOPE)
endfunction()
_yaze_ensure_yaml_cpp(YAZE_YAML_CPP_TARGET)
set(YAZE_AGENT_SOURCES
cli/service/agent/proposal_executor.cc
cli/handlers/agent/todo_commands.cc
@@ -73,6 +7,7 @@ set(YAZE_AGENT_SOURCES
cli/service/agent/tool_dispatcher.cc
cli/service/agent/learned_knowledge_service.cc
cli/service/agent/todo_manager.cc
cli/service/agent/vim_mode.cc
cli/service/ai/ai_service.cc
cli/service/ai/ai_action_parser.cc
cli/service/ai/vision_action_refiner.cc
@@ -91,8 +26,9 @@ set(YAZE_AGENT_SOURCES
cli/service/resources/resource_context_builder.cc
cli/service/resources/command_context.cc
cli/service/resources/command_handler.cc
cli/handlers/command_wrappers.cc
cli/handlers/agent.cc
cli/handlers/command_handlers.cc
cli/handlers/agent/simple_chat_command.cc
cli/handlers/game/overworld_inspect.cc
cli/handlers/game/message.cc
cli/handlers/rom/mock_rom.cc
@@ -107,6 +43,7 @@ set(YAZE_AGENT_SOURCES
cli/handlers/graphics/palette_commands.cc
cli/handlers/tools/emulator_commands.cc
cli/handlers/game/message_commands.cc
cli/handlers/graphics/sprite_commands.cc
# ROM commands
cli/handlers/rom/rom_commands.cc
cli/handlers/rom/project_commands.cc
@@ -122,8 +59,14 @@ add_library(yaze_agent STATIC ${YAZE_AGENT_SOURCES})
set(_yaze_agent_link_targets
yaze_common
yaze_util
yaze_gfx
yaze_gui
yaze_core_lib
yaze_zelda3
yaze_emulator
${ABSL_TARGETS}
${YAZE_YAML_CPP_TARGET}
yaml-cpp
)
target_link_libraries(yaze_agent PUBLIC ${_yaze_agent_link_targets})
@@ -135,15 +78,9 @@ target_include_directories(yaze_agent
${CMAKE_SOURCE_DIR}/third_party/httplib
${CMAKE_SOURCE_DIR}/third_party/json/include
${CMAKE_SOURCE_DIR}/src/lib
${CMAKE_SOURCE_DIR}/src/cli/handlers
)
if(YAZE_YAML_CPP_TARGET)
get_target_property(_yaze_yaml_include_dirs ${YAZE_YAML_CPP_TARGET} INTERFACE_INCLUDE_DIRECTORIES)
if(_yaze_yaml_include_dirs)
target_include_directories(yaze_agent PUBLIC ${_yaze_yaml_include_dirs})
endif()
endif()
if(SDL2_INCLUDE_DIR)
target_include_directories(yaze_agent PUBLIC ${SDL2_INCLUDE_DIR})
endif()

File diff suppressed because it is too large Load Diff

View File

@@ -13,6 +13,10 @@
#include "app/snes.h"
#include "util/macro.h"
#include "cli/service/resources/command_handler.h"
#include <map>
#include <memory>
// Forward declarations
namespace ftxui {
class ScreenInteractive;
@@ -24,215 +28,21 @@ namespace cli {
// Forward declaration
class TuiComponent;
class CommandHandler {
public:
CommandHandler() = default;
virtual ~CommandHandler() = default;
virtual absl::Status Run(const std::vector<std::string>& arg_vec) = 0;
virtual void RunTUI(ftxui::ScreenInteractive& screen) {
// Default implementation does nothing
}
Rom rom_;
};
struct CommandInfo {
std::string name;
std::string description;
std::string usage;
std::function<absl::Status(const std::vector<std::string>&)> handler;
};
class ModernCLI {
public:
ModernCLI();
absl::Status Run(int argc, char* argv[]);
CommandHandler* GetCommandHandler(const std::string& name);
void PrintTopLevelHelp() const;
void PrintCategoryHelp(const std::string& category) const;
void PrintCommandSummary() const;
std::map<std::string, CommandInfo> commands_;
private:
void SetupCommands();
void ShowHelp();
void ShowCategoryHelp(const std::string& category);
void ShowCategoryHelp(const std::string& category) const;
void ShowCommandSummary() const;
// Command Handlers
absl::Status HandleAsarPatchCommand(const std::vector<std::string>& args);
absl::Status HandleBpsPatchCommand(const std::vector<std::string>& args);
absl::Status HandleExtractSymbolsCommand(const std::vector<std::string>& args);
absl::Status HandleAgentCommand(const std::vector<std::string>& args);
absl::Status HandleCollabCommand(const std::vector<std::string>& args);
absl::Status HandleProjectBuildCommand(const std::vector<std::string>& args);
absl::Status HandleProjectInitCommand(const std::vector<std::string>& args);
absl::Status HandleRomInfoCommand(const std::vector<std::string>& args);
absl::Status HandleRomGenerateGoldenCommand(const std::vector<std::string>& args);
absl::Status HandleRomDiffCommand(const std::vector<std::string>& args);
absl::Status HandleDungeonExportCommand(const std::vector<std::string>& args);
absl::Status HandleDungeonListObjectsCommand(const std::vector<std::string>& args);
absl::Status HandleGfxExportCommand(const std::vector<std::string>& args);
absl::Status HandleGfxImportCommand(const std::vector<std::string>& args);
absl::Status HandleCommandPaletteCommand(const std::vector<std::string>& args);
absl::Status HandlePaletteExportCommand(const std::vector<std::string>& args);
absl::Status HandlePaletteImportCommand(const std::vector<std::string>& args);
absl::Status HandlePaletteCommand(const std::vector<std::string>& args);
absl::Status HandleRomValidateCommand(const std::vector<std::string>& args);
absl::Status HandleOverworldGetTileCommand(const std::vector<std::string>& args);
absl::Status HandleOverworldFindTileCommand(const std::vector<std::string>& args);
absl::Status HandleOverworldDescribeMapCommand(const std::vector<std::string>& args);
absl::Status HandleOverworldListWarpsCommand(const std::vector<std::string>& args);
absl::Status HandleOverworldSetTileCommand(const std::vector<std::string>& args);
absl::Status HandleOverworldSelectRectCommand(const std::vector<std::string>& args);
absl::Status HandleOverworldScrollToCommand(const std::vector<std::string>& args);
absl::Status HandleOverworldSetZoomCommand(const std::vector<std::string>& args);
absl::Status HandleOverworldGetVisibleRegionCommand(const std::vector<std::string>& args);
absl::Status HandleSpriteCreateCommand(const std::vector<std::string>& args);
absl::Status HandleChatEntryCommand(const std::vector<std::string>& args);
absl::Status HandleProposalCommand(const std::vector<std::string>& args);
absl::Status HandleWidgetCommand(const std::vector<std::string>& args);
};
// Legacy command classes removed - using new CommandHandler system
// See TODO comments in cli.cc for implementation roadmap
class Open : public CommandHandler {
public:
absl::Status Run(const std::vector<std::string>& arg_vec) override {
auto const& arg = arg_vec[0];
RETURN_IF_ERROR(rom_.LoadFromFile(arg, RomLoadOptions::CliDefaults()))
std::cout << "Title: " << rom_.title() << std::endl;
std::cout << "Size: 0x" << std::hex << rom_.size() << std::endl;
return absl::OkStatus();
}
};
class Backup : public CommandHandler {
public:
absl::Status Run(const std::vector<std::string>& arg_vec) override {
RETURN_IF_ERROR(rom_.LoadFromFile(arg_vec[0]))
Rom::SaveSettings settings;
settings.backup = true;
if (arg_vec.size() == 2) {
// Optional filename added
settings.filename = arg_vec[1];
}
RETURN_IF_ERROR(rom_.SaveToFile(settings))
return absl::OkStatus();
}
};
class Compress : public CommandHandler {
public:
absl::Status Run(const std::vector<std::string>& arg_vec) override;
};
class Decompress : public CommandHandler {
public:
absl::Status Run(const std::vector<std::string>& arg_vec) override;
};
/**
* @brief Convert a SNES address to a PC address.
* @param arg_vec `-s <address>`
* @return absl::Status
*/
class SnesToPcCommand : public CommandHandler {
public:
absl::Status Run(const std::vector<std::string>& arg_vec) override {
auto arg = arg_vec[0];
std::stringstream ss(arg.data());
uint32_t snes_address;
ss >> std::hex >> snes_address;
uint32_t pc_address = SnesToPc(snes_address);
std::cout << std::hex << pc_address << std::endl;
return absl::OkStatus();
}
};
/**
* @brief Convert a PC address to a SNES address.
* @param arg_vec `-p <address>`
* @return absl::Status
*/
class PcToSnesCommand : public CommandHandler {
public:
absl::Status Run(const std::vector<std::string>& arg_vec) override {
auto arg = arg_vec[0];
std::stringstream ss(arg.data());
uint32_t pc_address;
ss >> std::hex >> pc_address;
uint32_t snes_address = PcToSnes(pc_address);
std::cout << "SNES LoROM Address: ";
std::cout << "$" << std::uppercase << std::hex << snes_address << "\n";
return absl::OkStatus();
}
};
/**
* @brief Read from a Rom file.
* @param arg_vec `-r <rom_file> <address> <optional:length, default: 0x01>`
* @return absl::Status
*/
class ReadFromRom : public CommandHandler {
public:
absl::Status Run(const std::vector<std::string>& arg_vec) override {
RETURN_IF_ERROR(rom_.LoadFromFile(arg_vec[0]))
std::stringstream ss(arg_vec[1].data());
uint32_t offset;
ss >> std::hex >> offset;
uint32_t length = 0x01;
if (!arg_vec[2].empty()) {
length = std::stoi(arg_vec[2]);
}
if (length > 1) {
auto returned_bytes_status = rom_.ReadByteVector(offset, length);
if (!returned_bytes_status.ok()) {
return returned_bytes_status.status();
}
auto returned_bytes = returned_bytes_status.value();
for (const auto& each : returned_bytes) {
std::cout << each;
}
std::cout << std::endl;
} else {
auto byte = rom_.ReadByte(offset);
std::cout << std::hex << byte.value() << std::endl;
}
return absl::OkStatus();
}
};
/**
* @brief Expand a Rom file.
* @param arg_vec `-x <rom_file> <file_size>`
* @return absl::Status
*/
class Expand : public CommandHandler {
public:
absl::Status Run(const std::vector<std::string>& arg_vec) override {
RETURN_IF_ERROR(rom_.LoadFromFile(arg_vec[0]))
std::stringstream ss(arg_vec[1].data());
uint32_t size;
ss >> std::hex >> size;
rom_.Expand(size);
std::cout << "Successfully expanded ROM to " << std::hex << size
<< std::endl;
return absl::OkStatus();
}
std::map<std::string, std::unique_ptr<resources::CommandHandler>> commands_;
};
} // namespace cli

320
src/cli/handlers/README.md Normal file
View File

@@ -0,0 +1,320 @@
# YAZE Modern Command Handler Architecture
This directory contains the modern command handler system that provides a consistent interface for both CLI and AI agent interactions with ROM data.
## Architecture Overview
The command handler system follows a clean, layered architecture:
```
┌─────────────────────────────────────────┐
│ CLI / Agent Interface │
│ (cli.cc, agent.cc, simple-chat, etc) │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ Command Handler Base Class │
│ (resources/command_handler.h) │
│ - Argument parsing │
│ - ROM context management │
│ - Output formatting │
│ - Error handling │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ Concrete Command Handlers │
│ (handlers/*/*) │
│ - SpriteListCommandHandler │
│ - DungeonDescribeRoomCommandHandler │
│ - OverworldFindTileCommandHandler │
│ - PaletteGetColorsCommandHandler │
│ - etc... │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ Core YAZE Libraries │
│ - zelda3/ (overworld, dungeon, sprite) │
│ - gfx/ (graphics, palette) │
│ - app/editor/ (ROM operations) │
└─────────────────────────────────────────┘
```
## Namespace Structure
All command handlers are organized under a simplified namespace:
```cpp
namespace yaze {
namespace cli {
namespace handlers {
// All command handler classes live here
class SpriteListCommandHandler : public resources::CommandHandler { ... };
class DungeonDescribeRoomCommandHandler : public resources::CommandHandler { ... };
// etc.
}
}
}
```
## Directory Organization
```
handlers/
├── README.md (this file)
├── commands.h // Legacy command function declarations
├── command_wrappers.cc // Wrapper functions for backward compatibility
├── command_handlers.h // Forward declarations and factory functions
├── command_handlers.cc // Factory implementations
├── graphics/ // Graphics-related commands
│ ├── sprite_commands.h/.cc // Sprite listing and properties
│ ├── palette_commands.h/.cc // Palette manipulation
│ ├── hex_commands.h/.cc // Raw hex data access
│ └── gfx.cc // Legacy graphics commands
├── game/ // Game data inspection
│ ├── dungeon_commands.h/.cc // Dungeon room inspection
│ ├── overworld_commands.h/.cc // Overworld map inspection
│ ├── music_commands.h/.cc // Music track information
│ ├── dialogue_commands.h/.cc // Dialogue/message search
│ └── message_commands.h/.cc // Message data access
├── tools/ // Development tools
│ ├── resource_commands.h/.cc // Resource label inspection
│ ├── gui_commands.h/.cc // GUI automation
│ └── emulator_commands.h/.cc // Emulator/debugger control
└── agent/ // AI agent specific
├── general_commands.cc // Agent command routing
├── test_commands.cc // Test harness
└── todo_commands.h/.cc // Task management
```
## Creating a New Command Handler
### 1. Define the Handler Class
Create a header file (e.g., `new_feature_commands.h`):
```cpp
#ifndef YAZE_SRC_CLI_HANDLERS_NEW_FEATURE_COMMANDS_H_
#define YAZE_SRC_CLI_HANDLERS_NEW_FEATURE_COMMANDS_H_
#include "cli/service/resources/command_handler.h"
namespace yaze {
namespace cli {
namespace handlers {
class NewFeatureCommandHandler : public resources::CommandHandler {
public:
std::string GetName() const { return "new-feature"; }
std::string GetDescription() const {
return "Brief description of what this command does";
}
std::string GetUsage() const {
return "new-feature --arg1 <value> [--optional-arg2 <value>]";
}
absl::Status ValidateArgs(const resources::ArgumentParser& parser) override {
// Validate required arguments
return parser.RequireArgs({"arg1"});
}
absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser,
resources::OutputFormatter& formatter) override;
};
} // namespace handlers
} // namespace cli
} // namespace yaze
#endif // YAZE_SRC_CLI_HANDLERS_NEW_FEATURE_COMMANDS_H_
```
### 2. Implement the Handler
Create the implementation file (e.g., `new_feature_commands.cc`):
```cpp
#include "cli/handlers/new_feature_commands.h"
#include "absl/strings/str_format.h"
namespace yaze {
namespace cli {
namespace handlers {
absl::Status NewFeatureCommandHandler::Execute(
Rom* rom, const resources::ArgumentParser& parser,
resources::OutputFormatter& formatter) {
// Parse arguments
auto arg1 = parser.GetString("arg1").value();
auto arg2 = parser.GetString("optional-arg2").value_or("default");
// Begin output
formatter.BeginObject("New Feature Result");
formatter.AddField("input_arg", arg1);
// Do work with ROM
// ... use rom->read(), zelda3 classes, etc.
// Add results to formatter
formatter.AddField("result", "success");
formatter.BeginArray("items");
// ... add items
formatter.EndArray();
formatter.EndObject();
return absl::OkStatus();
}
} // namespace handlers
} // namespace cli
} // namespace yaze
```
### 3. Register in Factory
Add to `command_handlers.cc`:
```cpp
#include "cli/handlers/new_feature_commands.h"
std::vector<std::unique_ptr<resources::CommandHandler>> CreateCliCommandHandlers() {
// ... existing handlers ...
handlers.push_back(std::make_unique<NewFeatureCommandHandler>());
return handlers;
}
```
### 4. Add Forward Declaration
Add to `command_handlers.h`:
```cpp
// Forward declarations for command handler classes
class NewFeatureCommandHandler;
```
## Command Handler Base Class
The `resources::CommandHandler` base class provides:
### Lifecycle Methods
- `Run(args, rom_context)` - Main entry point that orchestrates the full command execution
- `ValidateArgs(parser)` - Override to validate command arguments
- `Execute(rom, parser, formatter)` - Override to implement command logic
### Helper Methods
- `GetUsage()` - Return usage string for help
- `GetName()` - Return command name
- `GetDescription()` - Return brief description
- `RequiresLabels()` - Return true if command needs ROM labels loaded
- `GetDefaultFormat()` - Return "json" or "text" for default output
- `GetOutputTitle()` - Return title for output object
## Argument Parsing
The `ArgumentParser` class handles common CLI patterns:
```cpp
// Get string argument
auto value = parser.GetString("arg_name").value_or("default");
// Get integer (supports hex with 0x prefix)
auto count = parser.GetInt("count").value_or(10);
// Get hex value
auto address = parser.GetHex("address").value();
// Check for flag
if (parser.HasFlag("verbose")) { ... }
// Get positional arguments
auto positional = parser.GetPositional();
// Require specific arguments
RETURN_IF_ERROR(parser.RequireArgs({"required1", "required2"}));
```
## Output Formatting
The `OutputFormatter` class provides consistent JSON/text output:
```cpp
// Begin object
formatter.BeginObject("Title");
// Add fields
formatter.AddField("string_field", "value");
formatter.AddField("int_field", 42);
formatter.AddField("bool_field", true);
formatter.AddHexField("address", 0x1234, 4); // Width in digits
// Arrays
formatter.BeginArray("items");
for (const auto& item : items) {
formatter.AddArrayItem(absl::StrFormat("Item %d", i));
}
formatter.EndArray();
// Nested objects
formatter.BeginObject("nested");
formatter.AddField("nested_field", "value");
formatter.EndObject();
// End object
formatter.EndObject();
```
## Integration with Public API
Command handlers are designed to work alongside the public C API defined in `incl/yaze.h` and `incl/zelda.h`.
- Handlers use internal C++ classes from `app/zelda3/`
- Output structures align with C API data types where possible
- Future: C API bridge will expose commands to external applications
## Best Practices
1. **Keep handlers focused** - One command per handler class
2. **Use existing zelda3 classes** - Don't duplicate ROM parsing logic
3. **Validate inputs early** - Use `ValidateArgs()` to catch errors
4. **Provide good error messages** - Return descriptive `absl::Status` errors
5. **Support both JSON and text** - Format output using `OutputFormatter`
6. **Document parameters** - Include full usage string in `GetUsage()`
7. **Test with agents** - Commands should be AI-friendly
8. **Mark unused rom parameter** - Use `Rom* /*rom*/` if not needed
## Testing
Test commands using the CLI:
```bash
# Direct command execution
./build/bin/z3ed agent sprite-list --limit 10 --format json --rom zelda3.sfc
# Via simple-chat interface
./build/bin/z3ed agent simple-chat --rom zelda3.sfc
> sprite-list --limit 5
# In agent test suite
./build/bin/z3ed agent test-conversation --rom zelda3.sfc
```
## Future Enhancements
- [ ] C API bridge for external language bindings
- [ ] Command auto-discovery and registration
- [ ] Per-command help system
- [ ] Command aliases and shortcuts
- [ ] Batch command execution
- [ ] Command pipelines (output of one → input of another)
- [ ] Interactive command REPL improvements

View File

@@ -1,4 +1,3 @@
#include "cli/handlers/commands.h"
#include "cli/handlers/agent/todo_commands.h"
#include "cli/cli.h"
@@ -8,19 +7,70 @@
#include "absl/flags/declare.h"
#include "absl/flags/flag.h"
#include "absl/status/status.h"
#include "cli/handlers/agent/common.h"
#include "cli/handlers/agent/todo_commands.h"
#include "cli/handlers/agent/simple_chat_command.h"
#include "cli/handlers/tools/resource_commands.h"
#include "cli/handlers/game/dungeon_commands.h"
#include "cli/handlers/game/overworld_commands.h"
#include "cli/handlers/tools/gui_commands.h"
#include "cli/handlers/tools/emulator_commands.h"
ABSL_DECLARE_FLAG(bool, quiet);
namespace yaze {
namespace cli {
// Forward declarations from general_commands.cc
namespace agent {
absl::Status HandlePlanCommand(const std::vector<std::string>& args);
absl::Status HandleTestCommand(const std::vector<std::string>& args);
absl::Status HandleTestConversationCommand(const std::vector<std::string>& args);
absl::Status HandleGuiCommand(const std::vector<std::string>& args);
absl::Status HandleLearnCommand(const std::vector<std::string>& args);
absl::Status HandleListCommand();
absl::Status HandleDescribeCommand(const std::vector<std::string>& args);
// Wrapper functions to call CommandHandlers
absl::Status HandleResourceListCommand(const std::vector<std::string>& args, Rom* rom) {
handlers::ResourceListCommandHandler handler;
return handler.Run(args, rom);
}
absl::Status HandleResourceSearchCommand(const std::vector<std::string>& args, Rom* rom) {
handlers::ResourceSearchCommandHandler handler;
return handler.Run(args, rom);
}
absl::Status HandleDungeonListSpritesCommand(const std::vector<std::string>& args, Rom* rom) {
handlers::DungeonListSpritesCommandHandler handler;
return handler.Run(args, rom);
}
absl::Status HandleDungeonDescribeRoomCommand(const std::vector<std::string>& args, Rom* rom) {
handlers::DungeonDescribeRoomCommandHandler handler;
return handler.Run(args, rom);
}
absl::Status HandleOverworldFindTileCommand(const std::vector<std::string>& args, Rom* rom) {
handlers::OverworldFindTileCommandHandler handler;
return handler.Run(args, rom);
}
absl::Status HandleOverworldDescribeMapCommand(const std::vector<std::string>& args, Rom* rom) {
handlers::OverworldDescribeMapCommandHandler handler;
return handler.Run(args, rom);
}
absl::Status HandleOverworldListWarpsCommand(const std::vector<std::string>& args, Rom* rom) {
handlers::OverworldListWarpsCommandHandler handler;
return handler.Run(args, rom);
}
} // namespace agent
namespace {
// Forward declarations for functions implemented in other files
// Function declarations moved to commands.h
// Use handlers from command_wrappers.cc
using namespace yaze::cli::handlers;
constexpr absl::string_view kUsage =
"Usage: agent <subcommand> [options]\n"
"\n"
@@ -98,13 +148,11 @@ absl::Status HandleAgentCommand(const std::vector<std::string>& arg_vec) {
const std::string& subcommand = arg_vec[0];
std::vector<std::string> subcommand_args(arg_vec.begin() + 1, arg_vec.end());
// TODO: Implement proper ROM context handling
// For now, return unimplemented for commands that require ROM context
if (subcommand == "run") {
return absl::UnimplementedError("Agent run command requires ROM context - not yet implemented");
}
if (subcommand == "plan") {
return HandlePlanCommand(subcommand_args);
return agent::HandlePlanCommand(subcommand_args);
}
if (subcommand == "diff") {
return absl::UnimplementedError("Agent diff command requires ROM context - not yet implemented");
@@ -113,19 +161,19 @@ absl::Status HandleAgentCommand(const std::vector<std::string>& arg_vec) {
return absl::UnimplementedError("Agent accept command requires ROM context - not yet implemented");
}
if (subcommand == "test") {
return HandleTestCommand(subcommand_args);
return agent::HandleTestCommand(subcommand_args);
}
if (subcommand == "test-conversation") {
return HandleTestConversationCommand(subcommand_args);
return agent::HandleTestConversationCommand(subcommand_args);
}
if (subcommand == "gui") {
return HandleGuiCommand(subcommand_args);
return agent::HandleGuiCommand(subcommand_args);
}
if (subcommand == "learn") {
return HandleLearnCommand(subcommand_args);
return agent::HandleLearnCommand(subcommand_args);
}
if (subcommand == "list") {
return HandleListCommand();
return agent::HandleListCommand();
}
if (subcommand == "commit") {
return absl::UnimplementedError("Agent commit command requires ROM context - not yet implemented");
@@ -134,60 +182,57 @@ absl::Status HandleAgentCommand(const std::vector<std::string>& arg_vec) {
return absl::UnimplementedError("Agent revert command requires ROM context - not yet implemented");
}
if (subcommand == "describe") {
return HandleDescribeCommand(subcommand_args);
return agent::HandleDescribeCommand(subcommand_args);
}
if (subcommand == "resource-list") {
return HandleResourceListCommand(subcommand_args, nullptr);
return agent::HandleResourceListCommand(subcommand_args, nullptr);
}
if (subcommand == "resource-search") {
return HandleResourceSearchCommand(subcommand_args, nullptr);
return agent::HandleResourceSearchCommand(subcommand_args, nullptr);
}
if (subcommand == "dungeon-list-sprites") {
return HandleDungeonListSpritesCommand(subcommand_args, nullptr);
return agent::HandleDungeonListSpritesCommand(subcommand_args, nullptr);
}
if (subcommand == "dungeon-describe-room") {
return HandleDungeonDescribeRoomCommand(subcommand_args, nullptr);
return agent::HandleDungeonDescribeRoomCommand(subcommand_args, nullptr);
}
if (subcommand == "overworld-find-tile") {
return HandleOverworldFindTileCommand(subcommand_args, nullptr);
return agent::HandleOverworldFindTileCommand(subcommand_args, nullptr);
}
if (subcommand == "overworld-describe-map") {
return HandleOverworldDescribeMapCommand(subcommand_args, nullptr);
return agent::HandleOverworldDescribeMapCommand(subcommand_args, nullptr);
}
if (subcommand == "overworld-list-warps") {
return HandleOverworldListWarpsCommand(subcommand_args, nullptr);
}
if (subcommand == "chat") {
return absl::UnimplementedError("Agent chat command requires ROM context - not yet implemented");
}
if (subcommand == "simple-chat") {
return absl::UnimplementedError("Agent simple-chat command requires ROM context - not yet implemented");
}
if (subcommand == "todo") {
return handlers::HandleTodoCommand(subcommand_args);
return agent::HandleOverworldListWarpsCommand(subcommand_args, nullptr);
}
// if (subcommand == "chat") {
// return absl::UnimplementedError("Agent chat command requires ROM context - not yet implemented");
// }
// if (subcommand == "todo") {
// return handlers::HandleTodoCommand(subcommand_args);
// }
// Hex manipulation commands
if (subcommand == "hex-read") {
return HandleHexRead(subcommand_args, nullptr);
}
if (subcommand == "hex-write") {
return HandleHexWrite(subcommand_args, nullptr);
}
if (subcommand == "hex-search") {
return HandleHexSearch(subcommand_args, nullptr);
}
// // Hex manipulation commands
// if (subcommand == "hex-read") {
// return HandleHexRead(subcommand_args, nullptr);
// }
// if (subcommand == "hex-write") {
// return HandleHexWrite(subcommand_args, nullptr);
// }
// if (subcommand == "hex-search") {
// return HandleHexSearch(subcommand_args, nullptr);
// }
// Palette manipulation commands
if (subcommand == "palette-get-colors") {
return HandlePaletteGetColors(subcommand_args, nullptr);
}
if (subcommand == "palette-set-color") {
return HandlePaletteSetColor(subcommand_args, nullptr);
}
if (subcommand == "palette-analyze") {
return HandlePaletteAnalyze(subcommand_args, nullptr);
}
// // Palette manipulation commands
// if (subcommand == "palette-get-colors") {
// return HandlePaletteGetColors(subcommand_args, nullptr);
// }
// if (subcommand == "palette-set-color") {
// return HandlePaletteSetColor(subcommand_args, nullptr);
// }
// if (subcommand == "palette-analyze") {
// return HandlePaletteAnalyze(subcommand_args, nullptr);
// }
return absl::InvalidArgumentError(std::string(kUsage));
}

View File

@@ -0,0 +1,37 @@
#include "cli/handlers/agent/simple_chat_command.h"
#include "cli/service/agent/simple_chat_session.h"
namespace yaze {
namespace cli {
namespace handlers {
absl::Status SimpleChatCommandHandler::Execute(
Rom* rom, const resources::ArgumentParser& parser,
resources::OutputFormatter& formatter) {
agent::SimpleChatSession session;
session.SetRomContext(rom);
// Configure session from parser
agent::AgentConfig config;
if (parser.HasFlag("verbose")) {
config.verbose = true;
}
if (auto format = parser.GetString("format")) {
// Simplified format handling
}
session.SetConfig(config);
if (auto file = parser.GetString("file")) {
return session.RunBatch(*file);
} else if (auto prompt = parser.GetString("prompt")) {
std::string response;
return session.SendAndWaitForResponse(*prompt, &response);
} else {
return session.RunInteractive();
}
}
} // namespace handlers
} // namespace cli
} // namespace yaze

View File

@@ -0,0 +1,31 @@
#ifndef YAZE_CLI_HANDLERS_AGENT_SIMPLE_CHAT_COMMAND_H_
#define YAZE_CLI_HANDLERS_AGENT_SIMPLE_CHAT_COMMAND_H_
#include "cli/service/resources/command_handler.h"
namespace yaze {
namespace cli {
namespace handlers {
class SimpleChatCommandHandler : public resources::CommandHandler {
public:
std::string GetName() const { return "simple-chat"; }
std::string GetDescription() const { return "Simple text-based chat with the AI agent."; }
std::string GetUsage() const override {
return "simple-chat [--prompt <message>] [--file <path>] [--format <format>]";
}
protected:
absl::Status ValidateArgs(const resources::ArgumentParser& parser) override {
return absl::OkStatus();
}
absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser,
resources::OutputFormatter& formatter) override;
};
} // namespace handlers
} // namespace cli
} // namespace yaze
#endif // YAZE_CLI_HANDLERS_AGENT_SIMPLE_CHAT_COMMAND_H_

View File

@@ -60,9 +60,14 @@ std::vector<std::unique_ptr<resources::CommandHandler>> CreateCliCommandHandlers
return handlers;
}
#include "cli/handlers/agent/simple_chat_command.h"
std::vector<std::unique_ptr<resources::CommandHandler>> CreateAgentCommandHandlers() {
std::vector<std::unique_ptr<resources::CommandHandler>> handlers;
// Add simple-chat command handler
handlers.push_back(std::make_unique<SimpleChatCommandHandler>());
// Resource inspection tools
handlers.push_back(std::make_unique<ResourceListCommandHandler>());
handlers.push_back(std::make_unique<ResourceSearchCommandHandler>());

View File

@@ -7,6 +7,8 @@
#include "cli/service/resources/command_handler.h"
#include "cli/handlers/agent/simple_chat_command.h"
namespace yaze {
namespace cli {
namespace handlers {

View File

@@ -1,328 +0,0 @@
#include "cli/handlers/commands.h"
#include "cli/handlers/tools/resource_commands.h"
#include "cli/handlers/game/dungeon_commands.h"
#include "cli/handlers/game/overworld_commands.h"
#include "cli/handlers/game/message_commands.h"
#include "cli/handlers/game/dialogue_commands.h"
#include "cli/handlers/game/music_commands.h"
#include "cli/handlers/graphics/hex_commands.h"
#include "cli/handlers/graphics/palette_commands.h"
// #include "cli/handlers/graphics/sprite_commands.h" // Implementations not available
#include "cli/handlers/tools/gui_commands.h"
#include "cli/handlers/tools/emulator_commands.h"
// #include "cli/handlers/rom/rom_commands.h" // Not used in stubs
// #include "cli/handlers/rom/project_commands.h" // Not used in stubs
namespace yaze {
namespace cli {
namespace handlers {
// Resource commands
absl::Status HandleResourceListCommand(const std::vector<std::string>& args, Rom* rom) {
ResourceListCommandHandler handler;
return handler.Run(args, rom);
}
absl::Status HandleResourceSearchCommand(const std::vector<std::string>& args, Rom* rom) {
ResourceSearchCommandHandler handler;
return handler.Run(args, rom);
}
// Dungeon commands
absl::Status HandleDungeonListSpritesCommand(const std::vector<std::string>& args, Rom* rom) {
DungeonListSpritesCommandHandler handler;
return handler.Run(args, rom);
}
absl::Status HandleDungeonDescribeRoomCommand(const std::vector<std::string>& args, Rom* rom) {
DungeonDescribeRoomCommandHandler handler;
return handler.Run(args, rom);
}
absl::Status HandleDungeonExportRoomCommand(const std::vector<std::string>& args, Rom* rom) {
DungeonExportRoomCommandHandler handler;
return handler.Run(args, rom);
}
absl::Status HandleDungeonListObjectsCommand(const std::vector<std::string>& args, Rom* rom) {
DungeonListObjectsCommandHandler handler;
return handler.Run(args, rom);
}
absl::Status HandleDungeonGetRoomTilesCommand(const std::vector<std::string>& args, Rom* rom) {
DungeonGetRoomTilesCommandHandler handler;
return handler.Run(args, rom);
}
absl::Status HandleDungeonSetRoomPropertyCommand(const std::vector<std::string>& args, Rom* rom) {
DungeonSetRoomPropertyCommandHandler handler;
return handler.Run(args, rom);
}
// Overworld commands
absl::Status HandleOverworldFindTileCommand(const std::vector<std::string>& args, Rom* rom) {
OverworldFindTileCommandHandler handler;
return handler.Run(args, rom);
}
absl::Status HandleOverworldDescribeMapCommand(const std::vector<std::string>& args, Rom* rom) {
OverworldDescribeMapCommandHandler handler;
return handler.Run(args, rom);
}
absl::Status HandleOverworldListWarpsCommand(const std::vector<std::string>& args, Rom* rom) {
OverworldListWarpsCommandHandler handler;
return handler.Run(args, rom);
}
absl::Status HandleOverworldListSpritesCommand(const std::vector<std::string>& args, Rom* rom) {
OverworldListSpritesCommandHandler handler;
return handler.Run(args, rom);
}
absl::Status HandleOverworldGetEntranceCommand(const std::vector<std::string>& args, Rom* rom) {
OverworldGetEntranceCommandHandler handler;
return handler.Run(args, rom);
}
absl::Status HandleOverworldTileStatsCommand(const std::vector<std::string>& args, Rom* rom) {
OverworldTileStatsCommandHandler handler;
return handler.Run(args, rom);
}
// Message commands
absl::Status HandleMessageListCommand(const std::vector<std::string>& args, Rom* rom) {
MessageListCommandHandler handler;
return handler.Run(args, rom);
}
absl::Status HandleMessageReadCommand(const std::vector<std::string>& args, Rom* rom) {
MessageReadCommandHandler handler;
return handler.Run(args, rom);
}
absl::Status HandleMessageSearchCommand(const std::vector<std::string>& args, Rom* rom) {
MessageSearchCommandHandler handler;
return handler.Run(args, rom);
}
// Dialogue commands
absl::Status HandleDialogueListCommand(const std::vector<std::string>& args, Rom* rom) {
DialogueListCommandHandler handler;
return handler.Run(args, rom);
}
absl::Status HandleDialogueReadCommand(const std::vector<std::string>& args, Rom* rom) {
DialogueReadCommandHandler handler;
return handler.Run(args, rom);
}
absl::Status HandleDialogueSearchCommand(const std::vector<std::string>& args, Rom* rom) {
DialogueSearchCommandHandler handler;
return handler.Run(args, rom);
}
// Music commands
absl::Status HandleMusicListCommand(const std::vector<std::string>& args, Rom* rom) {
MusicListCommandHandler handler;
return handler.Run(args, rom);
}
absl::Status HandleMusicInfoCommand(const std::vector<std::string>& args, Rom* rom) {
MusicInfoCommandHandler handler;
return handler.Run(args, rom);
}
absl::Status HandleMusicTracksCommand(const std::vector<std::string>& args, Rom* rom) {
MusicTracksCommandHandler handler;
return handler.Run(args, rom);
}
// Sprite commands (stubs - implementations not available)
absl::Status HandleSpriteListCommand(const std::vector<std::string>& /*args*/, Rom* /*rom*/) {
return absl::OkStatus();
}
absl::Status HandleSpritePropertiesCommand(const std::vector<std::string>& /*args*/, Rom* /*rom*/) {
return absl::OkStatus();
}
absl::Status HandleSpritePaletteCommand(const std::vector<std::string>& /*args*/, Rom* /*rom*/) {
return absl::OkStatus();
}
// GUI commands
absl::Status HandleGuiPlaceTileCommand(const std::vector<std::string>& args, Rom* rom) {
GuiPlaceTileCommandHandler handler;
return handler.Run(args, rom);
}
absl::Status HandleGuiClickCommand(const std::vector<std::string>& args, Rom* rom) {
GuiClickCommandHandler handler;
return handler.Run(args, rom);
}
absl::Status HandleGuiDiscoverToolCommand(const std::vector<std::string>& args, Rom* rom) {
GuiDiscoverToolCommandHandler handler;
return handler.Run(args, rom);
}
absl::Status HandleGuiScreenshotCommand(const std::vector<std::string>& args, Rom* rom) {
GuiScreenshotCommandHandler handler;
return handler.Run(args, rom);
}
// Emulator commands
absl::Status HandleEmulatorStepCommand(const std::vector<std::string>& args, Rom* rom) {
EmulatorStepCommandHandler handler;
return handler.Run(args, rom);
}
absl::Status HandleEmulatorRunCommand(const std::vector<std::string>& args, Rom* rom) {
EmulatorRunCommandHandler handler;
return handler.Run(args, rom);
}
absl::Status HandleEmulatorPauseCommand(const std::vector<std::string>& args, Rom* rom) {
EmulatorPauseCommandHandler handler;
return handler.Run(args, rom);
}
absl::Status HandleEmulatorResetCommand(const std::vector<std::string>& args, Rom* rom) {
EmulatorResetCommandHandler handler;
return handler.Run(args, rom);
}
absl::Status HandleEmulatorGetStateCommand(const std::vector<std::string>& args, Rom* rom) {
EmulatorGetStateCommandHandler handler;
return handler.Run(args, rom);
}
absl::Status HandleEmulatorSetBreakpointCommand(const std::vector<std::string>& args, Rom* rom) {
EmulatorSetBreakpointCommandHandler handler;
return handler.Run(args, rom);
}
absl::Status HandleEmulatorClearBreakpointCommand(const std::vector<std::string>& args, Rom* rom) {
EmulatorClearBreakpointCommandHandler handler;
return handler.Run(args, rom);
}
absl::Status HandleEmulatorListBreakpointsCommand(const std::vector<std::string>& args, Rom* rom) {
EmulatorListBreakpointsCommandHandler handler;
return handler.Run(args, rom);
}
absl::Status HandleEmulatorReadMemoryCommand(const std::vector<std::string>& args, Rom* rom) {
EmulatorReadMemoryCommandHandler handler;
return handler.Run(args, rom);
}
absl::Status HandleEmulatorWriteMemoryCommand(const std::vector<std::string>& args, Rom* rom) {
EmulatorWriteMemoryCommandHandler handler;
return handler.Run(args, rom);
}
absl::Status HandleEmulatorGetRegistersCommand(const std::vector<std::string>& args, Rom* rom) {
EmulatorGetRegistersCommandHandler handler;
return handler.Run(args, rom);
}
absl::Status HandleEmulatorGetMetricsCommand(const std::vector<std::string>& args, Rom* rom) {
EmulatorGetMetricsCommandHandler handler;
return handler.Run(args, rom);
}
// Hex commands
absl::Status HandleHexRead(const std::vector<std::string>& args, Rom* rom) {
HexReadCommandHandler handler;
return handler.Run(args, rom);
}
absl::Status HandleHexWrite(const std::vector<std::string>& args, Rom* rom) {
HexWriteCommandHandler handler;
return handler.Run(args, rom);
}
absl::Status HandleHexSearch(const std::vector<std::string>& args, Rom* rom) {
HexSearchCommandHandler handler;
return handler.Run(args, rom);
}
// Palette commands
absl::Status HandlePaletteGetColors(const std::vector<std::string>& args, Rom* rom) {
PaletteGetColorsCommandHandler handler;
return handler.Run(args, rom);
}
absl::Status HandlePaletteSetColor(const std::vector<std::string>& args, Rom* rom) {
PaletteSetColorCommandHandler handler;
return handler.Run(args, rom);
}
absl::Status HandlePaletteAnalyze(const std::vector<std::string>& args, Rom* rom) {
PaletteAnalyzeCommandHandler handler;
return handler.Run(args, rom);
}
// Agent-specific commands (stubs for now)
absl::Status HandleRunCommand(const std::vector<std::string>& /*args*/, Rom& /*rom*/) {
return absl::OkStatus();
}
absl::Status HandlePlanCommand(const std::vector<std::string>& /*args*/) {
return absl::OkStatus();
}
absl::Status HandleDiffCommand(Rom& /*rom*/, const std::vector<std::string>& /*args*/) {
return absl::OkStatus();
}
absl::Status HandleAcceptCommand(const std::vector<std::string>& /*args*/, Rom& /*rom*/) {
return absl::OkStatus();
}
absl::Status HandleTestCommand(const std::vector<std::string>& /*args*/) {
return absl::OkStatus();
}
absl::Status HandleGuiCommand(const std::vector<std::string>& /*args*/) {
return absl::OkStatus();
}
absl::Status HandleLearnCommand(const std::vector<std::string>& /*args*/) {
return absl::OkStatus();
}
absl::Status HandleListCommand() {
return absl::OkStatus();
}
absl::Status HandleCommitCommand(Rom& /*rom*/) {
return absl::OkStatus();
}
absl::Status HandleRevertCommand(Rom& /*rom*/) {
return absl::OkStatus();
}
absl::Status HandleDescribeCommand(const std::vector<std::string>& /*arg_vec*/) {
return absl::OkStatus();
}
absl::Status HandleChatCommand(Rom& /*rom*/) {
return absl::OkStatus();
}
absl::Status HandleSimpleChatCommand(const std::vector<std::string>&, Rom* /*rom*/, bool /*quiet*/) {
return absl::OkStatus();
}
absl::Status HandleTestConversationCommand(const std::vector<std::string>& /*arg_vec*/) {
return absl::OkStatus();
}
} // namespace handlers
} // namespace cli
} // namespace yaze

View File

@@ -1,194 +0,0 @@
#ifndef YAZE_CLI_HANDLERS_COMMANDS_H_
#define YAZE_CLI_HANDLERS_COMMANDS_H_
#include <string>
#include <vector>
#include "absl/status/status.h"
namespace yaze {
class Rom;
namespace cli {
namespace handlers {
absl::Status HandleRunCommand(const std::vector<std::string>& args,
Rom& rom);
absl::Status HandlePlanCommand(const std::vector<std::string>& args);
absl::Status HandleDiffCommand(Rom& rom,
const std::vector<std::string>& args);
absl::Status HandleAcceptCommand(const std::vector<std::string>& args, Rom& rom);
absl::Status HandleTestCommand(const std::vector<std::string>& args);
absl::Status HandleGuiCommand(const std::vector<std::string>& args);
absl::Status HandleLearnCommand(const std::vector<std::string>& args = {});
absl::Status HandleListCommand();
absl::Status HandleCommitCommand(Rom& rom);
absl::Status HandleRevertCommand(Rom& rom);
absl::Status HandleDescribeCommand(const std::vector<std::string>& arg_vec);
absl::Status HandleResourceListCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleResourceSearchCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleDungeonListSpritesCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleDungeonDescribeRoomCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleOverworldFindTileCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleOverworldDescribeMapCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleOverworldListWarpsCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleOverworldListSpritesCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleOverworldGetEntranceCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleOverworldTileStatsCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleMessageListCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleMessageReadCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleMessageSearchCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
// GUI Automation Tools
absl::Status HandleGuiPlaceTileCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleGuiClickCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleGuiDiscoverToolCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleGuiScreenshotCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
// Dialogue Inspection Tools
absl::Status HandleDialogueListCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleDialogueReadCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleDialogueSearchCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
// Music Data Tools
absl::Status HandleMusicListCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleMusicInfoCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleMusicTracksCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
// Sprite Property Tools
absl::Status HandleSpriteListCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleSpritePropertiesCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleSpritePaletteCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleChatCommand(Rom& rom);
absl::Status HandleSimpleChatCommand(const std::vector<std::string>&, Rom* rom, bool quiet);
absl::Status HandleTestConversationCommand(
const std::vector<std::string>& arg_vec);
// Agent command handler
absl::Status HandleAgentCommand(const std::vector<std::string>& arg_vec);
// Hex manipulation commands
absl::Status HandleHexRead(const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleHexWrite(const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleHexSearch(const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
// Palette manipulation commands
absl::Status HandlePaletteGetColors(const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandlePaletteSetColor(const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandlePaletteAnalyze(const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
// Dungeon editing commands
absl::Status HandleDungeonExportRoomCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleDungeonListObjectsCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleDungeonGetRoomTilesCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleDungeonSetRoomPropertyCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
// Emulator & Debugger commands
absl::Status HandleEmulatorStepCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleEmulatorRunCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleEmulatorPauseCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleEmulatorResetCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleEmulatorGetStateCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleEmulatorSetBreakpointCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleEmulatorClearBreakpointCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleEmulatorListBreakpointsCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleEmulatorReadMemoryCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleEmulatorWriteMemoryCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleEmulatorGetRegistersCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
absl::Status HandleEmulatorGetMetricsCommand(
const std::vector<std::string>& arg_vec,
Rom* rom_context = nullptr);
} // namespace handlers
} // namespace cli
} // namespace yaze
#endif // YAZE_CLI_HANDLERS_COMMANDS_H_

View File

@@ -183,6 +183,7 @@ ConversationalAgentService::ConversationalAgentService(const AgentConfig& config
void ConversationalAgentService::SetRomContext(Rom* rom) {
rom_context_ = rom;
tool_dispatcher_.SetRomContext(rom_context_);
if (ai_service_) {
ai_service_->SetRomContext(rom_context_);

View File

@@ -6,11 +6,12 @@
#include <string>
#include <vector>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/time/time.h"
#include "cli/service/ai/ai_service.h"
#include "cli/service/agent/tool_dispatcher.h"
#include "cli/service/agent/proposal_executor.h"
#include "cli/service/agent/tool_dispatcher.h"
namespace yaze {

View File

@@ -1,139 +1,282 @@
#include "cli/service/agent/tool_dispatcher.h"
#include <iostream>
#include <algorithm>
#include <memory>
#include <regex>
#include <sstream>
#include <string>
#include "absl/strings/match.h"
#include "absl/strings/str_format.h"
#include "cli/handlers/commands.h"
#include "absl/strings/str_cat.h"
#include "cli/handlers/game/dialogue_commands.h"
#include "cli/handlers/game/dungeon_commands.h"
#include "cli/handlers/game/message_commands.h"
#include "cli/handlers/game/music_commands.h"
#include "cli/handlers/game/overworld_commands.h"
#include "cli/handlers/graphics/sprite_commands.h"
#include "cli/handlers/tools/emulator_commands.h"
#include "cli/handlers/tools/gui_commands.h"
#include "cli/handlers/tools/resource_commands.h"
#include "cli/service/resources/command_context.h"
#include "cli/util/terminal_colors.h"
namespace yaze {
namespace cli {
namespace agent {
absl::StatusOr<std::string> ToolDispatcher::Dispatch(
const ToolCall& tool_call) {
namespace {
// Map tool name to handler type
ToolCallType GetToolCallType(const std::string& tool_name) {
// Resource commands
if (tool_name == "resource-list") return ToolCallType::kResourceList;
if (tool_name == "resource-search") return ToolCallType::kResourceSearch;
// Dungeon commands
if (tool_name == "dungeon-list-sprites") return ToolCallType::kDungeonListSprites;
if (tool_name == "dungeon-describe-room") return ToolCallType::kDungeonDescribeRoom;
if (tool_name == "dungeon-export-room") return ToolCallType::kDungeonExportRoom;
if (tool_name == "dungeon-list-objects") return ToolCallType::kDungeonListObjects;
if (tool_name == "dungeon-get-room-tiles") return ToolCallType::kDungeonGetRoomTiles;
if (tool_name == "dungeon-set-room-property") return ToolCallType::kDungeonSetRoomProperty;
// Overworld commands
if (tool_name == "overworld-find-tile") return ToolCallType::kOverworldFindTile;
if (tool_name == "overworld-describe-map") return ToolCallType::kOverworldDescribeMap;
if (tool_name == "overworld-list-warps") return ToolCallType::kOverworldListWarps;
if (tool_name == "overworld-list-sprites") return ToolCallType::kOverworldListSprites;
if (tool_name == "overworld-get-entrance") return ToolCallType::kOverworldGetEntrance;
if (tool_name == "overworld-tile-stats") return ToolCallType::kOverworldTileStats;
// Message & Dialogue commands
if (tool_name == "message-list") return ToolCallType::kMessageList;
if (tool_name == "message-read") return ToolCallType::kMessageRead;
if (tool_name == "message-search") return ToolCallType::kMessageSearch;
if (tool_name == "dialogue-list") return ToolCallType::kDialogueList;
if (tool_name == "dialogue-read") return ToolCallType::kDialogueRead;
if (tool_name == "dialogue-search") return ToolCallType::kDialogueSearch;
// GUI Automation commands
if (tool_name == "gui-place-tile") return ToolCallType::kGuiPlaceTile;
if (tool_name == "gui-click") return ToolCallType::kGuiClick;
if (tool_name == "gui-discover-tool") return ToolCallType::kGuiDiscover;
if (tool_name == "gui-screenshot") return ToolCallType::kGuiScreenshot;
// Music commands
if (tool_name == "music-list") return ToolCallType::kMusicList;
if (tool_name == "music-info") return ToolCallType::kMusicInfo;
if (tool_name == "music-tracks") return ToolCallType::kMusicTracks;
// Sprite commands
if (tool_name == "sprite-list") return ToolCallType::kSpriteList;
if (tool_name == "sprite-properties") return ToolCallType::kSpriteProperties;
if (tool_name == "sprite-palette") return ToolCallType::kSpritePalette;
// Emulator & Debugger commands
if (tool_name == "emulator-step") return ToolCallType::kEmulatorStep;
if (tool_name == "emulator-run") return ToolCallType::kEmulatorRun;
if (tool_name == "emulator-pause") return ToolCallType::kEmulatorPause;
if (tool_name == "emulator-reset") return ToolCallType::kEmulatorReset;
if (tool_name == "emulator-get-state") return ToolCallType::kEmulatorGetState;
if (tool_name == "emulator-set-breakpoint") return ToolCallType::kEmulatorSetBreakpoint;
if (tool_name == "emulator-clear-breakpoint") return ToolCallType::kEmulatorClearBreakpoint;
if (tool_name == "emulator-list-breakpoints") return ToolCallType::kEmulatorListBreakpoints;
if (tool_name == "emulator-read-memory") return ToolCallType::kEmulatorReadMemory;
if (tool_name == "emulator-write-memory") return ToolCallType::kEmulatorWriteMemory;
if (tool_name == "emulator-get-registers") return ToolCallType::kEmulatorGetRegisters;
if (tool_name == "emulator-get-metrics") return ToolCallType::kEmulatorGetMetrics;
return ToolCallType::kUnknown;
}
// Create the appropriate command handler for a tool call type
std::unique_ptr<resources::CommandHandler> CreateHandler(ToolCallType type) {
using namespace yaze::cli::handlers;
std::vector<std::string> args;
switch (type) {
// Resource commands
case ToolCallType::kResourceList:
return std::make_unique<ResourceListCommandHandler>();
case ToolCallType::kResourceSearch:
return std::make_unique<ResourceSearchCommandHandler>();
// Dungeon commands
case ToolCallType::kDungeonListSprites:
return std::make_unique<DungeonListSpritesCommandHandler>();
case ToolCallType::kDungeonDescribeRoom:
return std::make_unique<DungeonDescribeRoomCommandHandler>();
case ToolCallType::kDungeonExportRoom:
return std::make_unique<DungeonExportRoomCommandHandler>();
case ToolCallType::kDungeonListObjects:
return std::make_unique<DungeonListObjectsCommandHandler>();
case ToolCallType::kDungeonGetRoomTiles:
return std::make_unique<DungeonGetRoomTilesCommandHandler>();
case ToolCallType::kDungeonSetRoomProperty:
return std::make_unique<DungeonSetRoomPropertyCommandHandler>();
// Overworld commands
case ToolCallType::kOverworldFindTile:
return std::make_unique<OverworldFindTileCommandHandler>();
case ToolCallType::kOverworldDescribeMap:
return std::make_unique<OverworldDescribeMapCommandHandler>();
case ToolCallType::kOverworldListWarps:
return std::make_unique<OverworldListWarpsCommandHandler>();
case ToolCallType::kOverworldListSprites:
return std::make_unique<OverworldListSpritesCommandHandler>();
case ToolCallType::kOverworldGetEntrance:
return std::make_unique<OverworldGetEntranceCommandHandler>();
case ToolCallType::kOverworldTileStats:
return std::make_unique<OverworldTileStatsCommandHandler>();
// Message & Dialogue commands
case ToolCallType::kMessageList:
return std::make_unique<MessageListCommandHandler>();
case ToolCallType::kMessageRead:
return std::make_unique<MessageReadCommandHandler>();
case ToolCallType::kMessageSearch:
return std::make_unique<MessageSearchCommandHandler>();
case ToolCallType::kDialogueList:
return std::make_unique<DialogueListCommandHandler>();
case ToolCallType::kDialogueRead:
return std::make_unique<DialogueReadCommandHandler>();
case ToolCallType::kDialogueSearch:
return std::make_unique<DialogueSearchCommandHandler>();
// GUI Automation commands
case ToolCallType::kGuiPlaceTile:
return std::make_unique<GuiPlaceTileCommandHandler>();
case ToolCallType::kGuiClick:
return std::make_unique<GuiClickCommandHandler>();
case ToolCallType::kGuiDiscover:
return std::make_unique<GuiDiscoverToolCommandHandler>();
case ToolCallType::kGuiScreenshot:
return std::make_unique<GuiScreenshotCommandHandler>();
// Music commands
case ToolCallType::kMusicList:
return std::make_unique<MusicListCommandHandler>();
case ToolCallType::kMusicInfo:
return std::make_unique<MusicInfoCommandHandler>();
case ToolCallType::kMusicTracks:
return std::make_unique<MusicTracksCommandHandler>();
// Sprite commands
case ToolCallType::kSpriteList:
return std::make_unique<SpriteListCommandHandler>();
case ToolCallType::kSpriteProperties:
return std::make_unique<SpritePropertiesCommandHandler>();
case ToolCallType::kSpritePalette:
return std::make_unique<SpritePaletteCommandHandler>();
// Emulator & Debugger commands
case ToolCallType::kEmulatorStep:
return std::make_unique<EmulatorStepCommandHandler>();
case ToolCallType::kEmulatorRun:
return std::make_unique<EmulatorRunCommandHandler>();
case ToolCallType::kEmulatorPause:
return std::make_unique<EmulatorPauseCommandHandler>();
case ToolCallType::kEmulatorReset:
return std::make_unique<EmulatorResetCommandHandler>();
case ToolCallType::kEmulatorGetState:
return std::make_unique<EmulatorGetStateCommandHandler>();
case ToolCallType::kEmulatorSetBreakpoint:
return std::make_unique<EmulatorSetBreakpointCommandHandler>();
case ToolCallType::kEmulatorClearBreakpoint:
return std::make_unique<EmulatorClearBreakpointCommandHandler>();
case ToolCallType::kEmulatorListBreakpoints:
return std::make_unique<EmulatorListBreakpointsCommandHandler>();
case ToolCallType::kEmulatorReadMemory:
return std::make_unique<EmulatorReadMemoryCommandHandler>();
case ToolCallType::kEmulatorWriteMemory:
return std::make_unique<EmulatorWriteMemoryCommandHandler>();
case ToolCallType::kEmulatorGetRegisters:
return std::make_unique<EmulatorGetRegistersCommandHandler>();
case ToolCallType::kEmulatorGetMetrics:
return std::make_unique<EmulatorGetMetricsCommandHandler>();
default:
return nullptr;
}
}
// Convert tool call arguments map to command-line style vector
std::vector<std::string> ConvertArgsToVector(
const std::map<std::string, std::string>& args) {
std::vector<std::string> result;
for (const auto& [key, value] : args) {
// Convert to --key=value format
result.push_back(absl::StrCat("--", key, "=", value));
}
// Always request JSON format for tool calls (easier for AI to parse)
bool has_format = false;
for (const auto& [key, value] : tool_call.args) {
args.push_back(absl::StrFormat("--%s", key));
args.push_back(value);
if (absl::EqualsIgnoreCase(key, "format")) {
for (const auto& arg : result) {
if (arg.find("--format=") == 0) {
has_format = true;
break;
}
}
if (!has_format) {
args.push_back("--format");
args.push_back("json");
result.push_back("--format=json");
}
return result;
}
// Capture stdout
std::stringstream buffer;
auto old_cout_buf = std::cout.rdbuf();
std::cout.rdbuf(buffer.rdbuf());
} // namespace
absl::Status status;
if (tool_call.tool_name == "resource-list") {
status = HandleResourceListCommand(args, rom_context_);
} else if (tool_call.tool_name == "resource-search") {
status = HandleResourceSearchCommand(args, rom_context_);
} else if (tool_call.tool_name == "dungeon-list-sprites") {
status = HandleDungeonListSpritesCommand(args, rom_context_);
} else if (tool_call.tool_name == "dungeon-describe-room") {
status = HandleDungeonDescribeRoomCommand(args, rom_context_);
} else if (tool_call.tool_name == "overworld-find-tile") {
status = HandleOverworldFindTileCommand(args, rom_context_);
} else if (tool_call.tool_name == "overworld-describe-map") {
status = HandleOverworldDescribeMapCommand(args, rom_context_);
} else if (tool_call.tool_name == "overworld-list-warps") {
status = HandleOverworldListWarpsCommand(args, rom_context_);
} else if (tool_call.tool_name == "overworld-list-sprites") {
status = HandleOverworldListSpritesCommand(args, rom_context_);
} else if (tool_call.tool_name == "overworld-get-entrance") {
status = HandleOverworldGetEntranceCommand(args, rom_context_);
} else if (tool_call.tool_name == "overworld-tile-stats") {
status = HandleOverworldTileStatsCommand(args, rom_context_);
} else if (tool_call.tool_name == "message-list") {
status = HandleMessageListCommand(args, rom_context_);
} else if (tool_call.tool_name == "message-read") {
status = HandleMessageReadCommand(args, rom_context_);
} else if (tool_call.tool_name == "message-search") {
status = HandleMessageSearchCommand(args, rom_context_);
} else if (tool_call.tool_name == "gui-place-tile") {
// GUI automation tool for placing tiles via test harness
status = HandleGuiPlaceTileCommand(args, rom_context_);
} else if (tool_call.tool_name == "gui-click") {
status = HandleGuiClickCommand(args, rom_context_);
} else if (tool_call.tool_name == "gui-discover") {
status = HandleGuiDiscoverToolCommand(args, rom_context_);
} else if (tool_call.tool_name == "gui-screenshot") {
status = HandleGuiScreenshotCommand(args, rom_context_);
} else if (tool_call.tool_name == "dialogue-list") {
status = HandleDialogueListCommand(args, rom_context_);
} else if (tool_call.tool_name == "dialogue-read") {
status = HandleDialogueReadCommand(args, rom_context_);
} else if (tool_call.tool_name == "dialogue-search") {
status = HandleDialogueSearchCommand(args, rom_context_);
} else if (tool_call.tool_name == "music-list") {
status = HandleMusicListCommand(args, rom_context_);
} else if (tool_call.tool_name == "music-info") {
status = HandleMusicInfoCommand(args, rom_context_);
} else if (tool_call.tool_name == "music-tracks") {
status = HandleMusicTracksCommand(args, rom_context_);
} else if (tool_call.tool_name == "sprite-list") {
status = HandleSpriteListCommand(args, rom_context_);
} else if (tool_call.tool_name == "sprite-properties") {
status = HandleSpritePropertiesCommand(args, rom_context_);
} else if (tool_call.tool_name == "sprite-palette") {
status = HandleSpritePaletteCommand(args, rom_context_);
} else if (tool_call.tool_name == "dungeon-export-room") {
status = HandleDungeonExportRoomCommand(args, rom_context_);
} else if (tool_call.tool_name == "dungeon-list-objects") {
status = HandleDungeonListObjectsCommand(args, rom_context_);
} else if (tool_call.tool_name == "dungeon-get-room-tiles") {
status = HandleDungeonGetRoomTilesCommand(args, rom_context_);
} else if (tool_call.tool_name == "dungeon-set-room-property") {
status = HandleDungeonSetRoomPropertyCommand(args, rom_context_);
} else if (tool_call.tool_name == "emulator-step") {
status = HandleEmulatorStepCommand(args, rom_context_);
} else if (tool_call.tool_name == "emulator-run") {
status = HandleEmulatorRunCommand(args, rom_context_);
} else if (tool_call.tool_name == "emulator-pause") {
status = HandleEmulatorPauseCommand(args, rom_context_);
} else if (tool_call.tool_name == "emulator-reset") {
status = HandleEmulatorResetCommand(args, rom_context_);
} else if (tool_call.tool_name == "emulator-get-state") {
status = HandleEmulatorGetStateCommand(args, rom_context_);
} else if (tool_call.tool_name == "emulator-set-breakpoint") {
status = HandleEmulatorSetBreakpointCommand(args, rom_context_);
} else if (tool_call.tool_name == "emulator-clear-breakpoint") {
status = HandleEmulatorClearBreakpointCommand(args, rom_context_);
} else if (tool_call.tool_name == "emulator-list-breakpoints") {
status = HandleEmulatorListBreakpointsCommand(args, rom_context_);
} else if (tool_call.tool_name == "emulator-read-memory") {
status = HandleEmulatorReadMemoryCommand(args, rom_context_);
} else if (tool_call.tool_name == "emulator-write-memory") {
status = HandleEmulatorWriteMemoryCommand(args, rom_context_);
} else if (tool_call.tool_name == "emulator-get-registers") {
status = HandleEmulatorGetRegistersCommand(args, rom_context_);
} else if (tool_call.tool_name == "emulator-get-metrics") {
status = HandleEmulatorGetMetricsCommand(args, rom_context_);
} else {
status = absl::UnimplementedError(
absl::StrFormat("Unknown tool: %s", tool_call.tool_name));
absl::StatusOr<std::string> ToolDispatcher::Dispatch(const ToolCall& call) {
// Determine tool call type
ToolCallType type = GetToolCallType(call.tool_name);
if (type == ToolCallType::kUnknown) {
return absl::InvalidArgumentError(
absl::StrCat("Unknown tool: ", call.tool_name));
}
// Create the appropriate command handler
auto handler = CreateHandler(type);
if (!handler) {
return absl::InternalError(
absl::StrCat("Failed to create handler for tool: ", call.tool_name));
}
// Convert arguments to command-line style
std::vector<std::string> args = ConvertArgsToVector(call.args);
// Check if ROM context is required but not available
if (!rom_context_) {
return absl::FailedPreconditionError(
absl::StrCat("Tool '", call.tool_name,
"' requires ROM context but none is available"));
}
// Execute the command handler
// The handler will internally use OutputFormatter to capture output
// We need to capture stdout to get the formatted output
// Redirect stdout to capture the output
std::stringstream output_buffer;
std::streambuf* old_cout = std::cout.rdbuf(output_buffer.rdbuf());
// Execute the handler
absl::Status status = handler->Run(args, rom_context_);
// Restore stdout
std::cout.rdbuf(old_cout_buf);
std::cout.rdbuf(old_cout);
if (!status.ok()) {
return status;
}
return buffer.str();
// Return the captured output
std::string output = output_buffer.str();
if (output.empty()) {
return absl::InternalError(
absl::StrCat("Tool '", call.tool_name, "' produced no output"));
}
return output;
}
} // namespace agent

View File

@@ -1,5 +1,23 @@
#include "cli/service/resources/command_handler.h"
namespace yaze {
namespace cli {
namespace resources {
CommandHandler::Descriptor CommandHandler::Describe() const {
Descriptor descriptor;
descriptor.display_name = GetUsage();
descriptor.summary = "Command summary not provided.";
descriptor.todo_reference = "todo#unassigned";
return descriptor;
}
} // namespace resources
} // namespace cli
} // namespace yaze
#include "cli/service/resources/command_handler.h"
#include <iostream>
#include "absl/strings/str_format.h"

View File

@@ -44,6 +44,19 @@ class CommandHandler {
public:
virtual ~CommandHandler() = default;
struct DescriptorEntry {
std::string name;
std::string description;
std::string todo_reference;
};
struct Descriptor {
std::string display_name;
std::string summary;
std::string todo_reference;
std::vector<DescriptorEntry> entries;
};
/**
* @brief Execute the command
*
@@ -56,6 +69,19 @@ class CommandHandler {
*/
absl::Status Run(const std::vector<std::string>& args, Rom* rom_context);
/**
* @brief Get the command name
*
* Override this to provide a unique identifier for the command.
* This is used for command registration and lookup.
*/
virtual std::string GetName() const = 0;
/**
* @brief Provide metadata for TUI/help summaries.
*/
virtual Descriptor Describe() const;
protected:
/**
* @brief Validate command arguments

View File

@@ -1,9 +1,13 @@
#include "cli/tui/chat_tui.h"
#include <algorithm>
#include <cmath>
#include <utility>
#include <vector>
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/time/time.h"
#include "ftxui/component/component.hpp"
#include "ftxui/component/component_base.hpp"
#include "ftxui/component/event.hpp"
@@ -12,6 +16,7 @@
#include "ftxui/dom/table.hpp"
#include "app/rom.h"
#include "cli/tui/autocomplete_ui.h"
#include "cli/tui/tui.h"
namespace yaze {
namespace cli {
@@ -19,6 +24,44 @@ namespace tui {
using namespace ftxui;
namespace {
const std::vector<std::string> kSpinnerFrames = {
"", "", "", "", "", "", "", "", "", ""};
Element RenderPanelCard(const std::string& title, const std::vector<Element>& body,
Color border_color, bool highlight = false) {
auto panel = window(
text(title) | bold,
vbox(body));
if (highlight) {
panel = panel | color(border_color) | bgcolor(Color::GrayDark);
} else {
panel = panel | color(border_color);
}
return panel;
}
Element RenderLatencySparkline(const std::vector<double>& data) {
if (data.empty()) {
return text("No latency data yet") | dim;
}
Elements bars;
for (double d : data) {
bars.push_back(gauge(d) | flex);
}
return hbox(bars);
}
Element RenderMetricLabel(const std::string& icon, const std::string& label,
const std::string& value, Color color) {
return hbox({
text(icon) | ftxui::color(color),
text(" " + label + ": ") | bold,
text(value) | ftxui::color(color)
});
}
} // namespace
ChatTUI::ChatTUI(Rom* rom_context) : rom_context_(rom_context) {
if (rom_context_ != nullptr) {
agent_service_.SetRomContext(rom_context_);
@@ -26,7 +69,19 @@ ChatTUI::ChatTUI(Rom* rom_context) : rom_context_(rom_context) {
} else {
rom_header_ = "No ROM loaded.";
}
auto status = todo_manager_.Initialize();
todo_manager_ready_ = status.ok();
InitializeAutocomplete();
quick_actions_ = {
"List dungeon entrances", "Show sprite palette summary",
"Summarize overworld map", "Find unused rooms",
"Explain ROM header", "Search dialogue for 'Master Sword'",
"Suggest QA checklist", "Show TODO status",
};
}
ChatTUI::~ChatTUI() {
CleanupWorkers();
}
void ChatTUI::SetRomContext(Rom* rom_context) {
@@ -60,15 +115,30 @@ void ChatTUI::Run() {
// Create autocomplete input component
auto input_component = CreateAutocompleteInput(input_message.get(), &autocomplete_engine_);
auto todo_popup_toggle = [this] {
ToggleTodoPopup();
};
auto shortcut_palette_toggle = [this] {
ToggleShortcutPalette();
};
// Handle Enter key BEFORE adding to container
input_component = CatchEvent(input_component, [this, input_message](const Event& event) {
input_component = CatchEvent(input_component, [this, input_message, todo_popup_toggle, shortcut_palette_toggle](const Event& event) {
if (event == Event::Return) {
if (input_message->empty()) return true;
OnSubmit(*input_message);
input_message->clear();
return true;
}
if (event == Event::Special({20})) { // Ctrl+T
todo_popup_toggle();
return true;
}
if (event == Event::Special({11})) { // Ctrl+K
shortcut_palette_toggle();
return true;
}
return false;
});
@@ -78,6 +148,12 @@ void ChatTUI::Run() {
input_message->clear();
});
auto quick_pick_index = std::make_shared<int>(0);
auto quick_pick_menu = Menu(&quick_actions_, quick_pick_index.get());
todo_popup_component_ = CreateTodoPopup();
shortcut_palette_component_ = BuildShortcutPalette();
// Add both input and button to container
auto input_container = Container::Horizontal({
input_component,
@@ -86,7 +162,7 @@ void ChatTUI::Run() {
input_component->TakeFocus();
auto main_renderer = Renderer(input_container, [this, input_component, send_button] {
auto main_renderer = Renderer(input_container, [this, input_component, send_button, quick_pick_menu, quick_pick_index] {
const auto& history = agent_service_.GetHistory();
// Build history view from current history state
@@ -147,24 +223,78 @@ void ChatTUI::Run() {
history_view = vbox(history_elements) | vscroll_indicator | frame | flex;
}
Elements layout_elements = {
text(rom_header_) | bold | center,
separator(),
history_view,
separator(),
};
// Build info panel with responsive layout
auto metrics = CurrentMetrics();
Element header_line = hbox({
text(rom_header_) | bold,
filler(),
agent_busy_.load() ? text(kSpinnerFrames[spinner_index_.load() % kSpinnerFrames.size()]) | color(Color::Yellow)
: text("") | color(Color::GreenLight)
});
std::vector<Element> info_cards;
info_cards.push_back(RenderPanelCard(
"Session",
{
RenderMetricLabel("🕒", "Turns", absl::StrFormat("%d", metrics.turn_index), Color::Cyan),
RenderMetricLabel("🙋", "User", absl::StrFormat("%d", metrics.total_user_messages), Color::White),
RenderMetricLabel("🤖", "Agent", absl::StrFormat("%d", metrics.total_agent_messages), Color::GreenLight),
RenderMetricLabel("🔧", "Tools", absl::StrFormat("%d", metrics.total_tool_calls), Color::YellowLight),
}, Color::GrayLight));
info_cards.push_back(RenderPanelCard(
"Latency",
{
RenderMetricLabel("", "Last", absl::StrFormat("%.2fs", last_response_seconds_), Color::Yellow),
RenderMetricLabel("📈", "Average", absl::StrFormat("%.2fs", metrics.average_latency_seconds), Color::MagentaLight),
RenderLatencySparkline(latency_history_)
}, Color::Magenta, agent_busy_.load()));
info_cards.push_back(RenderPanelCard(
"Shortcuts",
{
text("⌨ Enter ↵ Send") | dim,
text("⌨ Shift+Enter ↩ Multiline") | dim,
text("⌨ /help, /rom_info, /status") | dim,
text("⌨ Ctrl+T TODO overlay · Ctrl+K shortcuts · f fullscreen") | dim,
}, Color::BlueLight));
Elements layout_elements;
layout_elements.push_back(header_line);
layout_elements.push_back(separatorLight());
layout_elements.push_back(
vbox({
hbox({
info_cards[0] | flex,
separator(),
info_cards[1] | flex,
separator(),
info_cards[2] | flex,
}) | flex,
separator(),
history_view |
bgcolor(Color::Black) |
flex
}) | flex);
// Add metrics bar
const auto metrics = agent_service_.GetMetrics();
layout_elements.push_back(separatorLight());
layout_elements.push_back(
hbox({
text(absl::StrFormat("Turns:%d", metrics.turn_index)),
text("Turns: ") | bold,
text(absl::StrFormat("%d", metrics.turn_index)),
separator(),
text(absl::StrFormat("User:%d", metrics.total_user_messages)),
text("User: ") | bold,
text(absl::StrFormat("%d", metrics.total_user_messages)),
separator(),
text(absl::StrFormat("Agent:%d", metrics.total_agent_messages)),
text("Agent: ") | bold,
text(absl::StrFormat("%d", metrics.total_agent_messages)),
separator(),
text(absl::StrFormat("Tools:%d", metrics.total_tool_calls)),
text("Tools: ") | bold,
text(absl::StrFormat("%d", metrics.total_tool_calls)),
filler(),
text("Last response " + absl::StrFormat("%.2fs", last_response_seconds_)) | color(Color::GrayLight)
}) | color(Color::GrayLight)
);
@@ -179,17 +309,38 @@ void ChatTUI::Run() {
// Add input area
layout_elements.push_back(separator());
layout_elements.push_back(
input_component->Render() | flex
vbox({
text("Quick Actions") | bold,
quick_pick_menu->Render() | frame | size(HEIGHT, EQUAL, 5) |
flex,
separatorLight(),
input_component->Render() | flex
})
);
layout_elements.push_back(
hbox({
text("Press Enter to send | ") | dim,
send_button->Render(),
text(" | /help for commands") | dim,
text(" | Tab quick actions · Ctrl+T TODO overlay · Ctrl+K shortcuts") | dim,
}) | center
);
return vbox(layout_elements) | border;
Element base = vbox(layout_elements) | borderRounded | bgcolor(Color::Black);
if ((todo_popup_visible_ && todo_popup_component_) ||
(shortcut_palette_visible_ && shortcut_palette_component_)) {
std::vector<Element> overlays;
overlays.push_back(base);
if (todo_popup_visible_ && todo_popup_component_) {
overlays.push_back(todo_popup_component_->Render());
}
if (shortcut_palette_visible_ && shortcut_palette_component_) {
overlays.push_back(shortcut_palette_component_->Render());
}
base = dbox(overlays);
}
return base;
});
screen_.Loop(main_renderer);
@@ -259,12 +410,181 @@ void ChatTUI::OnSubmit(const std::string& message) {
return;
}
auto response = agent_service_.SendMessage(message);
if (!response.ok()) {
last_error_ = response.status().message();
} else {
last_error_.reset();
LaunchAgentPrompt(message);
}
void ChatTUI::LaunchAgentPrompt(const std::string& prompt) {
if (prompt.empty()) {
return;
}
agent_busy_.store(true);
spinner_running_.store(true);
if (!spinner_thread_.joinable()) {
spinner_thread_ = std::thread([this] {
while (spinner_running_.load()) {
std::this_thread::sleep_for(std::chrono::milliseconds(90));
spinner_index_.fetch_add(1);
screen_.PostEvent(Event::Custom);
}
});
}
last_send_time_ = std::chrono::steady_clock::now();
auto future = std::async(std::launch::async, [this, prompt] {
auto response = agent_service_.SendMessage(prompt);
if (!response.ok()) {
last_error_ = response.status().message();
} else {
last_error_.reset();
}
auto end_time = std::chrono::steady_clock::now();
last_response_seconds_ = std::chrono::duration<double>(end_time - last_send_time_).count();
latency_history_.push_back(last_response_seconds_);
if (latency_history_.size() > 30) {
latency_history_.erase(latency_history_.begin());
}
agent_busy_.store(false);
StopSpinner();
screen_.PostEvent(Event::Custom);
});
{
std::lock_guard<std::mutex> lock(worker_mutex_);
worker_futures_.push_back(std::move(future));
}
}
void ChatTUI::CleanupWorkers() {
std::lock_guard<std::mutex> lock(worker_mutex_);
for (auto& future : worker_futures_) {
if (future.valid()) {
future.wait();
}
}
worker_futures_.clear();
StopSpinner();
}
agent::ChatMessage::SessionMetrics ChatTUI::CurrentMetrics() const {
return agent_service_.GetMetrics();
}
void ChatTUI::StopSpinner() {
spinner_running_.store(false);
if (spinner_thread_.joinable()) {
spinner_thread_.join();
}
}
void ChatTUI::ToggleTodoPopup() {
if (!todo_popup_component_) {
todo_popup_component_ = CreateTodoPopup();
}
todo_popup_visible_ = !todo_popup_visible_;
if (todo_popup_visible_ && todo_popup_component_) {
screen_.PostEvent(Event::Custom);
}
}
void ChatTUI::ToggleShortcutPalette() {
if (!shortcut_palette_component_) {
shortcut_palette_component_ = BuildShortcutPalette();
}
shortcut_palette_visible_ = !shortcut_palette_visible_;
if (shortcut_palette_visible_) {
screen_.PostEvent(Event::Custom);
}
}
ftxui::Component ChatTUI::CreateTodoPopup() {
auto refresh_button = Button("Refresh", [this] {
screen_.PostEvent(Event::Custom);
});
auto close_button = Button("Close", [this] {
todo_popup_visible_ = false;
screen_.PostEvent(Event::Custom);
});
auto renderer = Renderer([this, refresh_button, close_button] {
Elements rows;
if (!todo_manager_ready_) {
rows.push_back(text("TODO manager unavailable") | color(Color::Red) | center);
} else {
auto todos = todo_manager_.GetAllTodos();
if (todos.empty()) {
rows.push_back(text("No TODOs tracked") | dim | center);
} else {
for (const auto& item : todos) {
rows.push_back(hbox({
text(absl::StrFormat("[%s]", item.StatusToString())) | color(Color::Yellow),
text(" " + item.description) | flex,
text(item.category.empty() ? "" : absl::StrCat(" (", item.category, ")")) | dim
}));
}
}
}
return dbox({
window(text("📝 TODO Overlay") | bold,
vbox({
separatorLight(),
vbox(rows) | frame | size(HEIGHT, LESS_THAN, 12) | size(WIDTH, LESS_THAN, 70),
separatorLight(),
hbox({refresh_button->Render(), text(" "), close_button->Render()}) | center
}))
| size(WIDTH, LESS_THAN, 72) | size(HEIGHT, LESS_THAN, 18) | center
});
});
return renderer;
}
ftxui::Component ChatTUI::BuildShortcutPalette() {
std::vector<std::pair<std::string, std::string>> shortcuts = {
{"Ctrl+T", "Toggle TODO overlay"},
{"Ctrl+K", "Shortcut palette"},
{"Ctrl+L", "Clear chat history"},
{"Ctrl+Shift+S", "Save transcript"},
{"Ctrl+G", "Focus quick actions"},
{"Ctrl+P", "Command palette"},
{"Ctrl+F", "Fullscreen chat"},
{"Esc", "Back to unified layout"},
};
auto close_button = Button("Close", [this] {
shortcut_palette_visible_ = false;
screen_.PostEvent(Event::Custom);
});
auto renderer = Renderer([shortcuts, close_button] {
Elements rows;
for (const auto& [combo, desc] : shortcuts) {
rows.push_back(hbox({text(combo) | bold | color(Color::Cyan), text(" " + desc)}));
}
return dbox({
window(text("⌨ Shortcuts") | bold,
vbox({
separatorLight(),
vbox(rows) | frame | size(HEIGHT, LESS_THAN, 12) | size(WIDTH, LESS_THAN, 60),
separatorLight(),
close_button->Render() | center
}))
| size(WIDTH, LESS_THAN, 64) | size(HEIGHT, LESS_THAN, 16) | center
});
});
return renderer;
}
bool ChatTUI::IsPopupOpen() const {
return todo_popup_visible_ || shortcut_palette_visible_;
}
} // namespace tui

View File

@@ -1,11 +1,18 @@
#ifndef YAZE_SRC_CLI_TUI_CHAT_TUI_H_
#define YAZE_SRC_CLI_TUI_CHAT_TUI_H_
#include <atomic>
#include <chrono>
#include <future>
#include <mutex>
#include <optional>
#include <thread>
#include <vector>
#include "ftxui/component/component.hpp"
#include "ftxui/component/screen_interactive.hpp"
#include "cli/service/agent/conversational_agent_service.h"
#include "cli/service/agent/todo_manager.h"
#include "cli/util/autocomplete.h"
@@ -19,19 +26,54 @@ namespace tui {
class ChatTUI {
public:
explicit ChatTUI(Rom* rom_context = nullptr);
~ChatTUI();
void Run();
void SetRomContext(Rom* rom_context);
private:
void OnSubmit(const std::string& message);
void LaunchAgentPrompt(const std::string& prompt);
void CleanupWorkers();
void StopSpinner();
void InitializeAutocomplete();
agent::ChatMessage::SessionMetrics CurrentMetrics() const;
// Popup state
void ToggleTodoPopup();
ftxui::Component CreateTodoPopup();
ftxui::Component BuildShortcutPalette();
bool IsPopupOpen() const;
void ToggleShortcutPalette();
ftxui::ScreenInteractive screen_ = ftxui::ScreenInteractive::Fullscreen();
agent::ConversationalAgentService agent_service_;
agent::TodoManager todo_manager_;
Rom* rom_context_ = nullptr;
std::optional<std::string> last_error_;
AutocompleteEngine autocomplete_engine_;
std::string rom_header_;
std::atomic<bool> agent_busy_{false};
std::atomic<bool> spinner_running_{false};
std::atomic<int> spinner_index_{0};
std::vector<std::future<void>> worker_futures_;
mutable std::mutex worker_mutex_;
std::chrono::steady_clock::time_point last_send_time_{};
double last_response_seconds_ = 0.0;
std::vector<double> latency_history_;
std::vector<std::string> quick_actions_;
std::thread spinner_thread_;
// Popup state
bool todo_popup_visible_ = false;
ftxui::Component todo_popup_component_;
ftxui::Component shortcut_palette_component_;
bool shortcut_palette_visible_ = false;
bool todo_manager_ready_ = false;
};
} // namespace tui

View File

@@ -4,9 +4,12 @@
#include <ftxui/component/screen_interactive.hpp>
#include <ftxui/dom/elements.hpp>
#include <ftxui/screen/terminal.hpp>
#include <numeric>
#include <utility>
#include "absl/strings/str_format.h"
#include "cli/service/agent/conversational_agent_service.h"
#include "cli/z3ed_ascii_logo.h"
namespace yaze {
namespace cli {
@@ -27,7 +30,45 @@ UnifiedLayout::UnifiedLayout(Rom* rom_context)
if (rom_context_) {
state_.current_rom_file = rom_context_->title();
}
state_.active_workflows = {
"ROM Audit",
"Dungeon QA",
"Palette Polish"
};
InitializeTheme();
status_provider_ = [this] {
auto rom_loaded = rom_context_ && rom_context_->is_loaded();
return vbox({
text(rom_loaded ? "✅ Ready" : "⚠ Awaiting ROM") |
color(rom_loaded ? Color::GreenLight : Color::YellowLight),
text(absl::StrFormat("Focus: %s",
state_.command_palette_hint.empty() ?
"Main Menu" : state_.command_palette_hint)) |
dim
});
};
command_summary_provider_ = [] {
return std::vector<std::string>{
"agent::chat — conversational ROM inspector",
"rom::info — metadata & validation",
"dungeon::list — dungeon manifest",
"gfx::export — sprite/palette dump",
"project::build — apply patches"
};
};
todo_provider_ = [] {
return std::vector<std::string>{
"[pending] Implement dungeon diff visualizer",
"[pending] Integrate Claude-style context panes",
"[todo] Hook TODO manager into project manifests"
};
};
// Create components
main_menu_panel_ = CreateMainMenuPanel();
chat_panel_ = CreateChatPanel();
@@ -94,10 +135,61 @@ void UnifiedLayout::ToggleStatus() {
screen_.PostEvent(Event::Custom); // Force screen refresh
}
void UnifiedLayout::ToggleTodoOverlay() {
todo_overlay_visible_ = !todo_overlay_visible_;
if (todo_overlay_visible_) {
if (!todo_overlay_component_ && todo_provider_) {
todo_overlay_component_ = Renderer([this] {
Elements rows;
if (todo_provider_) {
auto items = todo_provider_();
if (items.empty()) {
rows.push_back(text("No TODOs available") | dim | center);
} else {
for (const auto& line : items) {
rows.push_back(text(line));
}
}
}
return dbox({window(text("📝 TODO Overlay") | bold,
vbox({
separatorLight(),
vbox(rows) | frame | size(HEIGHT, LESS_THAN, 15) | size(WIDTH, LESS_THAN, 80),
separatorLight(),
text("Ctrl+T to close • Enter to jump via command palette") | dim | center
})) | center});
});
}
screen_.PostEvent(Event::Custom);
} else {
screen_.PostEvent(Event::Custom);
}
}
void UnifiedLayout::SetLayoutConfig(const LayoutConfig& config) {
config_ = config;
}
void UnifiedLayout::SetStatusProvider(std::function<Element()> provider) {
status_provider_ = std::move(provider);
}
void UnifiedLayout::SetCommandSummaryProvider(std::function<std::vector<std::string>()> provider) {
command_summary_provider_ = std::move(provider);
}
void UnifiedLayout::SetTodoProvider(std::function<std::vector<std::string>()> provider) {
todo_provider_ = std::move(provider);
}
void UnifiedLayout::InitializeTheme() {
auto terminal = Terminal::Size();
if (terminal.dimx < 120) {
config_.right_panel_width = 30;
config_.bottom_panel_height = 12;
}
}
Component UnifiedLayout::CreateMainMenuPanel() {
struct MenuState {
int selected = 0;
@@ -133,13 +225,18 @@ Component UnifiedLayout::CreateMainMenuPanel() {
auto menu = Menu(&state->items, &state->selected, option);
return Renderer(menu, [this, menu, state] {
auto banner = RenderAnimatedBanner();
return vbox({
text("🎮 Z3ED Main Menu") | bold | center,
banner,
separator(),
menu->Render(),
separator(),
RenderCommandHints(),
separator(),
RenderWorkflowLane(),
separator(),
text("↑/↓: Navigate | Enter: Select | q: Quit") | dim | center
});
}) | borderRounded | bgcolor(Color::Black);
});
}
@@ -147,26 +244,40 @@ Component UnifiedLayout::CreateChatPanel() {
// Use the full-featured ChatTUI if available
if (chat_tui_) {
return Renderer([this] {
std::vector<Element> cards;
cards.push_back(vbox({
text("🤖 Overview") | bold,
text("Claude-style assistant for ROM editing"),
text("Press 'f' for fullscreen chat") | dim
}) | borderRounded);
if (rom_context_) {
cards.push_back(vbox({
text("📦 ROM Context") | bold,
text(rom_context_->title()),
text(absl::StrFormat("Size: %d bytes", rom_context_->size())) | dim
}) | borderRounded | color(Color::GreenLight));
} else {
cards.push_back(vbox({
text("⚠ No ROM loaded") | color(Color::Yellow),
text("Use Load ROM from main menu") | dim
}) | borderRounded);
}
cards.push_back(vbox({
text("🛠 Integrations") | bold,
text("• TODO manager status") | dim,
text("• Command palette shortcuts") | dim,
text("• Tool dispatcher metrics") | dim
}) | borderRounded);
return vbox({
text("🤖 AI Chat Assistant") | bold | center | color(Color::Cyan),
RenderPanelHeader(PanelType::kChat),
separator(),
text("Press 'f' for full chat | 'c' to toggle") | center | dim,
RenderResponsiveGrid(cards),
separator(),
hbox({
text("Status: ") | bold,
text(rom_context_ ? "ROM Loaded ✓" : "No ROM") |
color(rom_context_ ? Color::Green : Color::Red)
}) | center,
separator(),
text("Features:") | bold | center,
text(" • Natural language ROM queries") | dim,
text(" • Dungeon & overworld inspection") | dim,
text(" • Sprite & palette analysis") | dim,
text(" • Message & dialogue search") | dim,
text(" • Emulator control (when running)") | dim,
separator(),
text("Type '/help' for commands") | center | dim
});
text("Shortcuts: f fullscreen | c toggle chat | /help commands") | dim | center
}) | borderRounded | bgcolor(Color::Black);
});
}
@@ -227,40 +338,32 @@ Component UnifiedLayout::CreateChatPanel() {
Component UnifiedLayout::CreateStatusPanel() {
return Renderer([this] {
std::string rom_info = rom_context_ ?
absl::StrFormat("ROM: %s | Size: %d bytes",
rom_context_->title(), rom_context_->size()) :
"No ROM loaded";
std::string panel_name;
switch (state_.active_main_panel) {
case PanelType::kMainMenu: panel_name = "Main Menu"; break;
case PanelType::kChat: panel_name = "Chat"; break;
case PanelType::kHexViewer: panel_name = "Hex Viewer"; break;
case PanelType::kPaletteEditor: panel_name = "Palette Editor"; break;
case PanelType::kTodoManager: panel_name = "TODO Manager"; break;
case PanelType::kRomTools: panel_name = "ROM Tools"; break;
case PanelType::kGraphicsTools: panel_name = "Graphics Tools"; break;
case PanelType::kSettings: panel_name = "Settings"; break;
case PanelType::kHelp: panel_name = "Help"; break;
default: panel_name = "Other"; break;
Element rom_info = rom_context_ ?
text(absl::StrFormat("ROM: %s", rom_context_->title())) | color(Color::GreenLight) :
text("ROM: none") | color(Color::RedLight);
Element provider_status = status_provider_ ? status_provider_() : text("Ready") | color(Color::GrayLight);
auto command_tiles = RenderCommandHints();
auto todo_tiles = RenderTodoStack();
std::vector<Element> sections = {
RenderAnimatedBanner(),
separatorLight(),
rom_info,
separatorLight(),
provider_status,
separatorLight(),
command_tiles,
separatorLight(),
todo_tiles
};
if (!state_.current_error.empty()) {
sections.push_back(separatorLight());
sections.push_back(text(state_.current_error) | color(Color::Red) | bold);
}
return vbox({
text("📊 Status") | bold | center,
separator(),
text(rom_info) | color(rom_context_ ? Color::Green : Color::Red),
separator(),
text("Panel: ") | bold,
text(panel_name),
separator(),
text("Layout: ") | bold,
text(absl::StrFormat("Chat: %s | Status: %s",
config_.show_chat ? "ON" : "OFF",
config_.show_status ? "ON" : "OFF")),
separator(),
text("Press 'h' for help, 'q' to quit") | dim | center
});
return vbox(sections) | borderRounded | bgcolor(Color::Black);
});
}
@@ -268,25 +371,35 @@ Component UnifiedLayout::CreateToolsPanel() {
struct ToolsState {
int selected = 0;
std::vector<std::string> items = {
"🔧 ROM Tools",
"🎨 Graphics Tools",
"📝 TODO Manager",
"🔧 ROM Tools (press t)",
"🎨 Graphics Tools (ref gfx::export)",
"📝 TODO Manager (ref todo::list)",
"⚙️ Settings",
"❓ Help"
};
};
auto state = std::make_shared<ToolsState>();
auto menu = Menu(&state->items, &state->selected);
MenuOption option;
option.on_change = [this, state] {
if (!state->items.empty()) {
state_.command_palette_hint = state->items[state->selected];
}
};
auto menu = Menu(&state->items, &state->selected, option);
return Renderer(menu, [this, menu, state] {
return vbox({
text("🛠️ Tools") | bold | center,
RenderPanelHeader(PanelType::kTools),
separator(),
menu->Render(),
separator(),
text("Select a tool category") | dim | center
}) | border;
text("Select a tool category") | dim | center,
separator(),
RenderCommandHints(),
separator(),
RenderWorkflowLane()
}) | borderRounded | bgcolor(Color::Black);
});
}
@@ -337,47 +450,77 @@ Component UnifiedLayout::CreateHexViewerPanel() {
rows.push_back(hbox(row));
}
auto workflow = RenderWorkflowLane();
return vbox({
text("🔍 Hex Viewer") | bold | center,
separator(),
workflow,
separator(),
vbox(rows) | frame | flex,
separator(),
text(absl::StrFormat("Offset: 0x%08X", *offset)) | color(Color::Cyan),
separator(),
text("↑/↓: Scroll | q: Back") | dim | center
}) | border;
}) | borderRounded | bgcolor(Color::Black);
});
}
Component UnifiedLayout::CreatePaletteEditorPanel() {
return Renderer([this] {
return vbox({
text("🎨 Palette Editor") | bold | center,
RenderPanelHeader(PanelType::kPaletteEditor),
separator(),
text("Palette editing functionality") | center,
text("coming soon...") | center | dim,
RenderResponsiveGrid({
vbox({
text("🌈 Overview") | bold,
text("Preview palette indices and colors"),
text("Highlight sprite-specific palettes") | dim
}) | borderRounded | bgcolor(Color::Black),
vbox({
text("🧪 Roadmap") | bold,
text("• Live recolor with undo stack"),
text("• Sprite preview viewport"),
text("• Export to .pal/.act")
}) | borderRounded | bgcolor(Color::Black),
vbox({
text("🗒 TODO") | bold,
text("Link to command palette"),
text("Use animation timeline"),
text("Add palette history panel") | dim
}) | borderRounded | bgcolor(Color::Black)
}),
separator(),
text("This panel will allow editing") | center,
text("color palettes from the ROM") | center,
RenderWorkflowLane(),
separator(),
text("Press 'q' to go back") | dim | center
}) | border;
}) | borderRounded;
});
}
Component UnifiedLayout::CreateTodoManagerPanel() {
return Renderer([this] {
std::vector<Element> todo_cards;
if (todo_provider_) {
for (const auto& item : todo_provider_()) {
todo_cards.push_back(text("" + item));
}
}
if (todo_cards.empty()) {
todo_cards.push_back(text("No TODOs yet") | dim);
}
return vbox({
text("📝 TODO Manager") | bold | center,
RenderPanelHeader(PanelType::kTodoManager),
separator(),
text("TODO management functionality") | center,
text("coming soon...") | center | dim,
vbox(todo_cards) | borderRounded | bgcolor(Color::Black),
separator(),
text("This panel will integrate with") | center,
text("the existing TODO manager") | center,
text("Press Ctrl+T anywhere to toggle the popup todo overlay.") | dim,
separator(),
RenderWorkflowLane(),
separator(),
text("Press 'q' to go back") | dim | center
}) | border;
}) | borderRounded;
});
}
@@ -385,11 +528,11 @@ Component UnifiedLayout::CreateRomToolsPanel() {
struct ToolsState {
int selected = 0;
std::vector<std::string> items = {
"Apply Asar Patch",
"Apply BPS Patch",
"Extract Symbols",
"Validate Assembly",
"Generate Save File",
"Apply Asar Patch — todo#123",
"Apply BPS Patch — todo#124",
"Extract Symbols — todo#098",
"Validate Assembly — todo#087",
"Generate Save File — todo#142",
"Back"
};
};
@@ -412,8 +555,8 @@ Component UnifiedLayout::CreateGraphicsToolsPanel() {
struct ToolsState {
int selected = 0;
std::vector<std::string> items = {
"Palette Editor",
"Hex Viewer",
"Palette Editor — ref gfx::export",
"Hex Viewer — ref rom::hex",
"Back"
};
};
@@ -478,7 +621,7 @@ Component UnifiedLayout::CreateSettingsPanel() {
return Renderer(controls, [this, controls, state, left_width_control, right_width_control,
bottom_height_control, apply_button, reset_button] {
return vbox({
text("⚙️ Layout Configuration") | bold | center | color(Color::Cyan),
RenderPanelHeader(PanelType::kSettings) | color(Color::Cyan),
separator(),
text("Customize the TUI layout") | center | dim,
separator(),
@@ -530,7 +673,7 @@ Component UnifiedLayout::CreateSettingsPanel() {
Component UnifiedLayout::CreateHelpPanel() {
return Renderer([this] {
return vbox({
text("❓ Z3ED Help") | bold | center | color(Color::Cyan),
RenderPanelHeader(PanelType::kHelp) | color(Color::Cyan),
separator(),
text("Unified TUI Layout - ROM Editor & AI Agent") | center | dim,
separator(),
@@ -620,25 +763,39 @@ Component UnifiedLayout::CreateUnifiedLayout() {
}
// Dynamically select right panel
Component right_panel = config_.show_status ? status_panel_ : tools_panel_;
Component right_panel;
if (config_.show_status) {
right_panel = status_panel_;
} else {
right_panel = tools_panel_;
}
// Create horizontal layout
auto top_section = hbox({
left_panel->Render() | flex,
separator(),
right_panel->Render() | size(WIDTH, EQUAL, config_.right_panel_width)
separatorLight(),
right_panel->Render() | size(WIDTH, LESS_THAN, config_.right_panel_width)
});
// Add chat panel if enabled
if (config_.show_chat) {
return vbox({
top_section | flex,
separator(),
chat_panel_->Render() | size(HEIGHT, EQUAL, config_.bottom_panel_height)
});
Element stacked = vbox({
top_section | flex,
separatorLight(),
chat_panel_->Render() | size(HEIGHT, EQUAL, config_.bottom_panel_height)
}) | bgcolor(Color::Black);
if (todo_overlay_visible_ && todo_overlay_component_) {
stacked = dbox({stacked, todo_overlay_component_->Render()});
}
return stacked;
}
return top_section;
Element content = top_section | bgcolor(Color::Black);
if (todo_overlay_visible_ && todo_overlay_component_) {
content = dbox({content, todo_overlay_component_->Render()});
}
return content;
});
}
@@ -653,6 +810,11 @@ bool UnifiedLayout::HandleGlobalEvents(const Event& event) {
}
// Global shortcuts
if (event == Event::Special({20})) { // Ctrl+T
ToggleTodoOverlay();
return true;
}
if (event == Event::Character('q') ||
(event == Event::Character('q') && state_.active_main_panel == PanelType::kMainMenu)) {
screen_.Exit();
@@ -751,9 +913,37 @@ Element UnifiedLayout::RenderStatusBar() {
text(absl::StrFormat("ROM: %s",
state_.current_rom_file.empty() ? "None" : state_.current_rom_file)) | color(Color::Green),
filler(),
text("q: Quit | h: Help | c: Chat | s: Status") | dim
text("Shortcuts: Ctrl+T TODO Overlay | f Full Chat | m Main Menu") | dim
});
}
Element UnifiedLayout::RenderAnimatedBanner() {
return text("🎮 Z3ED CLI") | bold | center;
}
Element UnifiedLayout::RenderWorkflowLane() {
return text("Workflow: Active") | color(Color::Green);
}
Element UnifiedLayout::RenderCommandHints() {
return vbox({
text("Command Hints:") | bold,
text(" Ctrl+T - Toggle TODO overlay"),
text(" f - Full chat mode"),
text(" m - Main menu")
});
}
Element UnifiedLayout::RenderTodoStack() {
return text("TODO Stack: Empty") | dim;
}
Element UnifiedLayout::RenderResponsiveGrid(const std::vector<Element>& tiles) {
if (tiles.empty()) {
return text("No items") | center | dim;
}
return vbox(tiles);
}
} // namespace cli
} // namespace yaze

View File

@@ -4,6 +4,7 @@
#include <ftxui/component/component.hpp>
#include <ftxui/dom/elements.hpp>
#include <ftxui/screen/screen.hpp>
#include <functional>
#include <memory>
#include <string>
#include <vector>
@@ -56,6 +57,10 @@ struct PanelState {
bool status_expanded = false;
std::string current_rom_file;
std::string current_error;
std::string command_palette_hint;
std::string todo_summary;
std::vector<std::string> active_workflows;
double last_tool_latency = 0.0;
};
class UnifiedLayout {
@@ -71,12 +76,17 @@ class UnifiedLayout {
void SwitchToolPanel(PanelType panel);
void ToggleChat();
void ToggleStatus();
void ToggleTodoOverlay();
// Configuration
void SetLayoutConfig(const LayoutConfig& config);
LayoutConfig GetLayoutConfig() const { return config_; }
void SetStatusProvider(std::function<ftxui::Element()> provider);
void SetCommandSummaryProvider(std::function<std::vector<std::string>()> provider);
void SetTodoProvider(std::function<std::vector<std::string>()> provider);
private:
void InitializeTheme();
// Component creation
ftxui::Component CreateMainMenuPanel();
ftxui::Component CreateChatPanel();
@@ -100,6 +110,11 @@ class UnifiedLayout {
// Rendering
ftxui::Element RenderPanelHeader(PanelType panel);
ftxui::Element RenderStatusBar();
ftxui::Element RenderAnimatedBanner();
ftxui::Element RenderWorkflowLane();
ftxui::Element RenderCommandHints();
ftxui::Element RenderTodoStack();
ftxui::Element RenderResponsiveGrid(const std::vector<ftxui::Element>& tiles);
// State
ftxui::ScreenInteractive screen_;
@@ -130,6 +145,14 @@ class UnifiedLayout {
// Event handlers
std::function<bool(const ftxui::Event&)> global_event_handler_;
std::function<bool(const ftxui::Event&)> panel_event_handler_;
// External providers
std::function<ftxui::Element()> status_provider_;
std::function<std::vector<std::string>()> command_summary_provider_;
std::function<std::vector<std::string>()> todo_provider_;
bool todo_overlay_visible_ = false;
ftxui::Component todo_overlay_component_;
};
} // namespace cli

View File

@@ -1,211 +1,43 @@
include(FetchContent)
# This file defines the z3ed command-line tool.
FetchContent_Declare(ftxui
GIT_REPOSITORY https://github.com/ArthurSonzogni/ftxui
GIT_TAG v5.0.0
)
FetchContent_GetProperties(ftxui)
if(NOT ftxui_POPULATED)
FetchContent_Populate(ftxui)
add_subdirectory(${ftxui_SOURCE_DIR} ${ftxui_BINARY_DIR} EXCLUDE_FROM_ALL)
endif()
find_package(yaml-cpp CONFIG)
if(NOT yaml-cpp_FOUND)
message(STATUS "yaml-cpp not found via package config, fetching from source")
FetchContent_Declare(yaml-cpp
GIT_REPOSITORY https://github.com/jbeder/yaml-cpp.git
GIT_TAG 0.8.0
)
FetchContent_GetProperties(yaml-cpp)
if(NOT yaml-cpp_POPULATED)
FetchContent_Populate(yaml-cpp)
set(_yaml_cpp_cmakelists "${yaml-cpp_SOURCE_DIR}/CMakeLists.txt")
if(EXISTS "${_yaml_cpp_cmakelists}")
file(READ "${_yaml_cpp_cmakelists}" _yaml_cpp_cmake_contents)
if(_yaml_cpp_cmake_contents MATCHES "cmake_minimum_required\\(VERSION 3\\.4\\)")
string(REPLACE "cmake_minimum_required(VERSION 3.4)"
"cmake_minimum_required(VERSION 3.5)"
_yaml_cpp_cmake_contents "${_yaml_cpp_cmake_contents}")
file(WRITE "${_yaml_cpp_cmakelists}" "${_yaml_cpp_cmake_contents}")
endif()
endif()
set(YAML_CPP_BUILD_TESTS OFF CACHE BOOL "Disable yaml-cpp tests" FORCE)
set(YAML_CPP_BUILD_CONTRIB OFF CACHE BOOL "Disable yaml-cpp contrib" FORCE)
set(YAML_CPP_BUILD_TOOLS OFF CACHE BOOL "Disable yaml-cpp tools" FORCE)
set(YAML_CPP_INSTALL OFF CACHE BOOL "Disable yaml-cpp install" FORCE)
set(YAML_CPP_FORMAT_SOURCE OFF CACHE BOOL "Disable yaml-cpp format target" FORCE)
add_subdirectory(${yaml-cpp_SOURCE_DIR} ${yaml-cpp_BINARY_DIR} EXCLUDE_FROM_ALL)
endif()
endif()
add_executable(
z3ed
add_executable(z3ed
cli/cli_main.cc
cli/cli.cc
cli/tui/tui.cc
cli/tui/unified_layout.cc
cli/tui/enhanced_chat_component.cc
cli/tui/enhanced_status_panel.cc
cli/tui/hex_viewer.cc
# Removed old-style handlers: compress.cc, patch.cc, tile16_transfer.cc
cli/handlers/game/dungeon.cc
cli/handlers/graphics/gfx.cc
cli/handlers/graphics/palette.cc
cli/handlers/rom/rom_commands.cc
cli/handlers/game/overworld.cc
cli/handlers/game/overworld_inspect.cc
# Removed old-style handlers: sprite.cc, command_palette.cc
cli/handlers/rom/project_commands.cc
cli/handlers/game/message.cc
cli/handlers/agent.cc
cli/handlers/agent/common.cc
cli/handlers/agent/general_commands.cc
cli/handlers/agent/conversation_test.cc
cli/handlers/agent/test_common.cc
cli/handlers/agent/test_commands.cc
cli/handlers/agent/todo_commands.cc
# New CommandHandler-based implementations
cli/handlers/tools/resource_commands.cc
cli/handlers/game/dungeon_commands.cc
cli/handlers/game/overworld_commands.cc
cli/handlers/tools/gui_commands.cc
cli/handlers/graphics/hex_commands.cc
cli/handlers/game/dialogue_commands.cc
cli/handlers/game/music_commands.cc
cli/handlers/graphics/palette_commands.cc
# cli/handlers/graphics/sprite_commands.cc # File doesn't exist
cli/handlers/tools/emulator_commands.cc
cli/handlers/game/message_commands.cc
cli/handlers/command_wrappers.cc
cli/flags.cc
cli/tui/asar_patch.cc
cli/tui/palette_editor.cc
cli/tui/command_palette.cc
cli/tui/chat_tui.cc
cli/tui/autocomplete_ui.cc
cli/util/autocomplete.cc
cli/service/agent/vim_mode.cc
cli/service/testing/test_suite_loader.cc
cli/service/testing/test_suite_reporter.cc
cli/service/testing/test_suite_writer.cc
# ... (source files)
)
target_compile_definitions(z3ed PRIVATE YAZE_ASSETS_PATH="${CMAKE_SOURCE_DIR}/assets")
# ============================================================================
# Copy Agent Assets for z3ed CLI
# ============================================================================
# Copy agent assets to build directory so z3ed can find them when running
# Copy agent assets for z3ed
if(EXISTS ${CMAKE_SOURCE_DIR}/assets/agent)
file(GLOB AGENT_ASSET_FILES "${CMAKE_SOURCE_DIR}/assets/agent/*")
file(COPY ${AGENT_ASSET_FILES} DESTINATION "${CMAKE_BINARY_DIR}/assets/agent/")
# Also add post-build copy for development workflow
file(COPY ${CMAKE_SOURCE_DIR}/assets/agent/ DESTINATION "${CMAKE_BINARY_DIR}/assets/agent/")
add_custom_command(TARGET z3ed POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/assets/agent
$<TARGET_FILE_DIR:z3ed>/assets/agent
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/assets/agent $<TARGET_FILE_DIR:z3ed>/assets/agent
COMMENT "Copying agent assets for z3ed"
)
endif()
# ============================================================================
# AI Agent Support (Consolidated via Z3ED_AI flag)
# ============================================================================
if(Z3ED_AI OR YAZE_WITH_JSON)
target_compile_definitions(z3ed PRIVATE YAZE_WITH_JSON)
message(STATUS "✓ z3ed AI agent enabled (Ollama + Gemini support)")
# Link nlohmann_json (already fetched in main CMakeLists if YAZE_WITH_JSON)
target_link_libraries(z3ed PRIVATE nlohmann_json::nlohmann_json)
endif()
# ============================================================================
# SSL/HTTPS Support (Optional - Required for Gemini API)
# ============================================================================
# SSL is only enabled when AI features are active
# Ollama (localhost) works without SSL, Gemini (HTTPS) requires it
if((Z3ED_AI OR YAZE_WITH_JSON) AND (YAZE_WITH_GRPC OR Z3ED_AI))
find_package(OpenSSL)
if(OpenSSL_FOUND)
# Define the SSL support macro for httplib
target_compile_definitions(z3ed PRIVATE CPPHTTPLIB_OPENSSL_SUPPORT)
# Link OpenSSL libraries
target_link_libraries(z3ed PRIVATE OpenSSL::SSL OpenSSL::Crypto)
# On macOS, also enable Keychain cert support
if(APPLE)
target_compile_definitions(z3ed PRIVATE CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN)
target_link_libraries(z3ed PRIVATE "-framework CoreFoundation" "-framework Security")
endif()
message(STATUS "✓ SSL/HTTPS support enabled for z3ed (Gemini API ready)")
else()
message(WARNING "OpenSSL not found - Gemini API will not work")
message(STATUS " • Ollama (local) still works without SSL")
message(STATUS " • Install OpenSSL for Gemini: brew install openssl (macOS) or apt install libssl-dev (Linux)")
endif()
else()
if(NOT Z3ED_AI AND NOT YAZE_WITH_JSON)
message(STATUS "○ z3ed AI agent disabled (set -DZ3ED_AI=ON to enable Gemini/Ollama)")
endif()
endif()
target_include_directories(z3ed PUBLIC
target_include_directories(z3ed PUBLIC
"${CMAKE_CURRENT_SOURCE_DIR}"
"${CMAKE_CURRENT_SOURCE_DIR}/tui"
)
# (Link libraries handled below; duplicate/unfinished lines removed.)
if(YAZE_USE_MODULAR_BUILD)
target_link_libraries(
z3ed PRIVATE
target_link_libraries(z3ed PRIVATE
yaze_core
yaze_agent
ftxui::component
)
else()
target_link_libraries(
z3ed PRIVATE
yaze_core
yaze_agent
ftxui::component
absl::flags
absl::flags_parse
)
)
if(Z3ED_AI)
target_link_libraries(z3ed PRIVATE yaml-cpp)
endif()
# ============================================================================
# Optional gRPC Support for CLI Agent Test Command
# ============================================================================
if(YAZE_WITH_GRPC)
message(STATUS "Adding gRPC support to z3ed CLI")
# Generate C++ code from .proto using the helper function from cmake/grpc.cmake
target_add_protobuf(z3ed
${CMAKE_SOURCE_DIR}/src/protos/imgui_test_harness.proto
${CMAKE_SOURCE_DIR}/src/protos/canvas_automation.proto)
# Add CLI gRPC service sources
target_sources(z3ed PRIVATE
${CMAKE_SOURCE_DIR}/src/cli/service/gui/gui_automation_client.cc
${CMAKE_SOURCE_DIR}/src/cli/service/gui/gui_automation_client.h
${CMAKE_SOURCE_DIR}/src/cli/service/testing/test_workflow_generator.cc
${CMAKE_SOURCE_DIR}/src/cli/service/testing/test_workflow_generator.h)
# Link gRPC libraries
target_link_libraries(z3ed PRIVATE
grpc++
grpc++_reflection
libprotobuf)
message(STATUS "✓ gRPC CLI automation integrated")
target_link_libraries(z3ed PRIVATE grpc++ grpc++_reflection libprotobuf)
endif()

View File

@@ -59,6 +59,7 @@ inline std::string GetColoredLogo() {
" ▲ ▲ " + "\033[0;37m" + "AI-Powered CLI\n" + // Gray
"\033[1;33m" +
" ▲▲▲▲▲ \n" +
"\033[1;32m" + " FTXUI ✦ Animations ✦ Command TODOs" + "\n" +
"\033[0m"; // Reset
}

View File

@@ -23,14 +23,13 @@ set(YAZE_UTIL_SRC
add_library(yaze_util STATIC ${YAZE_UTIL_SRC})
target_precompile_headers(yaze_util PRIVATE
<string>
<vector>
<string_view>
"$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_SOURCE_DIR}/src/yaze_pch.h>"
)
target_include_directories(yaze_util PUBLIC
${CMAKE_SOURCE_DIR}/src
${CMAKE_SOURCE_DIR}/incl
${CMAKE_SOURCE_DIR}/src/lib
${PROJECT_BINARY_DIR}
)

30
src/yaze_pch.h Normal file
View File

@@ -0,0 +1,30 @@
// This is a precompiled header for the yaze project.
// It includes a set of common, relatively stable headers that are used across
// multiple source files to speed up compilation.
#ifndef YAZE_PCH_H
#define YAZE_PCH_H
// Standard Library
#include <array>
#include <cstdint>
#include <iostream>
#include <memory>
#include <set>
#include <string>
#include <string_view>
#include <vector>
#ifdef __OBJC__
#import <Foundation/Foundation.h>
#endif
// Third-party libraries
#include "absl/strings/str_format.h"
#include "absl/strings/string_view.h"
// Project-specific headers
#include "util/log.h"
#endif // YAZE_PCH_H