refactor(gui): reorganize background rendering and layout helpers
- Moved background rendering functionality from the editor to a dedicated GUI module, enhancing modularity and separation of concerns. - Introduced layout helpers for consistent theme-aware sizing across the GUI, improving UI consistency and maintainability. - Updated CMake configuration to reflect the new structure, ensuring proper linkage of the background renderer and layout helpers. Benefits: - Improved organization of GUI components, facilitating easier updates and enhancements. - Enhanced user interface consistency through theme-aware layout management.
This commit is contained in:
@@ -1,12 +1,18 @@
|
||||
set(YAZE_AGENT_SOURCES
|
||||
# Core infrastructure
|
||||
cli/service/command_registry.cc
|
||||
cli/service/agent/proposal_executor.cc
|
||||
cli/handlers/agent/todo_commands.cc
|
||||
cli/service/agent/conversational_agent_service.cc
|
||||
cli/service/agent/simple_chat_session.cc
|
||||
cli/service/agent/enhanced_tui.cc
|
||||
cli/service/agent/tool_dispatcher.cc
|
||||
|
||||
# Advanced features
|
||||
cli/service/agent/learned_knowledge_service.cc
|
||||
cli/service/agent/todo_manager.cc
|
||||
cli/service/agent/advanced_routing.cc
|
||||
cli/service/agent/agent_pretraining.cc
|
||||
cli/service/agent/vim_mode.cc
|
||||
cli/service/ai/ai_service.cc
|
||||
cli/service/ai/ai_action_parser.cc
|
||||
|
||||
@@ -7,20 +7,18 @@
|
||||
#include "absl/flags/declare.h"
|
||||
#include "absl/flags/flag.h"
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "cli/service/command_registry.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"
|
||||
|
||||
ABSL_DECLARE_FLAG(bool, quiet);
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
|
||||
// Forward declarations from general_commands.cc
|
||||
// Forward declarations for special agent commands (not in registry)
|
||||
namespace agent {
|
||||
absl::Status HandlePlanCommand(const std::vector<std::string>& args);
|
||||
absl::Status HandleTestCommand(const std::vector<std::string>& args);
|
||||
@@ -29,211 +27,134 @@ 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 {
|
||||
|
||||
constexpr absl::string_view kUsage =
|
||||
"Usage: agent <subcommand> [options]\n"
|
||||
"\n"
|
||||
"AI-Powered Agent Subcommands:\n"
|
||||
" simple-chat Simple text-based chat (recommended for testing)\n"
|
||||
" Modes: interactive | piped | batch | single-message\n"
|
||||
" Example: agent simple-chat \"What dungeons exist?\" --rom=zelda3.sfc\n"
|
||||
" Example: agent simple-chat --rom=zelda3.sfc --ai_provider=ollama\n"
|
||||
" Example: echo \"List sprites\" | agent simple-chat --rom=zelda3.sfc\n"
|
||||
" Example: agent simple-chat --file=queries.txt --rom=zelda3.sfc\n"
|
||||
"\n"
|
||||
" test-conversation Run automated test conversation with AI\n"
|
||||
" Example: agent test-conversation --rom=zelda3.sfc --ai_provider=ollama\n"
|
||||
"\n"
|
||||
" chat Full FTXUI-based chat interface\n"
|
||||
" Example: agent chat --rom=zelda3.sfc\n"
|
||||
"\n"
|
||||
"ROM Inspection Tools (can be called by AI or directly):\n"
|
||||
" resource-list List labeled resources (dungeons, sprites, etc.)\n"
|
||||
" Example: agent resource-list --type=dungeon --format=json\n"
|
||||
"\n"
|
||||
" resource-search Search resource labels by fuzzy text\n"
|
||||
" Example: agent resource-search --query=soldier --type=sprite\n"
|
||||
"\n"
|
||||
" dungeon-list-sprites List sprites in a dungeon room\n"
|
||||
" Example: agent dungeon-list-sprites --room=5 --format=json\n"
|
||||
"\n"
|
||||
" dungeon-describe-room Summarize metadata for a dungeon room\n"
|
||||
" Example: agent dungeon-describe-room --room=0x12 --format=text\n"
|
||||
"\n"
|
||||
" overworld-find-tile Search for tile placements in overworld\n"
|
||||
" Example: agent overworld-find-tile --tile=0x02E --format=json\n"
|
||||
"\n"
|
||||
" overworld-describe-map Get metadata about an overworld map\n"
|
||||
" Example: agent overworld-describe-map --map=0 --format=json\n"
|
||||
"\n"
|
||||
" overworld-list-warps List entrances/exits/holes in overworld\n"
|
||||
" Example: agent overworld-list-warps --map=0 --format=json\n"
|
||||
"\n"
|
||||
"Proposal & Testing Commands:\n"
|
||||
" run Execute agent task\n"
|
||||
" plan Generate execution plan\n"
|
||||
" diff Show ROM differences\n"
|
||||
" accept Accept and apply proposal changes\n"
|
||||
" test Run agent tests\n"
|
||||
" gui Launch GUI components\n"
|
||||
" learn Train agent on examples\n"
|
||||
" list List available resources\n"
|
||||
" commit Commit changes\n"
|
||||
" revert Revert changes\n"
|
||||
" describe Describe agent capabilities\n"
|
||||
" todo Manage tasks and project planning\n"
|
||||
"\n"
|
||||
"Global Options:\n"
|
||||
" --rom=<path> Path to Zelda3 ROM file (required for most commands)\n"
|
||||
" --ai_provider=<name> AI provider: mock (default) | ollama | gemini\n"
|
||||
" --ai_model=<name> Model name (e.g., qwen2.5-coder:7b for Ollama)\n"
|
||||
" --ollama_host=<url> Ollama server URL (default: http://localhost:11434)\n"
|
||||
" --gemini_api_key=<key> Gemini API key (or set GEMINI_API_KEY env var)\n"
|
||||
" --format=<type> Output format: text | markdown | json | compact\n"
|
||||
"\n"
|
||||
"For more details, see: docs/simple_chat_input_methods.md";
|
||||
std::string GenerateAgentHelp() {
|
||||
auto& registry = CommandRegistry::Instance();
|
||||
|
||||
std::ostringstream help;
|
||||
help << "Usage: agent <subcommand> [options]\n\n";
|
||||
|
||||
help << "AI-Powered Agent Commands:\n";
|
||||
help << " simple-chat Interactive AI chat\n";
|
||||
help << " test-conversation Automated test conversation\n";
|
||||
help << " plan Generate execution plan\n";
|
||||
help << " learn Manage learned knowledge\n";
|
||||
help << " todo Task management\n";
|
||||
help << " test Run tests\n";
|
||||
help << " list/describe List/describe proposals\n\n";
|
||||
|
||||
// Auto-list available tool commands from registry
|
||||
help << "Tool Commands (AI can call these):\n";
|
||||
auto agent_commands = registry.GetAgentCommands();
|
||||
int count = 0;
|
||||
for (const auto& cmd : agent_commands) {
|
||||
if (count++ < 10) { // Show first 10
|
||||
auto* meta = registry.GetMetadata(cmd);
|
||||
if (meta) {
|
||||
help << " " << cmd;
|
||||
for (size_t i = cmd.length(); i < 24; i++) help << " ";
|
||||
help << meta->description << "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
help << " ... and " << (agent_commands.size() - 10) << " more (see z3ed --list-commands)\n\n";
|
||||
|
||||
help << "Global Options:\n";
|
||||
help << " --rom=<path> Path to ROM file\n";
|
||||
help << " --ai_provider=<name> AI provider: ollama | gemini\n";
|
||||
help << " --format=<type> Output format: text | json\n\n";
|
||||
|
||||
help << "For detailed help: z3ed agent <command> --help\n";
|
||||
help << "For all commands: z3ed --list-commands\n";
|
||||
|
||||
return help.str();
|
||||
}
|
||||
|
||||
constexpr absl::string_view kUsage = "";
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace handlers {
|
||||
|
||||
// Legacy Agent class removed - using new CommandHandler system
|
||||
// This implementation should be moved to a proper AgentCommandHandler
|
||||
/**
|
||||
* @brief Unified agent command handler using CommandRegistry
|
||||
*
|
||||
* Routes commands in this order:
|
||||
* 1. Special agent commands (plan, test, learn, todo) - Not in registry
|
||||
* 2. Registry commands (resource-*, dungeon-*, overworld-*, emulator-*, etc.)
|
||||
* 3. Fallback to error
|
||||
*/
|
||||
absl::Status HandleAgentCommand(const std::vector<std::string>& arg_vec) {
|
||||
if (arg_vec.empty()) {
|
||||
return absl::InvalidArgumentError(std::string(kUsage));
|
||||
std::cout << GenerateAgentHelp();
|
||||
return absl::InvalidArgumentError("No subcommand specified");
|
||||
}
|
||||
|
||||
const std::string& subcommand = arg_vec[0];
|
||||
std::vector<std::string> subcommand_args(arg_vec.begin() + 1, arg_vec.end());
|
||||
|
||||
if (subcommand == "run") {
|
||||
return absl::UnimplementedError("Agent run command requires ROM context - not yet implemented");
|
||||
|
||||
// === Special Agent Commands (not in registry) ===
|
||||
|
||||
if (subcommand == "simple-chat" || subcommand == "chat") {
|
||||
auto& registry = CommandRegistry::Instance();
|
||||
return registry.Execute("simple-chat", subcommand_args, nullptr);
|
||||
}
|
||||
|
||||
if (subcommand == "plan") {
|
||||
return agent::HandlePlanCommand(subcommand_args);
|
||||
}
|
||||
if (subcommand == "diff") {
|
||||
return absl::UnimplementedError("Agent diff command requires ROM context - not yet implemented");
|
||||
}
|
||||
if (subcommand == "accept") {
|
||||
return absl::UnimplementedError("Agent accept command requires ROM context - not yet implemented");
|
||||
}
|
||||
|
||||
if (subcommand == "test") {
|
||||
return agent::HandleTestCommand(subcommand_args);
|
||||
}
|
||||
|
||||
if (subcommand == "test-conversation") {
|
||||
return agent::HandleTestConversationCommand(subcommand_args);
|
||||
}
|
||||
|
||||
if (subcommand == "gui") {
|
||||
return agent::HandleGuiCommand(subcommand_args);
|
||||
}
|
||||
|
||||
if (subcommand == "learn") {
|
||||
return agent::HandleLearnCommand(subcommand_args);
|
||||
}
|
||||
|
||||
if (subcommand == "todo") {
|
||||
return handlers::HandleTodoCommand(subcommand_args);
|
||||
}
|
||||
|
||||
if (subcommand == "list") {
|
||||
return agent::HandleListCommand();
|
||||
}
|
||||
if (subcommand == "commit") {
|
||||
return absl::UnimplementedError("Agent commit command requires ROM context - not yet implemented");
|
||||
}
|
||||
if (subcommand == "revert") {
|
||||
return absl::UnimplementedError("Agent revert command requires ROM context - not yet implemented");
|
||||
}
|
||||
|
||||
if (subcommand == "describe") {
|
||||
return agent::HandleDescribeCommand(subcommand_args);
|
||||
}
|
||||
if (subcommand == "resource-list") {
|
||||
return agent::HandleResourceListCommand(subcommand_args, nullptr);
|
||||
}
|
||||
if (subcommand == "resource-search") {
|
||||
return agent::HandleResourceSearchCommand(subcommand_args, nullptr);
|
||||
}
|
||||
if (subcommand == "dungeon-list-sprites") {
|
||||
return agent::HandleDungeonListSpritesCommand(subcommand_args, nullptr);
|
||||
}
|
||||
if (subcommand == "dungeon-describe-room") {
|
||||
return agent::HandleDungeonDescribeRoomCommand(subcommand_args, nullptr);
|
||||
}
|
||||
if (subcommand == "overworld-find-tile") {
|
||||
return agent::HandleOverworldFindTileCommand(subcommand_args, nullptr);
|
||||
}
|
||||
if (subcommand == "overworld-describe-map") {
|
||||
return agent::HandleOverworldDescribeMapCommand(subcommand_args, nullptr);
|
||||
}
|
||||
if (subcommand == "overworld-list-warps") {
|
||||
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);
|
||||
// }
|
||||
// Placeholder for unimplemented workflow commands
|
||||
if (subcommand == "run" || subcommand == "diff" || subcommand == "accept" ||
|
||||
subcommand == "commit" || subcommand == "revert") {
|
||||
return absl::UnimplementedError(
|
||||
absl::StrCat("Agent ", subcommand, " command requires ROM context - not yet implemented"));
|
||||
}
|
||||
|
||||
// // 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));
|
||||
// === Registry Commands (resource, dungeon, overworld, emulator, etc.) ===
|
||||
|
||||
auto& registry = CommandRegistry::Instance();
|
||||
|
||||
// Check if this is a registered command
|
||||
if (registry.HasCommand(subcommand)) {
|
||||
return registry.Execute(subcommand, subcommand_args, nullptr);
|
||||
}
|
||||
|
||||
// Not found
|
||||
std::cout << GenerateAgentHelp();
|
||||
return absl::InvalidArgumentError(
|
||||
absl::StrCat("Unknown agent command: ", subcommand));
|
||||
}
|
||||
|
||||
// Handler functions are now implemented in command_wrappers.cc
|
||||
|
||||
279
src/cli/service/command_registry.cc
Normal file
279
src/cli/service/command_registry.cc
Normal file
@@ -0,0 +1,279 @@
|
||||
#include "cli/service/command_registry.h"
|
||||
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "absl/strings/str_join.h"
|
||||
#include "cli/handlers/command_handlers.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
|
||||
CommandRegistry& CommandRegistry::Instance() {
|
||||
static CommandRegistry instance;
|
||||
static bool initialized = false;
|
||||
if (!initialized) {
|
||||
instance.RegisterAllCommands();
|
||||
initialized = true;
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
void CommandRegistry::Register(std::unique_ptr<resources::CommandHandler> handler,
|
||||
const CommandMetadata& metadata) {
|
||||
std::string name = handler->GetName();
|
||||
|
||||
// Store metadata
|
||||
metadata_[name] = metadata;
|
||||
|
||||
// Register aliases
|
||||
for (const auto& alias : metadata.aliases) {
|
||||
aliases_[alias] = name;
|
||||
}
|
||||
|
||||
// Store handler
|
||||
handlers_[name] = std::move(handler);
|
||||
}
|
||||
|
||||
resources::CommandHandler* CommandRegistry::Get(const std::string& name) const {
|
||||
// Check direct name
|
||||
auto it = handlers_.find(name);
|
||||
if (it != handlers_.end()) {
|
||||
return it->second.get();
|
||||
}
|
||||
|
||||
// Check aliases
|
||||
auto alias_it = aliases_.find(name);
|
||||
if (alias_it != aliases_.end()) {
|
||||
auto handler_it = handlers_.find(alias_it->second);
|
||||
if (handler_it != handlers_.end()) {
|
||||
return handler_it->second.get();
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const CommandRegistry::CommandMetadata* CommandRegistry::GetMetadata(
|
||||
const std::string& name) const {
|
||||
// Resolve alias first
|
||||
std::string canonical_name = name;
|
||||
auto alias_it = aliases_.find(name);
|
||||
if (alias_it != aliases_.end()) {
|
||||
canonical_name = alias_it->second;
|
||||
}
|
||||
|
||||
auto it = metadata_.find(canonical_name);
|
||||
return (it != metadata_.end()) ? &it->second : nullptr;
|
||||
}
|
||||
|
||||
std::vector<std::string> CommandRegistry::GetCommandsInCategory(
|
||||
const std::string& category) const {
|
||||
std::vector<std::string> result;
|
||||
for (const auto& [name, metadata] : metadata_) {
|
||||
if (metadata.category == category) {
|
||||
result.push_back(name);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::string> CommandRegistry::GetCategories() const {
|
||||
std::vector<std::string> categories;
|
||||
for (const auto& [_, metadata] : metadata_) {
|
||||
if (std::find(categories.begin(), categories.end(), metadata.category) == categories.end()) {
|
||||
categories.push_back(metadata.category);
|
||||
}
|
||||
}
|
||||
return categories;
|
||||
}
|
||||
|
||||
std::vector<std::string> CommandRegistry::GetAgentCommands() const {
|
||||
std::vector<std::string> result;
|
||||
for (const auto& [name, metadata] : metadata_) {
|
||||
if (metadata.available_to_agent) {
|
||||
result.push_back(name);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string CommandRegistry::ExportFunctionSchemas() const {
|
||||
// TODO: Generate JSON function schemas from metadata
|
||||
// This would replace manual function_schemas.json maintenance
|
||||
return "{}"; // Placeholder
|
||||
}
|
||||
|
||||
std::string CommandRegistry::GenerateHelp(const std::string& name) const {
|
||||
auto* metadata = GetMetadata(name);
|
||||
if (!metadata) {
|
||||
return absl::StrFormat("Command '%s' not found", name);
|
||||
}
|
||||
|
||||
std::ostringstream help;
|
||||
help << "\n\033[1;36m" << metadata->name << "\033[0m - " << metadata->description << "\n\n";
|
||||
help << "\033[1;33mUsage:\033[0m\n";
|
||||
help << " " << metadata->usage << "\n\n";
|
||||
|
||||
if (!metadata->examples.empty()) {
|
||||
help << "\033[1;33mExamples:\033[0m\n";
|
||||
for (const auto& example : metadata->examples) {
|
||||
help << " " << example << "\n";
|
||||
}
|
||||
help << "\n";
|
||||
}
|
||||
|
||||
if (metadata->requires_rom) {
|
||||
help << "\033[1;33mRequires:\033[0m ROM file (--rom=<path>)\n";
|
||||
}
|
||||
if (metadata->requires_grpc) {
|
||||
help << "\033[1;33mRequires:\033[0m YAZE running with gRPC enabled\n";
|
||||
}
|
||||
|
||||
if (!metadata->aliases.empty()) {
|
||||
help << "\n\033[1;33mAliases:\033[0m " << absl::StrJoin(metadata->aliases, ", ") << "\n";
|
||||
}
|
||||
|
||||
return help.str();
|
||||
}
|
||||
|
||||
std::string CommandRegistry::GenerateCategoryHelp(const std::string& category) const {
|
||||
auto commands = GetCommandsInCategory(category);
|
||||
if (commands.empty()) {
|
||||
return absl::StrFormat("No commands in category '%s'", category);
|
||||
}
|
||||
|
||||
std::ostringstream help;
|
||||
help << "\n\033[1;36m" << category << " commands:\033[0m\n\n";
|
||||
|
||||
for (const auto& cmd : commands) {
|
||||
auto* metadata = GetMetadata(cmd);
|
||||
if (metadata) {
|
||||
help << " \033[1;33m" << cmd << "\033[0m\n";
|
||||
help << " " << metadata->description << "\n";
|
||||
if (!metadata->usage.empty()) {
|
||||
help << " Usage: " << metadata->usage << "\n";
|
||||
}
|
||||
help << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
return help.str();
|
||||
}
|
||||
|
||||
std::string CommandRegistry::GenerateCompleteHelp() const {
|
||||
std::ostringstream help;
|
||||
help << "\n\033[1;36mAll z3ed Commands:\033[0m\n\n";
|
||||
|
||||
auto categories = GetCategories();
|
||||
for (const auto& category : categories) {
|
||||
help << GenerateCategoryHelp(category);
|
||||
}
|
||||
|
||||
return help.str();
|
||||
}
|
||||
|
||||
absl::Status CommandRegistry::Execute(const std::string& name,
|
||||
const std::vector<std::string>& args,
|
||||
Rom* rom_context) {
|
||||
auto* handler = Get(name);
|
||||
if (!handler) {
|
||||
return absl::NotFoundError(absl::StrFormat("Command '%s' not found", name));
|
||||
}
|
||||
|
||||
return handler->Run(args, rom_context);
|
||||
}
|
||||
|
||||
bool CommandRegistry::HasCommand(const std::string& name) const {
|
||||
return Get(name) != nullptr;
|
||||
}
|
||||
|
||||
void CommandRegistry::RegisterAllCommands() {
|
||||
// Get all command handlers from factory
|
||||
auto all_handlers = handlers::CreateAllCommandHandlers();
|
||||
|
||||
for (auto& handler : all_handlers) {
|
||||
std::string name = handler->GetName();
|
||||
|
||||
// Build metadata from handler
|
||||
CommandMetadata metadata;
|
||||
metadata.name = name;
|
||||
metadata.usage = handler->GetUsage();
|
||||
metadata.available_to_agent = true; // Most commands available to agent
|
||||
metadata.requires_rom = true; // Most commands need ROM
|
||||
metadata.requires_grpc = false;
|
||||
|
||||
// Categorize and enhance metadata based on command type
|
||||
if (name.find("resource-") == 0) {
|
||||
metadata.category = "resource";
|
||||
metadata.description = "Resource inspection and search";
|
||||
if (name == "resource-list") {
|
||||
metadata.examples = {
|
||||
"z3ed resource-list --type=dungeon --format=json",
|
||||
"z3ed resource-list --type=sprite --format=table"
|
||||
};
|
||||
}
|
||||
} else if (name.find("dungeon-") == 0) {
|
||||
metadata.category = "dungeon";
|
||||
metadata.description = "Dungeon inspection and editing";
|
||||
if (name == "dungeon-describe-room") {
|
||||
metadata.examples = {
|
||||
"z3ed dungeon-describe-room --room=5 --format=json"
|
||||
};
|
||||
}
|
||||
} else if (name.find("overworld-") == 0) {
|
||||
metadata.category = "overworld";
|
||||
metadata.description = "Overworld inspection and editing";
|
||||
if (name == "overworld-find-tile") {
|
||||
metadata.examples = {
|
||||
"z3ed overworld-find-tile --tile=0x42 --format=json"
|
||||
};
|
||||
}
|
||||
} else if (name.find("emulator-") == 0) {
|
||||
metadata.category = "emulator";
|
||||
metadata.description = "Emulator control and debugging";
|
||||
metadata.requires_grpc = true;
|
||||
if (name == "emulator-set-breakpoint") {
|
||||
metadata.examples = {
|
||||
"z3ed emulator-set-breakpoint --address=0x83D7 --description='NMI handler'"
|
||||
};
|
||||
}
|
||||
} else if (name.find("gui-") == 0) {
|
||||
metadata.category = "gui";
|
||||
metadata.description = "GUI automation";
|
||||
metadata.requires_grpc = true;
|
||||
} else if (name.find("hex-") == 0) {
|
||||
metadata.category = "graphics";
|
||||
metadata.description = "Hex data manipulation";
|
||||
} else if (name.find("palette-") == 0) {
|
||||
metadata.category = "graphics";
|
||||
metadata.description = "Palette operations";
|
||||
} else if (name.find("sprite-") == 0) {
|
||||
metadata.category = "graphics";
|
||||
metadata.description = "Sprite operations";
|
||||
} else if (name.find("message-") == 0 || name.find("dialogue-") == 0) {
|
||||
metadata.category = "game";
|
||||
metadata.description = name.find("message-") == 0 ? "Message inspection" : "Dialogue inspection";
|
||||
} else if (name.find("music-") == 0) {
|
||||
metadata.category = "game";
|
||||
metadata.description = "Music/audio inspection";
|
||||
} else if (name == "simple-chat" || name == "chat") {
|
||||
metadata.category = "agent";
|
||||
metadata.description = "AI conversational agent";
|
||||
metadata.available_to_agent = false; // Meta-command
|
||||
metadata.requires_rom = false;
|
||||
metadata.examples = {
|
||||
"z3ed simple-chat --rom=zelda3.sfc",
|
||||
"z3ed simple-chat \"What dungeons exist?\" --rom=zelda3.sfc"
|
||||
};
|
||||
} else {
|
||||
metadata.category = "misc";
|
||||
metadata.description = "Miscellaneous command";
|
||||
}
|
||||
|
||||
Register(std::move(handler), metadata);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace cli
|
||||
} // namespace yaze
|
||||
|
||||
130
src/cli/service/command_registry.h
Normal file
130
src/cli/service/command_registry.h
Normal file
@@ -0,0 +1,130 @@
|
||||
#ifndef YAZE_CLI_SERVICE_COMMAND_REGISTRY_H_
|
||||
#define YAZE_CLI_SERVICE_COMMAND_REGISTRY_H_
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/status/statusor.h"
|
||||
#include "cli/service/resources/command_handler.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
|
||||
/**
|
||||
* @class CommandRegistry
|
||||
* @brief Single source of truth for all z3ed commands
|
||||
*
|
||||
* Serves as the central registry for:
|
||||
* - CLI command routing
|
||||
* - Agent tool calling
|
||||
* - Help text generation
|
||||
* - TUI menu generation
|
||||
* - Function schema export (for AI)
|
||||
*
|
||||
* Ensures consistency: if a command exists, it's available to both
|
||||
* human users (CLI) and AI agents (tool calling).
|
||||
*/
|
||||
class CommandRegistry {
|
||||
public:
|
||||
struct CommandMetadata {
|
||||
std::string name; // e.g., "resource-list"
|
||||
std::string category; // e.g., "resource", "emulator", "dungeon"
|
||||
std::string description; // Short description
|
||||
std::string usage; // Full usage string
|
||||
bool available_to_agent; // Can AI call this via tool dispatch?
|
||||
bool requires_rom; // Requires ROM context?
|
||||
bool requires_grpc; // Requires gRPC/emulator running?
|
||||
std::vector<std::string> aliases; // Alternative names
|
||||
std::vector<std::string> examples; // Usage examples
|
||||
std::string todo_reference; // TODO tracker reference (if incomplete)
|
||||
};
|
||||
|
||||
static CommandRegistry& Instance();
|
||||
|
||||
/**
|
||||
* @brief Register a command handler
|
||||
*/
|
||||
void Register(std::unique_ptr<resources::CommandHandler> handler,
|
||||
const CommandMetadata& metadata);
|
||||
|
||||
/**
|
||||
* @brief Get a command handler by name or alias
|
||||
*/
|
||||
resources::CommandHandler* Get(const std::string& name) const;
|
||||
|
||||
/**
|
||||
* @brief Get command metadata
|
||||
*/
|
||||
const CommandMetadata* GetMetadata(const std::string& name) const;
|
||||
|
||||
/**
|
||||
* @brief Get all commands in a category
|
||||
*/
|
||||
std::vector<std::string> GetCommandsInCategory(const std::string& category) const;
|
||||
|
||||
/**
|
||||
* @brief Get all categories
|
||||
*/
|
||||
std::vector<std::string> GetCategories() const;
|
||||
|
||||
/**
|
||||
* @brief Get all commands available to AI agents
|
||||
*/
|
||||
std::vector<std::string> GetAgentCommands() const;
|
||||
|
||||
/**
|
||||
* @brief Export function schemas for AI tool calling (JSON)
|
||||
*/
|
||||
std::string ExportFunctionSchemas() const;
|
||||
|
||||
/**
|
||||
* @brief Generate help text for a command
|
||||
*/
|
||||
std::string GenerateHelp(const std::string& name) const;
|
||||
|
||||
/**
|
||||
* @brief Generate category help text
|
||||
*/
|
||||
std::string GenerateCategoryHelp(const std::string& category) const;
|
||||
|
||||
/**
|
||||
* @brief Generate complete help text (all commands)
|
||||
*/
|
||||
std::string GenerateCompleteHelp() const;
|
||||
|
||||
/**
|
||||
* @brief Execute a command by name
|
||||
*/
|
||||
absl::Status Execute(const std::string& name,
|
||||
const std::vector<std::string>& args,
|
||||
Rom* rom_context = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Check if command exists
|
||||
*/
|
||||
bool HasCommand(const std::string& name) const;
|
||||
|
||||
/**
|
||||
* @brief Get total command count
|
||||
*/
|
||||
size_t Count() const { return handlers_.size(); }
|
||||
|
||||
private:
|
||||
CommandRegistry() = default;
|
||||
|
||||
// Storage
|
||||
std::map<std::string, std::unique_ptr<resources::CommandHandler>> handlers_;
|
||||
std::map<std::string, CommandMetadata> metadata_;
|
||||
std::map<std::string, std::string> aliases_; // alias → canonical name
|
||||
|
||||
// Auto-register all commands
|
||||
void RegisterAllCommands();
|
||||
};
|
||||
|
||||
} // namespace cli
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_CLI_SERVICE_COMMAND_REGISTRY_H_
|
||||
|
||||
@@ -81,6 +81,11 @@ class CommandHandler {
|
||||
* @brief Provide metadata for TUI/help summaries.
|
||||
*/
|
||||
virtual Descriptor Describe() const;
|
||||
|
||||
/**
|
||||
* @brief Get the command usage string
|
||||
*/
|
||||
virtual std::string GetUsage() const = 0;
|
||||
|
||||
protected:
|
||||
/**
|
||||
@@ -100,10 +105,6 @@ class CommandHandler {
|
||||
virtual absl::Status Execute(Rom* rom, const ArgumentParser& parser,
|
||||
OutputFormatter& formatter) = 0;
|
||||
|
||||
/**
|
||||
* @brief Get the command usage string
|
||||
*/
|
||||
virtual std::string GetUsage() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Check if the command requires ROM labels
|
||||
|
||||
Reference in New Issue
Block a user