6#include "absl/strings/str_cat.h"
7#include "absl/strings/str_format.h"
8#include "absl/strings/str_join.h"
17 escaped.reserve(input.size());
18 for (
char c : input) {
21 escaped.append(
"\\\\");
24 escaped.append(
"\\\"");
27 escaped.append(
"\\n");
30 escaped.append(
"\\r");
33 escaped.append(
"\\t");
43 const std::vector<std::string>& values) {
45 for (
size_t i = 0; i < values.size(); ++i) {
56 static bool initialized =
false;
65 std::unique_ptr<resources::CommandHandler> handler,
67 std::string name = handler->GetName();
73 for (
const auto& alias : metadata.
aliases) {
85 return it->second.get();
91 auto handler_it =
handlers_.find(alias_it->second);
93 return handler_it->second.get();
101 const std::string& name)
const {
103 std::string canonical_name = name;
104 auto alias_it =
aliases_.find(name);
106 canonical_name = alias_it->second;
109 auto it =
metadata_.find(canonical_name);
110 return (it !=
metadata_.end()) ? &it->second :
nullptr;
114 const std::string& category)
const {
115 std::vector<std::string> result;
116 for (
const auto& [name, metadata] :
metadata_) {
117 if (metadata.category == category) {
118 result.push_back(name);
125 std::vector<std::string> categories;
126 for (
const auto& [_, metadata] :
metadata_) {
127 if (std::find(categories.begin(), categories.end(), metadata.category) ==
129 categories.push_back(metadata.category);
136 std::vector<std::string> result;
137 for (
const auto& [name, metadata] :
metadata_) {
138 if (metadata.available_to_agent) {
139 result.push_back(name);
146 std::ostringstream out;
147 out <<
"{\n \"commands\": [\n";
150 for (
const auto& [_, metadata] :
metadata_) {
156 out <<
" \"name\": \"" << EscapeJson(metadata.name) <<
"\",\n";
157 out <<
" \"category\": \"" << EscapeJson(metadata.category) <<
"\",\n";
158 out <<
" \"description\": \"" << EscapeJson(metadata.description)
160 out <<
" \"usage\": \"" << EscapeJson(metadata.usage) <<
"\",\n";
161 out <<
" \"available_to_agent\": "
162 << (metadata.available_to_agent ?
"true" :
"false") <<
",\n";
163 out <<
" \"requires_rom\": "
164 << (metadata.requires_rom ?
"true" :
"false") <<
",\n";
165 out <<
" \"requires_grpc\": "
166 << (metadata.requires_grpc ?
"true" :
"false") <<
",\n";
167 if (!metadata.todo_reference.empty()) {
168 out <<
" \"todo_reference\": \""
169 << EscapeJson(metadata.todo_reference) <<
"\",\n";
171 out <<
" \"todo_reference\": \"\",\n";
173 out <<
" \"aliases\": ";
174 AppendStringArray(out, metadata.aliases);
176 out <<
" \"examples\": ";
177 AppendStringArray(out, metadata.examples);
189 return absl::StrFormat(
"Command '%s' not found", name);
192 std::ostringstream help;
193 help <<
"\n\033[1;36m" << metadata->name <<
"\033[0m - "
194 << metadata->description <<
"\n\n";
195 help <<
"\033[1;33mUsage:\033[0m\n";
196 help <<
" " << metadata->usage <<
"\n\n";
198 if (!metadata->examples.empty()) {
199 help <<
"\033[1;33mExamples:\033[0m\n";
200 for (
const auto& example : metadata->examples) {
201 help <<
" " << example <<
"\n";
206 if (metadata->requires_rom) {
207 help <<
"\033[1;33mRequires:\033[0m ROM file (--rom=<path>)\n";
209 if (metadata->requires_grpc) {
210 help <<
"\033[1;33mRequires:\033[0m YAZE running with gRPC enabled\n";
213 if (!metadata->aliases.empty()) {
214 help <<
"\n\033[1;33mAliases:\033[0m "
215 << absl::StrJoin(metadata->aliases,
", ") <<
"\n";
222 const std::string& category)
const {
224 if (commands.empty()) {
225 return absl::StrFormat(
"No commands in category '%s'", category);
228 std::ostringstream help;
229 help <<
"\n\033[1;36m" << category <<
" commands:\033[0m\n\n";
231 for (
const auto& cmd : commands) {
234 help <<
" \033[1;33m" << cmd <<
"\033[0m\n";
235 help <<
" " << metadata->description <<
"\n";
236 if (!metadata->usage.empty()) {
237 help <<
" Usage: " << metadata->usage <<
"\n";
247 std::ostringstream help;
248 help <<
"\n\033[1;36mAll z3ed Commands:\033[0m\n\n";
251 for (
const auto& category : categories) {
259 const std::vector<std::string>& args,
261 std::string* captured_output) {
262 auto* handler =
Get(name);
264 return absl::NotFoundError(absl::StrFormat(
"Command '%s' not found", name));
267 return handler->Run(args, rom_context, captured_output);
271 return Get(name) !=
nullptr;
278 for (
auto& handler : all_handlers) {
279 std::string name = handler->GetName();
282 auto descriptor = handler->Describe();
284 metadata.
name = name;
285 metadata.
usage = handler->GetUsage();
291 if (name.find(
"resource-") == 0) {
293 metadata.
description =
"Resource inspection and search";
294 if (name ==
"resource-list") {
295 metadata.
examples = {
"z3ed resource-list --type=dungeon --format=json",
296 "z3ed resource-list --type=sprite --format=table"};
298 }
else if (name.find(
"dungeon-") == 0) {
300 metadata.
description =
"Dungeon inspection and editing";
301 if (name ==
"dungeon-describe-room") {
303 "z3ed dungeon-describe-room --room=5 --format=json"};
305 }
else if (name.find(
"overworld-") == 0) {
307 metadata.
description =
"Overworld inspection and editing";
308 if (name ==
"overworld-find-tile") {
310 "z3ed overworld-find-tile --tile=0x42 --format=json"};
312 }
else if (name.find(
"rom-") == 0) {
314 metadata.
description =
"ROM inspection and validation";
315 if (name ==
"rom-info") {
316 metadata.
examples = {
"z3ed rom-info --rom=zelda3.sfc"};
317 }
else if (name ==
"rom-validate") {
318 metadata.
examples = {
"z3ed rom-validate --rom=zelda3.sfc"};
319 }
else if (name ==
"rom-doctor") {
320 metadata.
examples = {
"z3ed rom-doctor --rom=zelda3.sfc --format=json"};
321 }
else if (name ==
"rom-diff") {
323 "z3ed rom-diff --rom_a=base.sfc --rom_b=target.sfc"};
324 }
else if (name ==
"rom-compare") {
326 "z3ed rom-compare --rom=target.sfc --baseline=vanilla.sfc"};
328 }
else if (name.find(
"emulator-") == 0) {
330 metadata.
description =
"Emulator control and debugging";
332 if (name ==
"emulator-set-breakpoint") {
334 "z3ed emulator-set-breakpoint --address=0x83D7 --description='NMI "
337 }
else if (name.find(
"gui-") == 0) {
341 }
else if (name.find(
"hex-") == 0) {
344 }
else if (name.find(
"palette-") == 0) {
347 }
else if (name.find(
"sprite-") == 0) {
350 }
else if (name.find(
"message-") == 0 || name.find(
"dialogue-") == 0) {
352 metadata.
description = name.find(
"message-") == 0 ?
"Message inspection"
353 :
"Dialogue inspection";
354 }
else if (name.find(
"music-") == 0) {
357 }
else if (name ==
"simple-chat" || name ==
"chat") {
363 "z3ed simple-chat --rom=zelda3.sfc",
364 "z3ed simple-chat \"What dungeons exist?\" --rom=zelda3.sfc"};
365 }
else if (name.find(
"tools-") == 0) {
367 if (name ==
"tools-list") {
368 metadata.
description =
"List available test helper tools";
371 }
else if (name ==
"tools-harness-state") {
372 metadata.
description =
"Generate WRAM state for test harnesses";
374 "z3ed tools-harness-state --rom=zelda3.sfc --output=state.h"};
375 }
else if (name ==
"tools-extract-values") {
376 metadata.
description =
"Extract vanilla ROM values";
377 metadata.
examples = {
"z3ed tools-extract-values --rom=zelda3.sfc"};
378 }
else if (name ==
"tools-extract-golden") {
379 metadata.
description =
"Extract comprehensive golden data for testing";
381 "z3ed tools-extract-golden --rom=zelda3.sfc --output=golden.h"};
382 }
else if (name ==
"tools-patch-v3") {
383 metadata.
description =
"Create v3 ZSCustomOverworld patched ROM";
385 "z3ed tools-patch-v3 --rom=zelda3.sfc --output=patched.sfc"};
389 }
else if (name.find(
"test-") == 0) {
391 metadata.
description =
"Test discovery and execution";
392 if (name ==
"test-list") {
394 metadata.
examples = {
"z3ed test-list",
"z3ed test-list --format json"};
395 }
else if (name ==
"test-run") {
396 metadata.
examples = {
"z3ed test-run --label stable",
397 "z3ed test-run --label gui"};
398 }
else if (name ==
"test-status") {
400 metadata.
examples = {
"z3ed test-status --format json"};
408 if (!descriptor.summary.empty() &&
409 descriptor.summary !=
"Command summary not provided.") {
414 if (!descriptor.todo_reference.empty() &&
415 descriptor.todo_reference !=
"todo#unassigned") {
419 Register(std::move(handler), metadata);
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Single source of truth for all z3ed commands.
std::map< std::string, std::string > aliases_
std::vector< std::string > GetCommandsInCategory(const std::string &category) const
Get all commands in a category.
static CommandRegistry & Instance()
std::string GenerateCategoryHelp(const std::string &category) const
Generate category help text.
void RegisterAllCommands()
std::string ExportFunctionSchemas() const
Export function schemas for AI tool calling (JSON)
const CommandMetadata * GetMetadata(const std::string &name) const
Get command metadata.
std::string GenerateCompleteHelp() const
Generate complete help text (all commands)
std::vector< std::string > GetAgentCommands() const
Get all commands available to AI agents.
std::map< std::string, CommandMetadata > metadata_
absl::Status Execute(const std::string &name, const std::vector< std::string > &args, Rom *rom_context=nullptr, std::string *captured_output=nullptr)
Execute a command by name.
std::map< std::string, std::unique_ptr< resources::CommandHandler > > handlers_
resources::CommandHandler * Get(const std::string &name) const
Get a command handler by name or alias.
bool HasCommand(const std::string &name) const
Check if command exists.
std::vector< std::string > GetCategories() const
Get all categories.
void Register(std::unique_ptr< resources::CommandHandler > handler, const CommandMetadata &metadata)
Register a command handler.
std::string GenerateHelp(const std::string &name) const
Generate help text for a command.
Base class for CLI command handlers.
std::string EscapeJson(const std::string &input)
void AppendStringArray(std::ostringstream &out, const std::vector< std::string > &values)
std::vector< std::unique_ptr< resources::CommandHandler > > CreateAllCommandHandlers()
Factory function to create all command handlers (CLI + agent)