From f19451e99e76a013053e389c6cdd4dfe6a341333 Mon Sep 17 00:00:00 2001 From: scawful Date: Sat, 4 Oct 2025 22:43:21 -0400 Subject: [PATCH] feat: Add networking commands for z3ed CLI - Implemented new CLI commands for network operations, including `net connect`, `net join`, `net leave`, and `net proposal` for real-time collaboration. - Enhanced the `CollaborationService` and `WebSocketClient` to support session management and proposal submissions. - Updated `README.md` to include usage instructions for the new networking commands and features. - Improved CMake configuration to include new source files for networking functionalities. --- docs/z3ed/README.md | 17 +- src/app/net/net_library.cmake | 1 + src/cli/handlers/net/net_commands.cc | 271 +++++++++++++++++++++++++++ src/cli/handlers/net/net_commands.h | 48 +++++ 4 files changed, 335 insertions(+), 2 deletions(-) create mode 100644 src/cli/handlers/net/net_commands.cc create mode 100644 src/cli/handlers/net/net_commands.h diff --git a/docs/z3ed/README.md b/docs/z3ed/README.md index 03dd9321..466ed9d2 100644 --- a/docs/z3ed/README.md +++ b/docs/z3ed/README.md @@ -889,16 +889,29 @@ The AI response appears in your chat history and can reference specific details ### ✅ Recently Completed (v0.2.0-alpha - October 5, 2025) +#### Core AI Features - **Enhanced System Prompt (v3)**: Proactive tool chaining with implicit iteration to minimize back-and-forth conversations - **Learn Command**: Full implementation with preferences, ROM patterns, project context, and conversation memory storage - **Native Gemini Function Calling**: Upgraded from manual curl to native function calling API with automatic tool schema generation - **Multimodal Vision Testing**: Comprehensive test suite for Gemini vision capabilities with screenshot integration - **AI-Controlled GUI Automation**: Natural language parsing (`AIActionParser`) and test script generation (`GuiActionGenerator`) for automated tile placement -- **gRPC Windows Build Optimization**: vcpkg integration for 10-20x faster Windows builds, removed abseil-cpp submodule + +#### Version Management & Protection - **ROM Version Management System**: `RomVersionManager` with automatic snapshots, safe points, corruption detection, and rollback capabilities - **Proposal Approval Framework**: `ProposalApprovalManager` with host/majority/unanimous voting modes to protect ROM from unwanted changes + +#### Networking & Collaboration (NEW) +- **Cross-Platform WebSocket Client**: `WebSocketClient` with Windows/macOS/Linux support using httplib +- **Collaboration Service**: `CollaborationService` integrating version management with real-time networking +- **yaze-server v2.0 Protocol**: Extended with proposal voting (`proposal_vote`, `proposal_vote_received`) +- **z3ed Network Commands**: CLI commands for remote collaboration (`net connect`, `net join`, `proposal submit/wait`) - **Collaboration UI Panel**: `CollaborationPanel` widget with version history, ROM sync tracking, snapshot gallery, and approval workflow -- **Improved Documentation**: Consolidated architecture, enhancement plans, and build instructions with JSON-first approach + +#### Build System & Infrastructure +- **gRPC Windows Build Optimization**: vcpkg integration for 10-20x faster Windows builds, removed abseil-cpp submodule +- **Cross-Platform Networking**: Native socket support (ws2_32 on Windows, BSD sockets on Unix) +- **Namespace Refactoring**: Created `app/net` namespace for networking components +- **Improved Documentation**: Consolidated architecture, enhancement plans, networking guide, and build instructions with JSON-first approach ## 12. Troubleshooting diff --git a/src/app/net/net_library.cmake b/src/app/net/net_library.cmake index 820b143e..f172e4c3 100644 --- a/src/app/net/net_library.cmake +++ b/src/app/net/net_library.cmake @@ -13,6 +13,7 @@ set( YAZE_NET_SRC app/net/rom_version_manager.cc app/net/websocket_client.cc + app/net/collaboration_service.cc ) add_library(yaze_net STATIC ${YAZE_NET_SRC}) diff --git a/src/cli/handlers/net/net_commands.cc b/src/cli/handlers/net/net_commands.cc new file mode 100644 index 00000000..d56686f0 --- /dev/null +++ b/src/cli/handlers/net/net_commands.cc @@ -0,0 +1,271 @@ +#include "cli/handlers/net/net_commands.h" + +#include +#include + +#include "absl/strings/str_format.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" + +#ifdef YAZE_WITH_JSON +#include "nlohmann/json.hpp" +#endif + +namespace yaze { +namespace cli { +namespace net { + +namespace { + +// Global network client for CLI operations +std::unique_ptr g_network_client; + +void EnsureClient() { + if (!g_network_client) { + g_network_client = std::make_unique(); + } +} + +} // namespace + +absl::Status HandleNetConnect(const std::vector& args) { + EnsureClient(); + + std::string host = "localhost"; + int port = 8765; + + // Parse arguments + for (size_t i = 0; i < args.size(); ++i) { + if (args[i] == "--host" && i + 1 < args.size()) { + host = args[i + 1]; + ++i; + } else if (args[i] == "--port" && i + 1 < args.size()) { + port = std::stoi(args[i + 1]); + ++i; + } + } + + std::cout << "Connecting to " << host << ":" << port << "..." << std::endl; + + auto status = g_network_client->Connect(host, port); + + if (status.ok()) { + std::cout << "✓ Connected to yaze-server" << std::endl; + } else { + std::cerr << "✗ Connection failed: " << status.message() << std::endl; + } + + return status; +} + +absl::Status HandleNetJoin(const std::vector& args) { + EnsureClient(); + + if (!g_network_client->IsConnected()) { + return absl::FailedPreconditionError( + "Not connected. Run: z3ed net connect"); + } + + std::string session_code; + std::string username; + + // Parse arguments + for (size_t i = 0; i < args.size(); ++i) { + if (args[i] == "--code" && i + 1 < args.size()) { + session_code = args[i + 1]; + ++i; + } else if (args[i] == "--username" && i + 1 < args.size()) { + username = args[i + 1]; + ++i; + } + } + + if (session_code.empty() || username.empty()) { + return absl::InvalidArgumentError( + "Usage: z3ed net join --code --username "); + } + + std::cout << "Joining session " << session_code << " as " << username << "..." + << std::endl; + + auto status = g_network_client->JoinSession(session_code, username); + + if (status.ok()) { + std::cout << "✓ Joined session successfully" << std::endl; + } else { + std::cerr << "✗ Failed to join: " << status.message() << std::endl; + } + + return status; +} + +absl::Status HandleNetLeave(const std::vector& args) { + EnsureClient(); + + if (!g_network_client->IsConnected()) { + return absl::FailedPreconditionError("Not connected"); + } + + std::cout << "Leaving session..." << std::endl; + + g_network_client->Disconnect(); + + std::cout << "✓ Left session" << std::endl; + + return absl::OkStatus(); +} + +absl::Status HandleNetProposal(const std::vector& args) { + EnsureClient(); + + if (!g_network_client->IsConnected()) { + return absl::FailedPreconditionError( + "Not connected. Run: z3ed net connect"); + } + + if (args.empty()) { + std::cout << "Usage:\n"; + std::cout << " z3ed net proposal submit --description --data \n"; + std::cout << " z3ed net proposal status --id \n"; + std::cout << " z3ed net proposal wait --id [--timeout ]\n"; + return absl::OkStatus(); + } + + std::string subcommand = args[0]; + std::vector subargs(args.begin() + 1, args.end()); + + if (subcommand == "submit") { + return HandleProposalSubmit(subargs); + } else if (subcommand == "status") { + return HandleProposalStatus(subargs); + } else if (subcommand == "wait") { + return HandleProposalWait(subargs); + } else { + return absl::InvalidArgumentError( + absl::StrFormat("Unknown proposal subcommand: %s", subcommand)); + } +} + +absl::Status HandleProposalSubmit(const std::vector& args) { + std::string description; + std::string data_json; + std::string username = "cli_user"; // Default + + for (size_t i = 0; i < args.size(); ++i) { + if (args[i] == "--description" && i + 1 < args.size()) { + description = args[i + 1]; + ++i; + } else if (args[i] == "--data" && i + 1 < args.size()) { + data_json = args[i + 1]; + ++i; + } else if (args[i] == "--username" && i + 1 < args.size()) { + username = args[i + 1]; + ++i; + } + } + + if (description.empty() || data_json.empty()) { + return absl::InvalidArgumentError( + "Usage: z3ed net proposal submit --description --data "); + } + + std::cout << "Submitting proposal..." << std::endl; + std::cout << " Description: " << description << std::endl; + + auto status = g_network_client->SubmitProposal( + description, + data_json, + username + ); + + if (status.ok()) { + std::cout << "✓ Proposal submitted" << std::endl; + std::cout << " Waiting for approval from host..." << std::endl; + } else { + std::cerr << "✗ Failed to submit: " << status.message() << std::endl; + } + + return status; +} + +absl::Status HandleProposalStatus(const std::vector& args) { + std::string proposal_id; + + for (size_t i = 0; i < args.size(); ++i) { + if (args[i] == "--id" && i + 1 < args.size()) { + proposal_id = args[i + 1]; + ++i; + } + } + + if (proposal_id.empty()) { + return absl::InvalidArgumentError( + "Usage: z3ed net proposal status --id "); + } + + auto status_result = g_network_client->GetProposalStatus(proposal_id); + + if (status_result.ok()) { + std::cout << "Proposal " << proposal_id.substr(0, 8) << "..." << std::endl; + std::cout << " Status: " << *status_result << std::endl; + } else { + std::cerr << "✗ Failed to get status: " << status_result.status().message() + << std::endl; + } + + return status_result.status(); +} + +absl::Status HandleProposalWait(const std::vector& args) { + std::string proposal_id; + int timeout_seconds = 60; + + for (size_t i = 0; i < args.size(); ++i) { + if (args[i] == "--id" && i + 1 < args.size()) { + proposal_id = args[i + 1]; + ++i; + } else if (args[i] == "--timeout" && i + 1 < args.size()) { + timeout_seconds = std::stoi(args[i + 1]); + ++i; + } + } + + if (proposal_id.empty()) { + return absl::InvalidArgumentError( + "Usage: z3ed net proposal wait --id [--timeout ]"); + } + + std::cout << "Waiting for approval (timeout: " << timeout_seconds << "s)..." + << std::endl; + + auto approved_result = g_network_client->WaitForApproval( + proposal_id, + timeout_seconds + ); + + if (approved_result.ok()) { + if (*approved_result) { + std::cout << "✓ Proposal approved!" << std::endl; + } else { + std::cout << "✗ Proposal rejected" << std::endl; + } + } else { + std::cerr << "✗ Error: " << approved_result.status().message() << std::endl; + } + + return approved_result.status(); +} + +absl::Status HandleNetStatus(const std::vector& args) { + EnsureClient(); + + std::cout << "Network Status:" << std::endl; + std::cout << " Connected: " + << (g_network_client->IsConnected() ? "Yes" : "No") << std::endl; + + return absl::OkStatus(); +} + +} // namespace net +} // namespace cli +} // namespace yaze diff --git a/src/cli/handlers/net/net_commands.h b/src/cli/handlers/net/net_commands.h new file mode 100644 index 00000000..3dad2780 --- /dev/null +++ b/src/cli/handlers/net/net_commands.h @@ -0,0 +1,48 @@ +#ifndef YAZE_CLI_HANDLERS_NET_NET_COMMANDS_H_ +#define YAZE_CLI_HANDLERS_NET_NET_COMMANDS_H_ + +#include +#include + +#include "absl/status/status.h" +#include "cli/service/net/z3ed_network_client.h" + +namespace yaze { +namespace cli { +namespace net { + +/** + * Handle 'z3ed net connect' command + */ +absl::Status HandleNetConnect(const std::vector& args); + +/** + * Handle 'z3ed net join' command + */ +absl::Status HandleNetJoin(const std::vector& args); + +/** + * Handle 'z3ed net leave' command + */ +absl::Status HandleNetLeave(const std::vector& args); + +/** + * Handle 'z3ed net proposal' command + */ +absl::Status HandleNetProposal(const std::vector& args); + +/** + * Handle 'z3ed net status' command + */ +absl::Status HandleNetStatus(const std::vector& args); + +// Proposal subcommands +absl::Status HandleProposalSubmit(const std::vector& args); +absl::Status HandleProposalStatus(const std::vector& args); +absl::Status HandleProposalWait(const std::vector& args); + +} // namespace net +} // namespace cli +} // namespace yaze + +#endif // YAZE_CLI_HANDLERS_NET_NET_COMMANDS_H_