6#include "absl/strings/str_cat.h"
7#include "absl/strings/str_format.h"
8#include "absl/strings/str_join.h"
15std::string EscapeJson(
const std::string& input) {
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) {
46 if (i > 0) out <<
", ";
47 out <<
"\"" << EscapeJson(values[i]) <<
"\"";
55 static bool initialized =
false;
64 std::unique_ptr<resources::CommandHandler> handler,
66 std::string name = handler->GetName();
72 for (
const auto& alias : metadata.
aliases) {
84 return it->second.get();
90 auto handler_it =
handlers_.find(alias_it->second);
92 return handler_it->second.get();
100 const std::string& name)
const {
102 std::string canonical_name = name;
103 auto alias_it =
aliases_.find(name);
105 canonical_name = alias_it->second;
108 auto it =
metadata_.find(canonical_name);
109 return (it !=
metadata_.end()) ? &it->second :
nullptr;
113 const std::string& category)
const {
114 std::vector<std::string> result;
115 for (
const auto& [name, metadata] :
metadata_) {
116 if (metadata.category == category) {
117 result.push_back(name);
124 std::vector<std::string> categories;
125 for (
const auto& [_, metadata] :
metadata_) {
126 if (std::find(categories.begin(), categories.end(), metadata.category) ==
128 categories.push_back(metadata.category);
135 std::vector<std::string> result;
136 for (
const auto& [name, metadata] :
metadata_) {
137 if (metadata.available_to_agent) {
138 result.push_back(name);
145 std::ostringstream out;
146 out <<
"{\n \"commands\": [\n";
149 for (
const auto& [_, metadata] :
metadata_) {
150 if (!first) out <<
",\n";
154 out <<
" \"name\": \"" << EscapeJson(metadata.name) <<
"\",\n";
155 out <<
" \"category\": \"" << EscapeJson(metadata.category) <<
"\",\n";
156 out <<
" \"description\": \"" << EscapeJson(metadata.description) <<
"\",\n";
157 out <<
" \"usage\": \"" << EscapeJson(metadata.usage) <<
"\",\n";
158 out <<
" \"available_to_agent\": "
159 << (metadata.available_to_agent ?
"true" :
"false") <<
",\n";
160 out <<
" \"requires_rom\": " << (metadata.requires_rom ?
"true" :
"false")
162 out <<
" \"requires_grpc\": "
163 << (metadata.requires_grpc ?
"true" :
"false") <<
",\n";
164 if (!metadata.todo_reference.empty()) {
165 out <<
" \"todo_reference\": \""
166 << EscapeJson(metadata.todo_reference) <<
"\",\n";
168 out <<
" \"todo_reference\": \"\",\n";
170 out <<
" \"aliases\": ";
171 AppendStringArray(out, metadata.aliases);
173 out <<
" \"examples\": ";
174 AppendStringArray(out, metadata.examples);
186 return absl::StrFormat(
"Command '%s' not found", name);
189 std::ostringstream help;
190 help <<
"\n\033[1;36m" << metadata->name <<
"\033[0m - "
191 << metadata->description <<
"\n\n";
192 help <<
"\033[1;33mUsage:\033[0m\n";
193 help <<
" " << metadata->usage <<
"\n\n";
195 if (!metadata->examples.empty()) {
196 help <<
"\033[1;33mExamples:\033[0m\n";
197 for (
const auto& example : metadata->examples) {
198 help <<
" " << example <<
"\n";
203 if (metadata->requires_rom) {
204 help <<
"\033[1;33mRequires:\033[0m ROM file (--rom=<path>)\n";
206 if (metadata->requires_grpc) {
207 help <<
"\033[1;33mRequires:\033[0m YAZE running with gRPC enabled\n";
210 if (!metadata->aliases.empty()) {
211 help <<
"\n\033[1;33mAliases:\033[0m "
212 << absl::StrJoin(metadata->aliases,
", ") <<
"\n";
219 const std::string& category)
const {
221 if (commands.empty()) {
222 return absl::StrFormat(
"No commands in category '%s'", category);
225 std::ostringstream help;
226 help <<
"\n\033[1;36m" << category <<
" commands:\033[0m\n\n";
228 for (
const auto& cmd : commands) {
231 help <<
" \033[1;33m" << cmd <<
"\033[0m\n";
232 help <<
" " << metadata->description <<
"\n";
233 if (!metadata->usage.empty()) {
234 help <<
" Usage: " << metadata->usage <<
"\n";
244 std::ostringstream help;
245 help <<
"\n\033[1;36mAll z3ed Commands:\033[0m\n\n";
248 for (
const auto& category : categories) {
256 const std::vector<std::string>& args,
258 std::string* captured_output) {
259 auto* handler =
Get(name);
261 return absl::NotFoundError(absl::StrFormat(
"Command '%s' not found", name));
264 return handler->Run(args, rom_context, captured_output);
268 return Get(name) !=
nullptr;
275 for (
auto& handler : all_handlers) {
276 std::string name = handler->GetName();
279 auto descriptor = handler->Describe();
281 metadata.
name = name;
282 metadata.
usage = handler->GetUsage();
288 if (name.find(
"resource-") == 0) {
290 metadata.
description =
"Resource inspection and search";
291 if (name ==
"resource-list") {
292 metadata.
examples = {
"z3ed resource-list --type=dungeon --format=json",
293 "z3ed resource-list --type=sprite --format=table"};
295 }
else if (name.find(
"dungeon-") == 0) {
297 metadata.
description =
"Dungeon inspection and editing";
298 if (name ==
"dungeon-describe-room") {
300 "z3ed dungeon-describe-room --room=5 --format=json"};
302 }
else if (name.find(
"overworld-") == 0) {
304 metadata.
description =
"Overworld inspection and editing";
305 if (name ==
"overworld-find-tile") {
307 "z3ed overworld-find-tile --tile=0x42 --format=json"};
309 }
else if (name.find(
"emulator-") == 0) {
311 metadata.
description =
"Emulator control and debugging";
313 if (name ==
"emulator-set-breakpoint") {
315 "z3ed emulator-set-breakpoint --address=0x83D7 --description='NMI "
318 }
else if (name.find(
"gui-") == 0) {
322 }
else if (name.find(
"hex-") == 0) {
325 }
else if (name.find(
"palette-") == 0) {
328 }
else if (name.find(
"sprite-") == 0) {
331 }
else if (name.find(
"message-") == 0 || name.find(
"dialogue-") == 0) {
333 metadata.
description = name.find(
"message-") == 0 ?
"Message inspection"
334 :
"Dialogue inspection";
335 }
else if (name.find(
"music-") == 0) {
338 }
else if (name ==
"simple-chat" || name ==
"chat") {
344 "z3ed simple-chat --rom=zelda3.sfc",
345 "z3ed simple-chat \"What dungeons exist?\" --rom=zelda3.sfc"};
346 }
else if (name.find(
"tools-") == 0) {
348 if (name ==
"tools-list") {
349 metadata.
description =
"List available test helper tools";
352 }
else if (name ==
"tools-harness-state") {
353 metadata.
description =
"Generate WRAM state for test harnesses";
355 "z3ed tools-harness-state --rom=zelda3.sfc --output=state.h"};
356 }
else if (name ==
"tools-extract-values") {
357 metadata.
description =
"Extract vanilla ROM values";
358 metadata.
examples = {
"z3ed tools-extract-values --rom=zelda3.sfc"};
359 }
else if (name ==
"tools-extract-golden") {
360 metadata.
description =
"Extract comprehensive golden data for testing";
362 "z3ed tools-extract-golden --rom=zelda3.sfc --output=golden.h"};
363 }
else if (name ==
"tools-patch-v3") {
364 metadata.
description =
"Create v3 ZSCustomOverworld patched ROM";
366 "z3ed tools-patch-v3 --rom=zelda3.sfc --output=patched.sfc"};
376 if (!descriptor.summary.empty() &&
377 descriptor.summary !=
"Command summary not provided.") {
382 if (!descriptor.todo_reference.empty() &&
383 descriptor.todo_reference !=
"todo#unassigned") {
387 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.
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)