yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
cli.cc
Go to the documentation of this file.
1#include "cli/cli.h"
2
3#include <ostream>
4#include <utility>
5
6#include "absl/status/statusor.h"
7#include "absl/strings/str_cat.h"
10#include "cli/z3ed_ascii_logo.h"
11
12namespace yaze::cli {
13namespace {
14
15// Word groups that expand to registered commands, e.g. `rom read` runs
16// `rom-read`.
17struct AliasGroup {
18 absl::string_view name;
19 absl::string_view title;
20 absl::StatusOr<CommandInvocation> (*resolve)(absl::Span<const std::string>);
21 std::string (*usage)();
22};
23
24constexpr AliasGroup kAliasGroups[] = {
25 {"rom", "ROM subcommands", &ResolveRomSubcommand, &RomSubcommandUsage},
26 {"debug", "Debug subcommands", &ResolveDebugSubcommand,
28};
29
30const AliasGroup* FindAliasGroup(absl::string_view name) {
31 for (const AliasGroup& group : kAliasGroups) {
32 if (group.name == name) {
33 return &group;
34 }
35 }
36 return nullptr;
37}
38
39bool IsHelpFlag(absl::string_view token) {
40 return token == "--help" || token == "-h";
41}
42
43constexpr absl::string_view kUsageText =
44 " z3ed [command] [flags]\n"
45 " z3ed help <command|category> # Scoped help\n"
46 " z3ed --export-schemas # JSON schemas for agents\n"
47 " z3ed --version # Show version\n";
48
49constexpr absl::string_view kCommonFlagsText =
50 " --rom=<path> Path to ROM file\n"
51 " --sandbox Run ROM commands in a sandbox copy\n"
52 " --quiet, -q Suppress output\n"
53 " --ai_provider=<name> AI provider (auto, ollama, gemini, openai,\n"
54 " anthropic, mock)\n"
55 " --ai_model=<name> Provider-specific model override\n"
56 " --version Show version\n"
57 " --self-test Run self-test diagnostics\n"
58 " --help <scope> Show help for command or category\n"
59 " --export-schemas Export command schemas as JSON\n";
60
61constexpr absl::string_view kHttpFlagsText =
62 " --http-port=<port> HTTP API server port (0=disabled)\n"
63 " (no command keeps server alive until Ctrl+C)\n"
64 " --http-host=<host> HTTP API bind address (default localhost)\n"
65 " --http-allow-commands Serve /api/v1/command/execute (runs any "
66 "command)\n";
67
68constexpr absl::string_view kExamplesText =
69 " z3ed rom-info --rom=zelda3.sfc\n"
70 " z3ed rom read --address=0x1000 --length=16 --rom=zelda3.sfc\n"
71 " z3ed debug state\n"
72 " z3ed message-search --rom=zelda3.sfc --query=\"Master Sword\"\n"
73 " z3ed dungeon-export --rom=zelda3.sfc --id=1\n";
74
75} // namespace
76
77Cli::Cli(CommandRegistry& registry, Options options, std::ostream& out)
78 : registry_(registry), options_(std::move(options)), out_(out) {}
79
80absl::Status Cli::Run(absl::Span<const std::string> args) const {
81 if (args.empty()) {
83 return absl::OkStatus();
84 }
85
86 const std::string& command = args[0];
87 const absl::Span<const std::string> rest = args.subspan(1);
88
89 if (command == "help") {
90 if (rest.empty()) {
92 return absl::OkStatus();
93 }
94 return PrintScopedHelp(rest);
95 }
96 if (command == "agent") {
97 return RunAgent(rest);
98 }
99 if (FindAliasGroup(command) != nullptr) {
100 return RunAlias(command, rest);
101 }
102 if (registry_.HasCommand(command)) {
103 return registry_.Execute(
104 command, std::vector<std::string>(rest.begin(), rest.end()), nullptr);
105 }
106 if (!registry_.GetCommandsInCategory(command).empty()) {
107 PrintCategoryHelp(command);
108 return absl::OkStatus();
109 }
110 return absl::NotFoundError(absl::StrCat("Unknown command: ", command));
111}
112
113absl::Status Cli::RunAgent(absl::Span<const std::string> args) const {
114 if (args.empty() || IsHelpFlag(args[0])) {
116 return absl::OkStatus();
117 }
118 if (!options_.agent_handler) {
119 return absl::FailedPreconditionError("Agent CLI is disabled in this build");
120 }
121 return options_.agent_handler(
122 std::vector<std::string>(args.begin(), args.end()));
123}
124
125absl::Status Cli::RunAlias(absl::string_view group_name,
126 absl::Span<const std::string> args) const {
127 if (args.empty() || IsHelpFlag(args[0])) {
128 PrintAliasHelp(group_name);
129 return absl::OkStatus();
130 }
131
132 const AliasGroup& group = *FindAliasGroup(group_name);
133 absl::StatusOr<CommandInvocation> invocation = group.resolve(args);
134 if (!invocation.ok()) {
135 if (absl::IsInvalidArgument(invocation.status())) {
136 PrintAliasHelp(group_name);
137 }
138 return invocation.status();
139 }
140 if (!registry_.HasCommand(invocation->command)) {
141 return absl::NotFoundError(
142 absl::StrCat("Command not found for ", group.name,
143 " subcommand: ", invocation->command));
144 }
145 return registry_.Execute(invocation->command, invocation->args, nullptr);
146}
147
148void Cli::PrintOverview() const {
149 const util::AnsiStyle& style = options_.style;
150
151 out_ << RenderLogo(style) << "\n";
152 out_ << " " << style.Emphasis()
153 << "Yet Another Zelda3 Editor - AI-Powered CLI" << style.Reset()
154 << "\n\n";
155
156 out_ << style.Heading() << "USAGE:" << style.Reset() << "\n"
157 << kUsageText << "\n";
158
159 out_ << style.Heading() << "CATEGORIES:" << style.Reset() << "\n";
160 for (const std::string& category : registry_.GetCategories()) {
161 out_ << " " << style.Highlight() << category << style.Reset() << " ("
162 << registry_.GetCommandsInCategory(category).size() << " commands)\n";
163 }
164 out_ << "\n";
165
166 out_ << style.Heading() << "COMMON FLAGS:" << style.Reset() << "\n"
167 << kCommonFlagsText;
169 out_ << kHttpFlagsText;
170 }
171 out_ << " --mesen-socket=<path> Override Mesen2 socket path\n\n";
172
173 out_ << style.Heading() << "EXAMPLES:" << style.Reset() << "\n";
175 out_ << " z3ed agent simple-chat --rom=zelda3.sfc\n";
176 }
177 out_ << kExamplesText << "\n";
178
179 out_ << "For detailed help: z3ed help <command>\n";
180 out_ << "For all commands: z3ed --list-commands\n\n";
181}
182
183absl::Status Cli::PrintScopedHelp(absl::Span<const std::string> words) const {
184 if (words.empty()) {
186 return absl::OkStatus();
187 }
188
189 const util::AnsiStyle& style = options_.style;
190 const std::string& target = words[0];
191 if (target == "all") {
192 out_ << registry_.GenerateCompleteHelp(style) << "\n";
193 return absl::OkStatus();
194 }
195 if (registry_.HasCommand(target)) {
196 out_ << registry_.GenerateHelp(target, style) << "\n";
197 return absl::OkStatus();
198 }
199 if (target == "agent") {
200 if (words.size() > 1 && registry_.HasCommand(words[1])) {
201 out_ << registry_.GenerateHelp(words[1], style) << "\n";
202 } else {
204 }
205 return absl::OkStatus();
206 }
207 if (const AliasGroup* group = FindAliasGroup(target)) {
208 if (words.size() > 1) {
209 absl::StatusOr<CommandInvocation> invocation =
210 group->resolve(words.subspan(1));
211 if (invocation.ok() && registry_.HasCommand(invocation->command)) {
212 out_ << registry_.GenerateHelp(invocation->command, style) << "\n";
213 return absl::OkStatus();
214 }
215 }
216 PrintAliasHelp(target);
217 return absl::OkStatus();
218 }
219 if (!registry_.GetCommandsInCategory(target).empty()) {
220 PrintCategoryHelp(target);
221 return absl::OkStatus();
222 }
223 return absl::NotFoundError(
224 absl::StrCat("Unknown command or category '", target,
225 "'. Use --list-commands for a full command list."));
226}
227
229 const util::AnsiStyle& style = options_.style;
230 out_ << "\n" << style.Heading() << "Agent command:" << style.Reset() << "\n";
231 if (!options_.agent_handler) {
232 out_ << " This build was compiled without agent CLI support.\n"
233 << " Rebuild with `YAZE_ENABLE_AGENT_CLI` to enable `z3ed agent`.\n";
234 return;
235 }
236 out_ << " z3ed agent <subcommand> [flags]\n\n";
237 if (!registry_.GetCommandsInCategory("agent").empty()) {
238 PrintCategoryHelp("agent");
239 }
240 out_ << "Use `z3ed help agent <subcommand>` for scoped details.\n";
241}
242
243void Cli::PrintAliasHelp(absl::string_view group_name) const {
244 const AliasGroup& group = *FindAliasGroup(group_name);
245 const util::AnsiStyle& style = options_.style;
246 const std::string category(group.name);
247
248 out_ << "\n"
249 << style.Heading() << group.title << ":" << style.Reset() << "\n"
250 << group.usage() << "\n";
251 if (!registry_.GetCommandsInCategory(category).empty()) {
252 out_ << "Equivalent direct commands:\n"
253 << registry_.GenerateCategoryHelp(category, style) << "\n";
254 }
255 out_ << "Use `z3ed help " << group.name
256 << " <subcommand>` for command-specific flags.\n";
257}
258
259void Cli::PrintCategoryHelp(const std::string& category) const {
260 const std::vector<std::string> commands =
262
263 out_ << "Category: " << category << "\n\n";
264 if (commands.empty()) {
265 out_ << " No commands in this category.\n";
266 return;
267 }
268
269 for (const std::string& name : commands) {
270 const CommandRegistry::CommandMetadata* metadata =
272 if (metadata == nullptr) {
273 continue;
274 }
275
276 out_ << " " << name << " - " << metadata->description << "\n";
277 if (!metadata->usage.empty()) {
278 out_ << " Usage: " << metadata->usage << "\n";
279 }
280
281 std::string requirements;
282 if (metadata->requires_rom) {
283 requirements += "ROM ";
284 }
285 if (metadata->requires_grpc) {
286 requirements += "gRPC ";
287 }
288 if (!requirements.empty()) {
289 out_ << " Requires: " << requirements << "\n";
290 }
291 out_ << "\n";
292 }
293}
294
296 const std::vector<std::string> categories = registry_.GetCategories();
297
298 out_ << "Z3ED Command Summary\n\n";
299 for (const std::string& category : categories) {
300 for (const std::string& name : registry_.GetCommandsInCategory(category)) {
301 if (const auto* metadata = registry_.GetMetadata(name)) {
302 out_ << " " << name << " [" << metadata->category << "] - "
303 << metadata->description << "\n";
304 }
305 }
306 }
307 out_ << "\nTotal: " << registry_.Count() << " commands across "
308 << categories.size() << " categories\n";
309}
310
311} // namespace yaze::cli
std::ostream & out_
Definition cli.h:54
void PrintCategoryHelp(const std::string &category) const
Definition cli.cc:259
absl::Status PrintScopedHelp(absl::Span< const std::string > words) const
Definition cli.cc:183
absl::Status Run(absl::Span< const std::string > args) const
Definition cli.cc:80
void PrintAgentHelp() const
Definition cli.cc:228
Options options_
Definition cli.h:53
CommandRegistry & registry_
Definition cli.h:52
void PrintCommandSummary() const
Definition cli.cc:295
void PrintOverview() const
Definition cli.cc:148
absl::Status RunAlias(absl::string_view group, absl::Span< const std::string > args) const
Definition cli.cc:125
absl::Status RunAgent(absl::Span< const std::string > args) const
Definition cli.cc:113
Cli(CommandRegistry &registry, Options options, std::ostream &out)
Definition cli.cc:77
void PrintAliasHelp(absl::string_view group) const
Definition cli.cc:243
Single source of truth for all z3ed commands.
std::string GenerateCompleteHelp(const util::AnsiStyle &style=util::AnsiStyle(false)) const
Generate complete help text (all commands)
std::string GenerateCategoryHelp(const std::string &category, const util::AnsiStyle &style=util::AnsiStyle(false)) const
Generate category help text.
std::vector< std::string > GetCommandsInCategory(const std::string &category) const
Get all commands in a category.
const CommandMetadata * GetMetadata(const std::string &name) const
Get command metadata.
std::string GenerateHelp(const std::string &name, const util::AnsiStyle &style=util::AnsiStyle(false)) const
Generate help text for a command.
size_t Count() const
Get total command count.
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.
bool HasCommand(const std::string &name) const
Check if command exists.
std::vector< std::string > GetCategories() const
Get all categories.
constexpr absl::string_view Emphasis() const
Definition ansi_style.h:24
constexpr absl::string_view Heading() const
Definition ansi_style.h:22
constexpr absl::string_view Highlight() const
Definition ansi_style.h:23
constexpr absl::string_view Reset() const
Definition ansi_style.h:28
const AliasGroup * FindAliasGroup(absl::string_view name)
Definition cli.cc:30
bool IsHelpFlag(absl::string_view token)
Definition cli.cc:39
constexpr absl::string_view kUsageText
Definition cli.cc:43
constexpr absl::string_view kCommonFlagsText
Definition cli.cc:49
constexpr AliasGroup kAliasGroups[]
Definition cli.cc:24
constexpr absl::string_view kHttpFlagsText
Definition cli.cc:61
constexpr absl::string_view kExamplesText
Definition cli.cc:68
absl::StatusOr< CommandInvocation > ResolveRomSubcommand(absl::Span< const std::string > args)
std::string RomSubcommandUsage()
absl::StatusOr< CommandInvocation > ResolveDebugSubcommand(absl::Span< const std::string > args)
std::string RenderLogo(const util::AnsiStyle &style)
std::string DebugSubcommandUsage()
bool http_api_available
Definition cli.h:28
util::AnsiStyle style
Definition cli.h:29
AgentHandler agent_handler
Definition cli.h:27