feat: Add SSL/HTTPS support and enhance test command handling
- Introduced SSL/HTTPS support in `z3ed.cmake` for secure communication, required for Gemini API and future collaborative features. - Added options for OpenSSL integration and macOS Keychain support. - Expanded `test_commands.cc` with new command handlers for running, replaying, and managing tests, improving modularity and usability. - Updated command usage messages for clarity and consistency. This commit enhances the security and functionality of the CLI, paving the way for more robust features in future iterations.
This commit is contained in:
@@ -6,22 +6,334 @@
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "cli/handlers/agent/common.h"
|
||||
|
||||
#ifdef YAZE_WITH_GRPC
|
||||
#include "cli/service/gui_automation_client.h"
|
||||
#include "cli/service/test_workflow_generator.h"
|
||||
#endif
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
namespace agent {
|
||||
|
||||
#ifdef YAZE_WITH_GRPC
|
||||
|
||||
// Forward declarations for subcommand handlers
|
||||
absl::Status HandleTestRunCommand(const std::vector<std::string>& args);
|
||||
absl::Status HandleTestReplayCommand(const std::vector<std::string>& args);
|
||||
absl::Status HandleTestStatusCommand(const std::vector<std::string>& args);
|
||||
absl::Status HandleTestListCommand(const std::vector<std::string>& args);
|
||||
absl::Status HandleTestResultsCommand(const std::vector<std::string>& args);
|
||||
absl::Status HandleTestRecordCommand(const std::vector<std::string>& args);
|
||||
|
||||
absl::Status HandleTestRunCommand(const std::vector<std::string>& args) {
|
||||
if (args.empty() || args[0] != "--prompt") {
|
||||
return absl::InvalidArgumentError(
|
||||
"Usage: agent test run --prompt <description> [--host <host>] [--port <port>]\n"
|
||||
"Example: agent test run --prompt \"Open the overworld editor and verify it loads\"");
|
||||
}
|
||||
|
||||
std::string prompt = args.size() > 1 ? args[1] : "";
|
||||
std::string host = "localhost";
|
||||
int port = 50052;
|
||||
|
||||
// Parse additional arguments
|
||||
for (size_t i = 2; i < args.size(); ++i) {
|
||||
if (args[i] == "--host" && i + 1 < args.size()) {
|
||||
host = args[++i];
|
||||
} else if (args[i] == "--port" && i + 1 < args.size()) {
|
||||
port = std::stoi(args[++i]);
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "\n=== GUI Automation Test ===\n";
|
||||
std::cout << "Prompt: " << prompt << "\n";
|
||||
std::cout << "Server: " << host << ":" << port << "\n\n";
|
||||
|
||||
// Generate workflow from natural language
|
||||
TestWorkflowGenerator generator;
|
||||
auto workflow_or = generator.GenerateWorkflow(prompt);
|
||||
if (!workflow_or.ok()) {
|
||||
std::cerr << "Failed to generate workflow: " << workflow_or.status().message() << std::endl;
|
||||
return workflow_or.status();
|
||||
}
|
||||
|
||||
auto workflow = workflow_or.value();
|
||||
std::cout << "Generated " << workflow.steps.size() << " test steps\n\n";
|
||||
|
||||
// Connect and execute
|
||||
GuiAutomationClient client(absl::StrCat(host, ":", port));
|
||||
auto status = client.Connect();
|
||||
if (!status.ok()) {
|
||||
std::cerr << "Failed to connect to test harness: " << status.message() << std::endl;
|
||||
return status;
|
||||
}
|
||||
|
||||
// Execute each step
|
||||
for (size_t i = 0; i < workflow.steps.size(); ++i) {
|
||||
const auto& step = workflow.steps[i];
|
||||
std::cout << "Step " << (i + 1) << ": " << step.ToString() << "... ";
|
||||
|
||||
// Execute based on step type
|
||||
absl::StatusOr<AutomationResult> result(absl::InternalError("Unknown step type"));
|
||||
|
||||
switch (step.type) {
|
||||
case TestStepType::kClick:
|
||||
result = client.Click(step.target);
|
||||
break;
|
||||
case TestStepType::kType:
|
||||
result = client.Type(step.target, step.text, step.clear_first);
|
||||
break;
|
||||
case TestStepType::kWait:
|
||||
result = client.Wait(step.condition, step.timeout_ms);
|
||||
break;
|
||||
case TestStepType::kAssert:
|
||||
result = client.Assert(step.condition);
|
||||
break;
|
||||
default:
|
||||
std::cout << "✗ SKIPPED (unknown type)\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!result.ok()) {
|
||||
std::cout << "✗ FAILED\n";
|
||||
std::cerr << " Error: " << result.status().message() << "\n";
|
||||
return result.status();
|
||||
}
|
||||
|
||||
if (!result.value().success) {
|
||||
std::cout << "✗ FAILED\n";
|
||||
std::cerr << " Error: " << result.value().message << "\n";
|
||||
return absl::InternalError(result.value().message);
|
||||
}
|
||||
|
||||
std::cout << "✓\n";
|
||||
}
|
||||
|
||||
std::cout << "\n✅ Test passed!\n";
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status HandleTestReplayCommand(const std::vector<std::string>& args) {
|
||||
if (args.empty()) {
|
||||
return absl::InvalidArgumentError(
|
||||
"Usage: agent test replay <script.json> [--host <host>] [--port <port>]\n"
|
||||
"Example: agent test replay tests/overworld_load.json");
|
||||
}
|
||||
|
||||
std::string script_path = args[0];
|
||||
std::string host = "localhost";
|
||||
int port = 50052;
|
||||
|
||||
for (size_t i = 1; i < args.size(); ++i) {
|
||||
if (args[i] == "--host" && i + 1 < args.size()) {
|
||||
host = args[++i];
|
||||
} else if (args[i] == "--port" && i + 1 < args.size()) {
|
||||
port = std::stoi(args[++i]);
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "\n=== Replay Test ===\n";
|
||||
std::cout << "Script: " << script_path << "\n";
|
||||
std::cout << "Server: " << host << ":" << port << "\n\n";
|
||||
|
||||
GuiAutomationClient client(absl::StrCat(host, ":", port));
|
||||
auto status = client.Connect();
|
||||
if (!status.ok()) {
|
||||
std::cerr << "Failed to connect: " << status.message() << std::endl;
|
||||
return status;
|
||||
}
|
||||
|
||||
auto result = client.ReplayTest(script_path, false, {});
|
||||
if (!result.ok()) {
|
||||
std::cerr << "Replay failed: " << result.status().message() << std::endl;
|
||||
return result.status();
|
||||
}
|
||||
|
||||
if (result.value().success) {
|
||||
std::cout << "✅ Replay succeeded\n";
|
||||
std::cout << "Steps executed: " << result.value().steps_executed << "\n";
|
||||
} else {
|
||||
std::cout << "❌ Replay failed: " << result.value().message << "\n";
|
||||
return absl::InternalError(result.value().message);
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status HandleTestStatusCommand(const std::vector<std::string>& args) {
|
||||
std::string test_id;
|
||||
std::string host = "localhost";
|
||||
int port = 50052;
|
||||
|
||||
for (size_t i = 0; i < args.size(); ++i) {
|
||||
if (args[i] == "--test-id" && i + 1 < args.size()) {
|
||||
test_id = args[++i];
|
||||
} else if (args[i] == "--host" && i + 1 < args.size()) {
|
||||
host = args[++i];
|
||||
} else if (args[i] == "--port" && i + 1 < args.size()) {
|
||||
port = std::stoi(args[++i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (test_id.empty()) {
|
||||
return absl::InvalidArgumentError(
|
||||
"Usage: agent test status --test-id <id> [--host <host>] [--port <port>]");
|
||||
}
|
||||
|
||||
GuiAutomationClient client(absl::StrCat(host, ":", port));
|
||||
auto status = client.Connect();
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
auto details = client.GetTestStatus(test_id);
|
||||
if (!details.ok()) {
|
||||
return details.status();
|
||||
}
|
||||
|
||||
std::cout << "\n=== Test Status ===\n";
|
||||
std::cout << "Test ID: " << test_id << "\n";
|
||||
std::cout << "Status: " << TestRunStatusToString(details.value().status) << "\n";
|
||||
std::cout << "Started: " << FormatOptionalTime(details.value().started_at) << "\n";
|
||||
std::cout << "Completed: " << FormatOptionalTime(details.value().completed_at) << "\n";
|
||||
|
||||
if (!details.value().error_message.empty()) {
|
||||
std::cout << "Error: " << details.value().error_message << "\n";
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status HandleTestListCommand(const std::vector<std::string>& args) {
|
||||
std::string host = "localhost";
|
||||
int port = 50052;
|
||||
|
||||
for (size_t i = 0; i < args.size(); ++i) {
|
||||
if (args[i] == "--host" && i + 1 < args.size()) {
|
||||
host = args[++i];
|
||||
} else if (args[i] == "--port" && i + 1 < args.size()) {
|
||||
port = std::stoi(args[++i]);
|
||||
}
|
||||
}
|
||||
|
||||
GuiAutomationClient client(absl::StrCat(host, ":", port));
|
||||
auto status = client.Connect();
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
auto batch = client.ListTests("", 100, "");
|
||||
if (!batch.ok()) {
|
||||
return batch.status();
|
||||
}
|
||||
|
||||
std::cout << "\n=== Available Tests ===\n";
|
||||
std::cout << "Total: " << batch.value().total_count << "\n\n";
|
||||
|
||||
for (const auto& test : batch.value().tests) {
|
||||
std::cout << "• " << test.name << "\n";
|
||||
std::cout << " ID: " << test.test_id << "\n";
|
||||
std::cout << " Category: " << test.category << "\n";
|
||||
std::cout << " Runs: " << test.total_runs << " (" << test.pass_count
|
||||
<< " passed, " << test.fail_count << " failed)\n\n";
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status HandleTestResultsCommand(const std::vector<std::string>& args) {
|
||||
std::string test_id;
|
||||
std::string host = "localhost";
|
||||
int port = 50052;
|
||||
bool include_logs = false;
|
||||
|
||||
for (size_t i = 0; i < args.size(); ++i) {
|
||||
if (args[i] == "--test-id" && i + 1 < args.size()) {
|
||||
test_id = args[++i];
|
||||
} else if (args[i] == "--host" && i + 1 < args.size()) {
|
||||
host = args[++i];
|
||||
} else if (args[i] == "--port" && i + 1 < args.size()) {
|
||||
port = std::stoi(args[++i]);
|
||||
} else if (args[i] == "--include-logs") {
|
||||
include_logs = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (test_id.empty()) {
|
||||
return absl::InvalidArgumentError(
|
||||
"Usage: agent test results --test-id <id> [--include-logs] [--host <host>] [--port <port>]");
|
||||
}
|
||||
|
||||
GuiAutomationClient client(absl::StrCat(host, ":", port));
|
||||
auto status = client.Connect();
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
auto details = client.GetTestResults(test_id, include_logs);
|
||||
if (!details.ok()) {
|
||||
return details.status();
|
||||
}
|
||||
|
||||
std::cout << "\n=== Test Results ===\n";
|
||||
std::cout << "Test ID: " << details.value().test_id << "\n";
|
||||
std::cout << "Name: " << details.value().test_name << "\n";
|
||||
std::cout << "Success: " << (details.value().success ? "✓" : "✗") << "\n";
|
||||
std::cout << "Duration: " << details.value().duration_ms << "ms\n\n";
|
||||
|
||||
if (!details.value().assertions.empty()) {
|
||||
std::cout << "Assertions:\n";
|
||||
for (const auto& assertion : details.value().assertions) {
|
||||
std::cout << " " << (assertion.passed ? "✓" : "✗") << " "
|
||||
<< assertion.description << "\n";
|
||||
if (!assertion.error_message.empty()) {
|
||||
std::cout << " Error: " << assertion.error_message << "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (include_logs && !details.value().logs.empty()) {
|
||||
std::cout << "\nLogs:\n";
|
||||
for (const auto& log : details.value().logs) {
|
||||
std::cout << " " << log << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status HandleTestRecordCommand(const std::vector<std::string>& args) {
|
||||
if (args.empty()) {
|
||||
return absl::InvalidArgumentError(
|
||||
"Usage: agent test record <start|stop> [--output <file>]");
|
||||
}
|
||||
|
||||
std::string action = args[0];
|
||||
if (action != "start" && action != "stop") {
|
||||
return absl::InvalidArgumentError("Record action must be 'start' or 'stop'");
|
||||
}
|
||||
|
||||
// TODO: Implement recording functionality
|
||||
return absl::UnimplementedError(
|
||||
"Test recording is not yet implemented.\n"
|
||||
"This feature will allow capturing GUI interactions for replay.");
|
||||
}
|
||||
|
||||
#endif // YAZE_WITH_GRPC
|
||||
|
||||
absl::Status HandleTestCommand(const std::vector<std::string>& args) {
|
||||
if (args.empty()) {
|
||||
return absl::InvalidArgumentError(
|
||||
"Usage: agent test <subcommand>\n"
|
||||
"Subcommands:\n"
|
||||
" run <prompt> - Generate and run a GUI automation test\n"
|
||||
" replay <script> - Replay a recorded test script\n"
|
||||
" status --test-id - Query test execution status\n"
|
||||
" list - List available tests\n"
|
||||
" results --test-id - Get detailed test results\n"
|
||||
" record start/stop - Record test interactions\n"
|
||||
" run --prompt <text> - Generate and run a GUI automation test\n"
|
||||
" replay <script> - Replay a recorded test script\n"
|
||||
" status --test-id <id> - Query test execution status\n"
|
||||
" list - List available tests\n"
|
||||
" results --test-id <id> - Get detailed test results\n"
|
||||
" record start/stop - Record test interactions\n"
|
||||
"\nNote: Test commands require YAZE_WITH_GRPC=ON at build time.");
|
||||
}
|
||||
|
||||
@@ -34,21 +346,26 @@ absl::Status HandleTestCommand(const std::vector<std::string>& args) {
|
||||
std::string subcommand = args[0];
|
||||
std::vector<std::string> tail(args.begin() + 1, args.end());
|
||||
|
||||
// TODO: Implement test subcommands
|
||||
// These will be moved to separate files:
|
||||
// - test_run_commands.cc (run, replay)
|
||||
// - test_introspection_commands.cc (status, list, results)
|
||||
// - test_record_commands.cc (record start/stop)
|
||||
// - test_suite_commands.cc (suite run/validate/create) [fully guarded]
|
||||
|
||||
return absl::UnimplementedError(
|
||||
absl::StrCat("Test subcommand '", subcommand,
|
||||
"' not yet implemented in modular structure.\n"
|
||||
"This is being refactored for better organization."));
|
||||
if (subcommand == "run") {
|
||||
return HandleTestRunCommand(tail);
|
||||
} else if (subcommand == "replay") {
|
||||
return HandleTestReplayCommand(tail);
|
||||
} else if (subcommand == "status") {
|
||||
return HandleTestStatusCommand(tail);
|
||||
} else if (subcommand == "list") {
|
||||
return HandleTestListCommand(tail);
|
||||
} else if (subcommand == "results") {
|
||||
return HandleTestResultsCommand(tail);
|
||||
} else if (subcommand == "record") {
|
||||
return HandleTestRecordCommand(tail);
|
||||
} else {
|
||||
return absl::InvalidArgumentError(
|
||||
absl::StrCat("Unknown test subcommand: ", subcommand,
|
||||
"\nRun 'z3ed agent test' for usage."));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace agent
|
||||
} // namespace cli
|
||||
} // namespace yaze
|
||||
|
||||
|
||||
@@ -14,63 +14,116 @@ PromptBuilder::PromptBuilder() {
|
||||
}
|
||||
|
||||
void PromptBuilder::LoadDefaultExamples() {
|
||||
// Palette manipulation examples
|
||||
examples_.push_back({
|
||||
"Change the color at index 5 in palette 0 to red",
|
||||
{
|
||||
"palette export --group overworld --id 0 --to temp_palette.json",
|
||||
"palette set-color --file temp_palette.json --index 5 --color 0xFF0000",
|
||||
"palette import --group overworld --id 0 --from temp_palette.json"
|
||||
},
|
||||
"Export palette, modify specific color, then import back"
|
||||
});
|
||||
// ==========================================================================
|
||||
// OVERWORLD TILE16 EDITING - Primary Focus
|
||||
// ==========================================================================
|
||||
|
||||
// Single tile placement
|
||||
examples_.push_back({
|
||||
"Make all soldiers red",
|
||||
{
|
||||
"palette export --group sprite --id 3 --to soldier_palette.json",
|
||||
"palette set-color --file soldier_palette.json --index 1 --color 0xFF0000",
|
||||
"palette set-color --file soldier_palette.json --index 2 --color 0xCC0000",
|
||||
"palette import --group sprite --id 3 --from soldier_palette.json"
|
||||
},
|
||||
"Modify multiple colors in a sprite palette"
|
||||
});
|
||||
|
||||
// Overworld manipulation examples
|
||||
examples_.push_back({
|
||||
"Place a tree at coordinates (10, 20) on map 0",
|
||||
"Place a tree at position 10, 20 on the Light World map",
|
||||
{
|
||||
"overworld set-tile --map 0 --x 10 --y 20 --tile 0x02E"
|
||||
},
|
||||
"Tree tile ID is 0x02E in ALTTP"
|
||||
"Single tile16 placement. Tree tile ID is 0x02E in vanilla ALTTP"
|
||||
});
|
||||
|
||||
// Area/region editing
|
||||
examples_.push_back({
|
||||
"Put a house at position 5, 5",
|
||||
"Create a 3x3 water pond at coordinates 15, 10",
|
||||
{
|
||||
"overworld set-tile --map 0 --x 5 --y 5 --tile 0x0C0",
|
||||
"overworld set-tile --map 0 --x 6 --y 5 --tile 0x0C1",
|
||||
"overworld set-tile --map 0 --x 5 --y 6 --tile 0x0D0",
|
||||
"overworld set-tile --map 0 --x 6 --y 6 --tile 0x0D1"
|
||||
"overworld set-tile --map 0 --x 15 --y 10 --tile 0x14C",
|
||||
"overworld set-tile --map 0 --x 16 --y 10 --tile 0x14D",
|
||||
"overworld set-tile --map 0 --x 17 --y 10 --tile 0x14C",
|
||||
"overworld set-tile --map 0 --x 15 --y 11 --tile 0x14D",
|
||||
"overworld set-tile --map 0 --x 16 --y 11 --tile 0x14D",
|
||||
"overworld set-tile --map 0 --x 17 --y 11 --tile 0x14D",
|
||||
"overworld set-tile --map 0 --x 15 --y 12 --tile 0x14E",
|
||||
"overworld set-tile --map 0 --x 16 --y 12 --tile 0x14E",
|
||||
"overworld set-tile --map 0 --x 17 --y 12 --tile 0x14E"
|
||||
},
|
||||
"Houses require 4 tiles (2x2 grid)"
|
||||
"Water areas use different edge tiles: 0x14C (top), 0x14D (middle), 0x14E (bottom)"
|
||||
});
|
||||
|
||||
// Validation examples
|
||||
// Path/line creation
|
||||
examples_.push_back({
|
||||
"Validate the ROM",
|
||||
"Add a dirt path from position 5,5 to 5,15",
|
||||
{
|
||||
"overworld set-tile --map 0 --x 5 --y 5 --tile 0x022",
|
||||
"overworld set-tile --map 0 --x 5 --y 6 --tile 0x022",
|
||||
"overworld set-tile --map 0 --x 5 --y 7 --tile 0x022",
|
||||
"overworld set-tile --map 0 --x 5 --y 8 --tile 0x022",
|
||||
"overworld set-tile --map 0 --x 5 --y 9 --tile 0x022",
|
||||
"overworld set-tile --map 0 --x 5 --y 10 --tile 0x022",
|
||||
"overworld set-tile --map 0 --x 5 --y 11 --tile 0x022",
|
||||
"overworld set-tile --map 0 --x 5 --y 12 --tile 0x022",
|
||||
"overworld set-tile --map 0 --x 5 --y 13 --tile 0x022",
|
||||
"overworld set-tile --map 0 --x 5 --y 14 --tile 0x022",
|
||||
"overworld set-tile --map 0 --x 5 --y 15 --tile 0x022"
|
||||
},
|
||||
"Linear paths are created by placing tiles sequentially. Dirt tile is 0x022"
|
||||
});
|
||||
|
||||
// Forest/tree grouping
|
||||
examples_.push_back({
|
||||
"Plant a row of trees horizontally at y=8 from x=20 to x=25",
|
||||
{
|
||||
"overworld set-tile --map 0 --x 20 --y 8 --tile 0x02E",
|
||||
"overworld set-tile --map 0 --x 21 --y 8 --tile 0x02E",
|
||||
"overworld set-tile --map 0 --x 22 --y 8 --tile 0x02E",
|
||||
"overworld set-tile --map 0 --x 23 --y 8 --tile 0x02E",
|
||||
"overworld set-tile --map 0 --x 24 --y 8 --tile 0x02E",
|
||||
"overworld set-tile --map 0 --x 25 --y 8 --tile 0x02E"
|
||||
},
|
||||
"Tree rows create natural barriers and visual boundaries"
|
||||
});
|
||||
|
||||
// ==========================================================================
|
||||
// DUNGEON EDITING - Label-Aware Operations
|
||||
// ==========================================================================
|
||||
|
||||
// Sprite placement (label-aware)
|
||||
examples_.push_back({
|
||||
"Add 3 soldiers to the Eastern Palace entrance room",
|
||||
{
|
||||
"dungeon add-sprite --dungeon 0x02 --room 0x00 --sprite 0x41 --x 5 --y 3",
|
||||
"dungeon add-sprite --dungeon 0x02 --room 0x00 --sprite 0x41 --x 10 --y 3",
|
||||
"dungeon add-sprite --dungeon 0x02 --room 0x00 --sprite 0x41 --x 7 --y 8"
|
||||
},
|
||||
"Dungeon ID 0x02 is Eastern Palace. Sprite 0x41 is soldier. Spread placement for balance"
|
||||
});
|
||||
|
||||
// Object placement
|
||||
examples_.push_back({
|
||||
"Place a chest in the Hyrule Castle treasure room",
|
||||
{
|
||||
"dungeon add-chest --dungeon 0x00 --room 0x60 --x 7 --y 5 --item 0x12 --big false"
|
||||
},
|
||||
"Dungeon 0x00 is Hyrule Castle. Item 0x12 is a small key. Position centered in room"
|
||||
});
|
||||
|
||||
// ==========================================================================
|
||||
// COMMON TILE16 REFERENCE (for AI knowledge)
|
||||
// ==========================================================================
|
||||
// Grass: 0x020
|
||||
// Dirt: 0x022
|
||||
// Tree: 0x02E
|
||||
// Water (top): 0x14C
|
||||
// Water (middle): 0x14D
|
||||
// Water (bottom): 0x14E
|
||||
// Bush: 0x003
|
||||
// Rock: 0x004
|
||||
// Flower: 0x021
|
||||
// Sand: 0x023
|
||||
// Deep Water: 0x14F
|
||||
// Shallow Water: 0x150
|
||||
|
||||
// Validation example (still useful)
|
||||
examples_.push_back({
|
||||
"Check if my overworld changes are valid",
|
||||
{
|
||||
"rom validate"
|
||||
},
|
||||
"Simple validation command"
|
||||
});
|
||||
|
||||
examples_.push_back({
|
||||
"Check if my changes are valid",
|
||||
{
|
||||
"rom validate"
|
||||
},
|
||||
"Validation ensures ROM integrity"
|
||||
"Validation ensures ROM integrity after tile modifications"
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -83,6 +83,29 @@ if(YAZE_WITH_JSON)
|
||||
list(APPEND Z3ED_SRC_FILES cli/service/prompt_builder.cc)
|
||||
endif()
|
||||
|
||||
# ============================================================================
|
||||
# SSL/HTTPS Support (Required for Gemini API and future collaborative features)
|
||||
# ============================================================================
|
||||
option(YAZE_WITH_SSL "Build with OpenSSL support for HTTPS" ON)
|
||||
if(YAZE_WITH_SSL OR YAZE_WITH_JSON)
|
||||
# Find OpenSSL on the system
|
||||
find_package(OpenSSL REQUIRED)
|
||||
|
||||
# Define the SSL support macro for httplib
|
||||
target_compile_definitions(z3ed PRIVATE CPPHTTPLIB_OPENSSL_SUPPORT)
|
||||
|
||||
# Link OpenSSL libraries
|
||||
target_link_libraries(z3ed PRIVATE OpenSSL::SSL OpenSSL::Crypto)
|
||||
|
||||
# On macOS, also enable Keychain cert support
|
||||
if(APPLE)
|
||||
target_compile_definitions(z3ed PRIVATE CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN)
|
||||
target_link_libraries(z3ed PRIVATE "-framework CoreFoundation" "-framework Security")
|
||||
endif()
|
||||
|
||||
message(STATUS "✓ SSL/HTTPS support enabled for z3ed")
|
||||
endif()
|
||||
|
||||
target_include_directories(
|
||||
z3ed PUBLIC
|
||||
${CMAKE_SOURCE_DIR}/src/lib/
|
||||
|
||||
Reference in New Issue
Block a user