feat: Enhance chat command with multiple output formats and improve help documentation

- Updated the chat command to support new output formats: text, markdown, json, and compact.
- Modified the agent configuration to include output format settings.
- Enhanced the command line interface to handle new format options and provide detailed usage instructions.
- Improved the message printing logic in SimpleChatSession to format output based on the selected format.
- Added JSON and Markdown formatting for session metrics and messages.
- Updated help documentation to reflect changes in command usage and available options.
This commit is contained in:
scawful
2025-10-04 13:33:19 -04:00
parent 0db71a71fe
commit 6990e565b8
8 changed files with 736 additions and 75 deletions

View File

@@ -1,9 +1,12 @@
#include "cli/modern_cli.h"
#include <iostream>
#include <optional>
#include "absl/flags/flag.h"
#include "absl/flags/declare.h"
#include "absl/strings/ascii.h"
#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_join.h"
@@ -13,6 +16,7 @@
#include "cli/z3ed_ascii_logo.h"
ABSL_DECLARE_FLAG(std::string, rom);
ABSL_DECLARE_FLAG(bool, quiet);
namespace yaze {
namespace cli {
@@ -114,6 +118,48 @@ void ModernCLI::SetupCommands() {
}
};
commands_["chat"] = {
.name = "chat",
.description =
"Unified chat entrypoint with text, markdown, or JSON output",
.usage =
"z3ed chat [--mode=simple|gui|test] [--format=text|markdown|json|compact]"
" [--prompt \"<message>\"] [--file=<questions.txt>] [--quiet]",
.handler = [this](const std::vector<std::string>& args) -> absl::Status {
return HandleChatEntryCommand(args);
}
};
commands_["proposal"] = {
.name = "proposal",
.description = "Review and manage AI-generated change proposals",
.usage =
"z3ed proposal <run|list|diff|show|accept|commit|revert> [options]",
.handler = [this](const std::vector<std::string>& args) -> absl::Status {
return HandleProposalCommand(args);
}
};
commands_["widget"] = {
.name = "widget",
.description = "Discover GUI widgets exposed through automation APIs",
.usage = "z3ed widget discover [--window=<name>] [--type=<widget>]",
.handler = [this](const std::vector<std::string>& args) -> absl::Status {
return HandleWidgetCommand(args);
}
};
commands_["widget discover"] = {
.name = "widget discover",
.description = "Inspect UI widgets using the automation service",
.usage =
"z3ed widget discover [--window=<name>] [--type=<widget>]"
" [--format=text|json]",
.handler = [this](const std::vector<std::string>& args) -> absl::Status {
return HandleWidgetCommand(args);
}
};
commands_["project build"] = {
.name = "project build",
.description = "Build the project and create a new ROM file",
@@ -305,11 +351,25 @@ void ModernCLI::ShowHelp() {
std::cout << "\033[1m\033[36mCOMMANDS:\033[0m" << std::endl;
std::cout << std::endl;
std::cout << " \033[1m🤖 AI Agent\033[0m" << std::endl;
std::cout << " agent simple-chat Natural language ROM queries" << std::endl;
std::cout << " agent test-conversation Interactive testing mode" << std::endl;
std::cout << " \033[90m→ z3ed help agent\033[0m" << std::endl;
std::cout << " \033[1m🤖 AI Agent\033[0m" << std::endl;
std::cout << " chat Unified chat entrypoint (text/json/markdown)" << std::endl;
std::cout << " agent simple-chat Natural language ROM queries" << std::endl;
std::cout << " agent test-conversation Interactive testing mode" << std::endl;
std::cout << " \033[90m→ z3ed help chat, z3ed help agent\033[0m" << std::endl;
std::cout << std::endl;
std::cout << " \033[1m🧠 Proposals\033[0m" << std::endl;
std::cout << " proposal run Execute AI-driven sandbox plan" << std::endl;
std::cout << " proposal list Show pending proposals" << std::endl;
std::cout << " proposal diff Review latest sandbox diff" << std::endl;
std::cout << " proposal accept Apply sandbox changes" << std::endl;
std::cout << " \033[90m→ z3ed help proposal\033[0m" << std::endl;
std::cout << std::endl;
std::cout << " \033[1m🪟 GUI Automation\033[0m" << std::endl;
std::cout << " widget discover Inspect GUI widgets via automation" << std::endl;
std::cout << " \033[90m→ z3ed help widget\033[0m" << std::endl;
std::cout << std::endl;
std::cout << " \033[1m🔧 ROM Patching\033[0m" << std::endl;
std::cout << " patch apply-asar Apply Asar 65816 assembly patch" << std::endl;
@@ -350,7 +410,7 @@ void ModernCLI::ShowHelp() {
std::cout << "\033[1m\033[36mQUICK START:\033[0m" << std::endl;
std::cout << " z3ed --tui" << std::endl;
std::cout << " z3ed agent simple-chat \"What is room 5?\" --rom=zelda3.sfc" << std::endl;
std::cout << " z3ed chat \"What is room 5?\" --rom=zelda3.sfc --format=markdown" << std::endl;
std::cout << " z3ed patch apply-asar patch.asm --rom=zelda3.sfc" << std::endl;
std::cout << std::endl;
@@ -361,6 +421,14 @@ void ModernCLI::PrintTopLevelHelp() const {
const_cast<ModernCLI*>(this)->ShowHelp();
}
void ModernCLI::PrintCategoryHelp(const std::string& category) const {
const_cast<ModernCLI*>(this)->ShowCategoryHelp(category);
}
void ModernCLI::PrintCommandSummary() const {
const_cast<ModernCLI*>(this)->ShowCommandSummary();
}
void ModernCLI::ShowCategoryHelp(const std::string& category) {
std::cout << GetColoredLogo() << std::endl;
std::cout << std::endl;
@@ -395,6 +463,54 @@ void ModernCLI::ShowCategoryHelp(const std::string& category) {
std::cout << " • Use --ai_provider=gemini or --ai_provider=ollama" << std::endl;
std::cout << std::endl;
} else if (category == "chat") {
std::cout << "\033[1m\033[36m💬 CHAT ENTRYPOINT\033[0m" << std::endl;
std::cout << std::endl;
std::cout << "\033[1mDESCRIPTION:\033[0m" << std::endl;
std::cout << " Launch the embedded agent in text, markdown, or JSON-friendly modes." << std::endl;
std::cout << std::endl;
std::cout << "\033[1mMODES:\033[0m" << std::endl;
std::cout << " chat --mode=simple Quick REPL with --format=text|markdown|json|compact" << std::endl;
std::cout << " chat --mode=batch --file=F Run prompts from file (one per line)" << std::endl;
std::cout << " chat --mode=gui Launch the full FTXUI conversation experience" << std::endl;
std::cout << " chat --mode=test Execute scripted agent conversation for QA" << std::endl;
std::cout << std::endl;
std::cout << "\033[1mOPTIONS:\033[0m" << std::endl;
std::cout << " --format=text|markdown|json|compact Control response formatting" << std::endl;
std::cout << " --prompt \"<msg>\" Send a single message and exit" << std::endl;
std::cout << " --file questions.txt Batch mode input" << std::endl;
std::cout << " --quiet Suppress extra banners" << std::endl;
std::cout << std::endl;
} else if (category == "proposal") {
std::cout << "\033[1m\033[36m🧠 PROPOSAL WORKFLOWS\033[0m" << std::endl;
std::cout << std::endl;
std::cout << "\033[1mCOMMANDS:\033[0m" << std::endl;
std::cout << " proposal run --prompt \"<desc>\" Plan and execute changes in sandbox" << std::endl;
std::cout << " proposal list Show pending proposals" << std::endl;
std::cout << " proposal diff [--proposal-id=X] Inspect latest diff/log" << std::endl;
std::cout << " proposal accept --proposal-id=X Apply sandbox changes to main ROM" << std::endl;
std::cout << " proposal commit | proposal revert Persist or undo sandbox changes" << std::endl;
std::cout << std::endl;
std::cout << "\033[1mTIPS:\033[0m" << std::endl;
std::cout << " • Run `z3ed proposal list` frequently to monitor progress" << std::endl;
std::cout << " • Use `--prompt` to describe tasks in natural language" << std::endl;
std::cout << " • Sandbox artifacts live alongside proposal logs" << std::endl;
std::cout << std::endl;
} else if (category == "widget") {
std::cout << "\033[1m\033[36m🪟 GUI WIDGET DISCOVERY\033[0m" << std::endl;
std::cout << std::endl;
std::cout << "\033[1mCOMMANDS:\033[0m" << std::endl;
std::cout << " widget discover [--window=<name>] [--type=<widget>]" << std::endl;
std::cout << " Enumerate UI widgets available through automation hooks" << std::endl;
std::cout << " Options: --format=table|json, --limit <n>, --include-invisible, --include-disabled" << std::endl;
std::cout << std::endl;
std::cout << "\033[1mTIPS:\033[0m" << std::endl;
std::cout << " • Requires the YAZE GUI to be running locally" << std::endl;
std::cout << " • Combine with `z3ed proposal run` for automated UI tests" << std::endl;
std::cout << std::endl;
} else if (category == "patch") {
std::cout << "\033[1m\033[36m🔧 ROM PATCHING COMMANDS\033[0m" << std::endl;
std::cout << std::endl;
@@ -520,12 +636,44 @@ void ModernCLI::ShowCategoryHelp(const std::string& category) {
} else {
std::cout << "\033[1m\033[31mUnknown category: " << category << "\033[0m" << std::endl;
std::cout << std::endl;
std::cout << "Available categories: agent, patch, rom, overworld, dungeon, gfx, palette" << std::endl;
std::cout << "Available categories: agent, chat, proposal, widget, patch, rom, overworld, dungeon, gfx, palette" << std::endl;
std::cout << std::endl;
std::cout << "Use 'z3ed --help' to see all commands." << std::endl;
}
}
void ModernCLI::ShowCommandSummary() const {
std::cout << GetColoredLogo() << std::endl;
std::cout << std::endl;
std::cout << "\033[1m\033[36mCOMMANDS OVERVIEW\033[0m" << std::endl;
std::cout << std::endl;
for (const auto& [key, info] : commands_) {
std::string headline = info.description;
const size_t newline_pos = headline.find('\n');
if (newline_pos != std::string::npos) {
headline = headline.substr(0, newline_pos);
}
if (headline.empty()) {
headline = info.usage;
}
if (headline.size() > 80) {
headline = absl::StrCat(headline.substr(0, 77), "");
}
std::string label = info.name;
if (label.size() > 24) {
label = absl::StrCat(label.substr(0, 23), "");
}
std::cout << " "
<< absl::StrFormat("%-24s%s", label, headline) << std::endl;
}
std::cout << std::endl;
std::cout << "Use \033[90mz3ed help <topic>\033[0m for detailed information." << std::endl;
}
absl::Status ModernCLI::Run(int argc, char* argv[]) {
if (argc < 2) {
ShowHelp();
@@ -737,8 +885,143 @@ absl::Status ModernCLI::HandleOverworldSetTileCommand(const std::vector<std::str
}
absl::Status ModernCLI::HandleSpriteCreateCommand(const std::vector<std::string>& args) {
SpriteCreate handler;
return handler.Run(args);
SpriteCreate handler;
return handler.Run(args);
}
absl::Status ModernCLI::HandleChatEntryCommand(
const std::vector<std::string>& args) {
std::string mode = "simple";
std::optional<std::string> prompt;
std::vector<std::string> forwarded;
for (size_t i = 0; i < args.size(); ++i) {
const std::string& token = args[i];
if (absl::StartsWith(token, "--mode=")) {
mode = absl::AsciiStrToLower(token.substr(7));
continue;
}
if (token == "--mode" && i + 1 < args.size()) {
mode = absl::AsciiStrToLower(args[i + 1]);
++i;
continue;
}
if (absl::StartsWith(token, "--prompt=")) {
prompt = token.substr(9);
continue;
}
if (token == "--prompt" && i + 1 < args.size()) {
prompt = args[i + 1];
++i;
continue;
}
if (token == "--quiet" || token == "-q") {
absl::SetFlag(&FLAGS_quiet, true);
continue;
}
if (!absl::StartsWith(token, "--") && !prompt.has_value()) {
prompt = token;
continue;
}
forwarded.push_back(token);
}
const std::string normalized_mode = absl::AsciiStrToLower(mode);
auto has_batch_file = [&forwarded]() {
for (const auto& token : forwarded) {
if (absl::StartsWith(token, "--file") || token == "--file") {
return true;
}
}
return false;
};
std::vector<std::string> agent_args;
if (normalized_mode == "gui" || normalized_mode == "visual" ||
normalized_mode == "tui") {
if (prompt.has_value()) {
return absl::InvalidArgumentError(
"GUI chat mode launches the interactive TUI and does not accept a --prompt value.");
}
agent_args.push_back("chat");
} else if (normalized_mode == "test" || normalized_mode == "qa") {
if (prompt.has_value()) {
return absl::InvalidArgumentError(
"Test conversation mode does not accept an inline prompt.");
}
agent_args.push_back("test-conversation");
} else {
if (normalized_mode == "batch" && !has_batch_file()) {
return absl::InvalidArgumentError(
"Batch chat mode requires a --file=<path> option.");
}
agent_args.push_back("simple-chat");
if (prompt.has_value()) {
agent_args.push_back(*prompt);
}
}
agent_args.insert(agent_args.end(), forwarded.begin(), forwarded.end());
return HandleAgentCommand(agent_args);
}
absl::Status ModernCLI::HandleProposalCommand(
const std::vector<std::string>& args) {
if (args.empty()) {
ShowCategoryHelp("proposal");
return absl::OkStatus();
}
std::string subcommand = absl::AsciiStrToLower(args[0]);
std::vector<std::string> forwarded(args.begin() + 1, args.end());
std::vector<std::string> agent_args;
if (subcommand == "run" || subcommand == "plan") {
agent_args.push_back(subcommand);
} else if (subcommand == "list") {
agent_args.push_back("list");
} else if (subcommand == "diff" || subcommand == "show") {
agent_args.push_back("diff");
} else if (subcommand == "accept") {
agent_args.push_back("accept");
} else if (subcommand == "commit") {
agent_args.push_back("commit");
} else if (subcommand == "revert" || subcommand == "reject") {
agent_args.push_back("revert");
} else if (subcommand == "test") {
agent_args.push_back("test");
} else {
return absl::InvalidArgumentError(
absl::StrCat("Unknown proposal command: ", subcommand,
". Valid actions: run, plan, list, diff, show, accept, commit, revert."));
}
agent_args.insert(agent_args.end(), forwarded.begin(), forwarded.end());
return HandleAgentCommand(agent_args);
}
absl::Status ModernCLI::HandleWidgetCommand(
const std::vector<std::string>& args) {
if (args.empty()) {
ShowCategoryHelp("widget");
return absl::OkStatus();
}
std::vector<std::string> forwarded(args.begin(), args.end());
std::string subcommand = absl::AsciiStrToLower(forwarded[0]);
std::vector<std::string> agent_args;
if (subcommand == "discover") {
agent_args.push_back("gui");
agent_args.insert(agent_args.end(), forwarded.begin(), forwarded.end());
} else {
return absl::InvalidArgumentError(
absl::StrCat("Unknown widget command: ", forwarded[0],
". Try 'z3ed widget discover'."));
}
return HandleAgentCommand(agent_args);
}
} // namespace cli