From 45ffc9dc488cfd029cb81ac02011c35d582fd4a8 Mon Sep 17 00:00:00 2001 From: libing0526 Date: Fri, 24 Oct 2025 23:10:24 +0800 Subject: [PATCH] feat(cli): add user args support for run command - Enhanced 'parallax run' to accept user-defined arguments like 'parallax join' - Refactored EscapeForShell from ModelRunCommand/ModelJoinCommand to WSLCommand base class - Added shell escaping for safe bash -c command passing - Updated documentation (README.md, CHANGELOG.md) --- CHANGELOG.md | 6 + README.md | 8 +- src/parallax/cli/commands/base_command.h | 472 ++++++++++--------- src/parallax/cli/commands/model_commands.cpp | 72 +-- src/parallax/cli/commands/model_commands.h | 252 +++++----- 5 files changed, 422 insertions(+), 388 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f691d6..0471a5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed +- Enhanced `parallax run` command to support user-defined arguments, allowing flexible configuration (e.g., `-m model_name`, `--port 8080`) +- Updated `parallax run` help documentation to reflect new argument passing capabilities +- Improved consistency between `parallax run` and `parallax join` command interfaces +- Refactored `EscapeForShell` function from duplicate implementations in `ModelRunCommand` and `ModelJoinCommand` to shared `WSLCommand` base class + ### Added - Initial release of Parallax Windows CLI - Comprehensive environment checking and installation diff --git a/README.md b/README.md index 0ee024e..51cc1e4 100644 --- a/README.md +++ b/README.md @@ -172,13 +172,13 @@ parallax config --help ### `parallax run` Run Parallax inference server directly in WSL ```cmd -parallax run [--help|-h] +parallax run [args...] ``` ### `parallax join` Join distributed inference cluster as a node ```cmd -parallax join [options] +parallax join [args...] ``` ### `parallax cmd` @@ -188,8 +188,8 @@ parallax cmd [--venv] [args...] ``` **Command Descriptions**: -- `run`: Start Parallax inference server directly in WSL, default configuration is Qwen/Qwen3-0.6B model, listening on localhost:3001 -- `join`: Join distributed inference cluster as a worker node (open a new terminal with administrator privileges and input `parallax join`) +- `run`: Start Parallax inference server directly in WSL. You can pass any arguments supported by `parallax run` command. Examples: `parallax run -m Qwen/Qwen3-0.6B`, `parallax run --port 8080` +- `join`: Join distributed inference cluster as a worker node. You can pass any arguments supported by `parallax join` command. Examples: `parallax join -m Qwen/Qwen3-0.6B`, `parallax join -s scheduler-addr` - `cmd`: Pass-through commands to WSL environment, supports `--venv` option to run in parallax project's Python virtual environment **Main Configuration Items**: diff --git a/src/parallax/cli/commands/base_command.h b/src/parallax/cli/commands/base_command.h index 4191654..3891de3 100644 --- a/src/parallax/cli/commands/base_command.h +++ b/src/parallax/cli/commands/base_command.h @@ -1,215 +1,259 @@ -#pragma once - -#include -#include -#include -#include -#include -#include "utils/utils.h" -#include "utils/process.h" -#include "config/config_manager.h" -#include "tinylog/tinylog.h" -#include - -namespace parallax { -namespace commands { - -// Command execution result -enum class CommandResult { - Success = 0, - InvalidArgs = 1, - EnvironmentError = 2, - ExecutionError = 3 -}; - -// Command execution context -struct CommandContext { - std::vector args; - std::string ubuntu_version; - std::string proxy_url; - bool is_admin = false; - bool wsl_available = false; -}; - -// Base command interface -class ICommand { - public: - virtual ~ICommand() = default; - virtual CommandResult Execute(const std::vector& args) = 0; - virtual void ShowHelp() = 0; - virtual std::string GetName() const = 0; - virtual std::string GetDescription() const = 0; -}; - -// Environment requirements structure -struct EnvironmentRequirements { - bool need_wsl = false; - bool need_admin = false; - bool sync_proxy = false; -}; - -// Base command template class - using CRTP and template method pattern -template -class BaseCommand : public ICommand { - public: - CommandResult Execute(const std::vector& args) override final { - // Template method: define execution flow - CommandContext context; - context.args = args; - - // Prioritize help parameters, return directly - if (ShouldShowHelp(context.args)) { - ShowHelp(); - return CommandResult::Success; - } - - // 1. Argument validation - auto result = ValidateArgs(context); - if (result != CommandResult::Success) { - return result; - } - - // 2. Environment preparation - result = PrepareEnvironment(context); - if (result != CommandResult::Success) { - return result; - } - - // 3. Execute specific command (implemented by derived class) - return static_cast(this)->ExecuteImpl(context); - } - - void ShowHelp() override final { - static_cast(this)->ShowHelpImpl(); - } - - protected: - // Virtual function: determine whether to show help information - // Derived classes can override this method to customize help parameter - // checking logic - virtual bool ShouldShowHelp(const std::vector& args) { - // Default behavior: show help if --help or -h appears anywhere - for (const auto& arg : args) { - if (arg == "--help" || arg == "-h") { - return true; - } - } - return false; - } - - // Template method: argument validation - virtual CommandResult ValidateArgs(CommandContext& context) { - // Call derived class argument validation - return static_cast(this)->ValidateArgsImpl(context); - } - - // Template method: environment preparation - virtual CommandResult PrepareEnvironment(CommandContext& context) { - // Get basic environment information - context.ubuntu_version = - parallax::config::ConfigManager::GetInstance().GetConfigValue( - parallax::config::KEY_WSL_LINUX_DISTRO); - context.proxy_url = GetProxyUrl(); - context.is_admin = IsAdmin(); - - // Check environment requirements - auto requirements = - static_cast(this)->GetEnvironmentRequirements(); - - if (requirements.need_admin && !context.is_admin) { - ShowError("Administrator privileges required for this command."); - return CommandResult::EnvironmentError; - } - - if (requirements.need_wsl) { - context.wsl_available = CheckWSLEnvironment(context); - if (!context.wsl_available) { - ShowError( - "WSL environment is not available. Please run 'parallax " - "install' first."); - return CommandResult::EnvironmentError; - } - } - - return CommandResult::Success; - } - - std::string GetProxyUrl() { return parallax::utils::GetProxyUrl(); } - - bool IsAdmin() { return parallax::utils::IsAdmin(); } - - bool CheckWSLEnvironment(const CommandContext& context) { - std::string stdout_output, stderr_output; - int exit_code = parallax::utils::ExecCommandEx( - "powershell.exe -Command \"wsl --list --quiet\"", 30, stdout_output, - stderr_output, false, true); - - if (exit_code != 0) { - return false; - } - - // Process PowerShell output encoding - std::string utf8_stdout = - parallax::utils::ConvertPowerShellOutputToUtf8(stdout_output); - - return utf8_stdout.find(context.ubuntu_version) != std::string::npos; - } - - void ShowError(const std::string& message) { - std::cout << "[ERROR] " << message << std::endl; - } - - void ShowInfo(const std::string& message) { - std::cout << "[INFO] " << message << std::endl; - } - - void ShowWarning(const std::string& message) { - std::cout << "[WARNING] " << message << std::endl; - } -}; - -// Administrator command base class - commands requiring administrator -// privileges -template -class AdminCommand : public BaseCommand { - public: - EnvironmentRequirements GetEnvironmentRequirements() { - EnvironmentRequirements req; - req.need_admin = true; - return req; - } -}; - -// WSL command base class - commands requiring WSL environment -template -class WSLCommand : public BaseCommand { - public: - EnvironmentRequirements GetEnvironmentRequirements() { - EnvironmentRequirements req; - req.need_wsl = true; - return req; - } - - protected: - // Build WSL command prefix, including -u root parameter - std::string GetWSLCommandPrefix(const CommandContext& context) { - return parallax::utils::GetWSLCommandPrefix(context.ubuntu_version); - } - - // Build complete WSL bash command - std::string BuildWSLCommand(const CommandContext& context, - const std::string& command) { - return parallax::utils::BuildWSLCommand(context.ubuntu_version, - command); - } - - // Build WSL command (without using bash -c, execute command directly) - std::string BuildWSLDirectCommand(const CommandContext& context, - const std::string& command) { - return parallax::utils::BuildWSLDirectCommand(context.ubuntu_version, - command); - } -}; - -} // namespace commands +#pragma once + +#include +#include +#include +#include +#include +#include "utils/utils.h" +#include "utils/process.h" +#include "config/config_manager.h" +#include "tinylog/tinylog.h" +#include + +namespace parallax { +namespace commands { + +// Command execution result +enum class CommandResult { + Success = 0, + InvalidArgs = 1, + EnvironmentError = 2, + ExecutionError = 3 +}; + +// Command execution context +struct CommandContext { + std::vector args; + std::string ubuntu_version; + std::string proxy_url; + bool is_admin = false; + bool wsl_available = false; +}; + +// Base command interface +class ICommand { + public: + virtual ~ICommand() = default; + virtual CommandResult Execute(const std::vector& args) = 0; + virtual void ShowHelp() = 0; + virtual std::string GetName() const = 0; + virtual std::string GetDescription() const = 0; +}; + +// Environment requirements structure +struct EnvironmentRequirements { + bool need_wsl = false; + bool need_admin = false; + bool sync_proxy = false; +}; + +// Base command template class - using CRTP and template method pattern +template +class BaseCommand : public ICommand { + public: + CommandResult Execute(const std::vector& args) override final { + // Template method: define execution flow + CommandContext context; + context.args = args; + + // Prioritize help parameters, return directly + if (ShouldShowHelp(context.args)) { + ShowHelp(); + return CommandResult::Success; + } + + // 1. Argument validation + auto result = ValidateArgs(context); + if (result != CommandResult::Success) { + return result; + } + + // 2. Environment preparation + result = PrepareEnvironment(context); + if (result != CommandResult::Success) { + return result; + } + + // 3. Execute specific command (implemented by derived class) + return static_cast(this)->ExecuteImpl(context); + } + + void ShowHelp() override final { + static_cast(this)->ShowHelpImpl(); + } + + protected: + // Virtual function: determine whether to show help information + // Derived classes can override this method to customize help parameter + // checking logic + virtual bool ShouldShowHelp(const std::vector& args) { + // Default behavior: show help if --help or -h appears anywhere + for (const auto& arg : args) { + if (arg == "--help" || arg == "-h") { + return true; + } + } + return false; + } + + // Template method: argument validation + virtual CommandResult ValidateArgs(CommandContext& context) { + // Call derived class argument validation + return static_cast(this)->ValidateArgsImpl(context); + } + + // Template method: environment preparation + virtual CommandResult PrepareEnvironment(CommandContext& context) { + // Get basic environment information + context.ubuntu_version = + parallax::config::ConfigManager::GetInstance().GetConfigValue( + parallax::config::KEY_WSL_LINUX_DISTRO); + context.proxy_url = GetProxyUrl(); + context.is_admin = IsAdmin(); + + // Check environment requirements + auto requirements = + static_cast(this)->GetEnvironmentRequirements(); + + if (requirements.need_admin && !context.is_admin) { + ShowError("Administrator privileges required for this command."); + return CommandResult::EnvironmentError; + } + + if (requirements.need_wsl) { + context.wsl_available = CheckWSLEnvironment(context); + if (!context.wsl_available) { + ShowError( + "WSL environment is not available. Please run 'parallax " + "install' first."); + return CommandResult::EnvironmentError; + } + } + + return CommandResult::Success; + } + + std::string GetProxyUrl() { return parallax::utils::GetProxyUrl(); } + + bool IsAdmin() { return parallax::utils::IsAdmin(); } + + bool CheckWSLEnvironment(const CommandContext& context) { + std::string stdout_output, stderr_output; + int exit_code = parallax::utils::ExecCommandEx( + "powershell.exe -Command \"wsl --list --quiet\"", 30, stdout_output, + stderr_output, false, true); + + if (exit_code != 0) { + return false; + } + + // Process PowerShell output encoding + std::string utf8_stdout = + parallax::utils::ConvertPowerShellOutputToUtf8(stdout_output); + + return utf8_stdout.find(context.ubuntu_version) != std::string::npos; + } + + void ShowError(const std::string& message) { + std::cout << "[ERROR] " << message << std::endl; + } + + void ShowInfo(const std::string& message) { + std::cout << "[INFO] " << message << std::endl; + } + + void ShowWarning(const std::string& message) { + std::cout << "[WARNING] " << message << std::endl; + } +}; + +// Administrator command base class - commands requiring administrator +// privileges +template +class AdminCommand : public BaseCommand { + public: + EnvironmentRequirements GetEnvironmentRequirements() { + EnvironmentRequirements req; + req.need_admin = true; + return req; + } +}; + +// WSL command base class - commands requiring WSL environment +template +class WSLCommand : public BaseCommand { + public: + EnvironmentRequirements GetEnvironmentRequirements() { + EnvironmentRequirements req; + req.need_wsl = true; + return req; + } + + protected: + // Build WSL command prefix, including -u root parameter + std::string GetWSLCommandPrefix(const CommandContext& context) { + return parallax::utils::GetWSLCommandPrefix(context.ubuntu_version); + } + + // Build complete WSL bash command + std::string BuildWSLCommand(const CommandContext& context, + const std::string& command) { + return parallax::utils::BuildWSLCommand(context.ubuntu_version, + command); + } + + // Build WSL command (without using bash -c, execute command directly) + std::string BuildWSLDirectCommand(const CommandContext& context, + const std::string& command) { + return parallax::utils::BuildWSLDirectCommand(context.ubuntu_version, + command); + } + + // Escape arguments for safe passing through bash -c "..." + // This prevents command injection and correctly handles spaces/special chars + // Note: This is for WSL bash layer, not Windows PowerShell layer + std::string EscapeForShell(const std::string& arg) { + // If parameter contains spaces, special characters, etc., need to wrap + // with quotes + if (arg.find(' ') != std::string::npos || + arg.find('\t') != std::string::npos || + arg.find('\n') != std::string::npos || + arg.find('"') != std::string::npos || + arg.find('\'') != std::string::npos || + arg.find('&') != std::string::npos || + arg.find('|') != std::string::npos || + arg.find(';') != std::string::npos || + arg.find('<') != std::string::npos || + arg.find('>') != std::string::npos || + arg.find('(') != std::string::npos || + arg.find(')') != std::string::npos || + arg.find('$') != std::string::npos || + arg.find('`') != std::string::npos || + arg.find('*') != std::string::npos || + arg.find('?') != std::string::npos || + arg.find('[') != std::string::npos || + arg.find(']') != std::string::npos || + arg.find('{') != std::string::npos || + arg.find('}') != std::string::npos) { + // Use single quotes to wrap and escape internal single quotes + std::string escaped = "'"; + for (char c : arg) { + if (c == '\'') { + escaped += "'\"'\"'"; // End single quote, add escaped + // single quote, restart single quote + } else { + escaped += c; + } + } + escaped += "'"; + return escaped; + } + + // If no special characters, return directly + return arg; + } +}; + +} // namespace commands } // namespace parallax \ No newline at end of file diff --git a/src/parallax/cli/commands/model_commands.cpp b/src/parallax/cli/commands/model_commands.cpp index d3e5b5b..7aec956 100644 --- a/src/parallax/cli/commands/model_commands.cpp +++ b/src/parallax/cli/commands/model_commands.cpp @@ -37,18 +37,23 @@ bool ModelRunCommand::IsParallaxProcessRunning(const CommandContext& context) { } bool ModelRunCommand::RunParallaxScript(const CommandContext& context) { - std::string launch_cmd = "cd ~/parallax && source ./venv/bin/activate"; + // Build run command: parallax run [user parameters...] + std::string run_command = BuildRunCommand(context); + + // Build complete WSL command: cd ~/parallax && source ./venv/bin/activate + // && parallax run [args...] + std::string full_command = "cd ~/parallax && source ./venv/bin/activate"; // If proxy is configured, add proxy environment variables if (!context.proxy_url.empty()) { - launch_cmd += " && HTTP_PROXY=\"" + context.proxy_url + - "\" HTTPS_PROXY=\"" + context.proxy_url + - "\" parallax run"; + full_command += " && HTTP_PROXY=\"" + context.proxy_url + + "\" HTTPS_PROXY=\"" + context.proxy_url + "\" " + + run_command; } else { - launch_cmd += " && parallax run"; + full_command += " && " + run_command; } - std::string wsl_command = BuildWSLCommand(context, launch_cmd); + std::string wsl_command = BuildWSLCommand(context, full_command); info_log("Executing Parallax launch command: %s", wsl_command.c_str()); @@ -58,6 +63,20 @@ bool ModelRunCommand::RunParallaxScript(const CommandContext& context) { return exit_code == 0; } +std::string ModelRunCommand::BuildRunCommand(const CommandContext& context) { + std::ostringstream command_stream; + + // Built-in execution of parallax run + command_stream << "parallax run"; + + // If there are user parameters, append them + for (const auto& arg : context.args) { + command_stream << " " << EscapeForShell(arg); + } + + return command_stream.str(); +} + // ModelJoinCommand implementation CommandResult ModelJoinCommand::ValidateArgsImpl(CommandContext& context) { // Check if it's a help request @@ -148,46 +167,5 @@ std::string ModelJoinCommand::BuildJoinCommand(const CommandContext& context) { return command_stream.str(); } -std::string ModelJoinCommand::EscapeForShell(const std::string& arg) { - // If parameter contains spaces, special characters, etc., need to wrap with - // quotes - if (arg.find(' ') != std::string::npos || - arg.find('\t') != std::string::npos || - arg.find('\n') != std::string::npos || - arg.find('"') != std::string::npos || - arg.find('\'') != std::string::npos || - arg.find('&') != std::string::npos || - arg.find('|') != std::string::npos || - arg.find(';') != std::string::npos || - arg.find('<') != std::string::npos || - arg.find('>') != std::string::npos || - arg.find('(') != std::string::npos || - arg.find(')') != std::string::npos || - arg.find('$') != std::string::npos || - arg.find('`') != std::string::npos || - arg.find('*') != std::string::npos || - arg.find('?') != std::string::npos || - arg.find('[') != std::string::npos || - arg.find(']') != std::string::npos || - arg.find('{') != std::string::npos || - arg.find('}') != std::string::npos) { - // Use single quotes to wrap and escape internal single quotes - std::string escaped = "'"; - for (char c : arg) { - if (c == '\'') { - escaped += "'\"'\"'"; // End single quote, add escaped single - // quote, restart single quote - } else { - escaped += c; - } - } - escaped += "'"; - return escaped; - } - - // If no special characters, return directly - return arg; -} - } // namespace commands } // namespace parallax \ No newline at end of file diff --git a/src/parallax/cli/commands/model_commands.h b/src/parallax/cli/commands/model_commands.h index c3aa4f4..4de40a6 100644 --- a/src/parallax/cli/commands/model_commands.h +++ b/src/parallax/cli/commands/model_commands.h @@ -1,124 +1,130 @@ -#pragma once - -#include "base_command.h" -#include "utils/wsl_process.h" -#include - -namespace parallax { -namespace commands { - -// Run command - directly run Parallax Python script in WSL -class ModelRunCommand : public WSLCommand { - public: - std::string GetName() const override { return "run"; } - std::string GetDescription() const override { - return "Run Parallax inference server directly in WSL"; - } - - EnvironmentRequirements GetEnvironmentRequirements() { - EnvironmentRequirements req; - req.need_wsl = true; - req.sync_proxy = false; - return req; - } - - CommandResult ValidateArgsImpl(CommandContext& context) { - // runex command does not accept additional parameters - for (const auto& arg : context.args) { - if (arg != "--help" && arg != "-h") { - this->ShowError("Unknown parameter: " + arg); - this->ShowError("Usage: parallax run [--help|-h]"); - return CommandResult::InvalidArgs; - } - } - return CommandResult::Success; - } - - CommandResult ExecuteImpl(const CommandContext& context) { - // Check if launch.py exists - // if (!CheckLaunchScriptExists(context)) { - // this->ShowError( - // "Parallax launch script not found at " - // "~/parallax/src/parallax/launch.py"); - // this->ShowError( - // "Please run 'parallax check' to verify your environment " - // "setup."); - // return CommandResult::ExecutionError; - // } - - // Check if there are already running processes - // if (IsParallaxProcessRunning(context)) { - // this->ShowError( - // "Parallax server is already running. Use 'parallax stop' to " - // "stop it first."); - // return CommandResult::ExecutionError; - // } - - // Start Parallax server - this->ShowInfo("Starting Parallax inference server..."); - this->ShowInfo("Server will be accessible at http://localhost:3000"); - this->ShowInfo("Press Ctrl+C to stop the server\n"); - - if (!RunParallaxScript(context)) { - this->ShowError("Failed to start Parallax server"); - return CommandResult::ExecutionError; - } - - this->ShowInfo("Parallax server stopped."); - return CommandResult::Success; - } - - void ShowHelpImpl() { - std::cout << "Usage: parallax run [options]\n\n"; - std::cout - << "Run Parallax distributed inference server directly in WSL.\n\n"; - std::cout << "This command will:\n"; - std::cout << " 1. Check if ~/parallax/src/parallax/launch.py exists\n"; - std::cout << " 2. Start Parallax inference server with default " - "configuration\n\n"; - std::cout << "Default Configuration:\n"; - std::cout << " Model: Qwen/Qwen3-0.6B\n"; - std::cout << " Host: 0.0.0.0\n"; - std::cout << " Port: 3000\n"; - std::cout << " Max Batch Size: 8\n"; - std::cout << " Start Layer: 0\n"; - std::cout << " End Layer: 28\n\n"; - std::cout << "Options:\n"; - std::cout << " --help, -h Show this help message\n\n"; - std::cout - << "Note: The server will be accessible at http://localhost:3000\n"; - std::cout << " Use 'parallax stop' to stop the running server.\n"; - } - - private: - bool CheckLaunchScriptExists(const CommandContext& context); - bool IsParallaxProcessRunning(const CommandContext& context); - bool RunParallaxScript(const CommandContext& context); -}; - -// Join command - join distributed inference cluster as a node -class ModelJoinCommand : public WSLCommand { - public: - std::string GetName() const override { return "join"; } - std::string GetDescription() const override { - return "Join distributed inference cluster as a node"; - } - - EnvironmentRequirements GetEnvironmentRequirements() { - EnvironmentRequirements req; - req.need_wsl = true; - req.sync_proxy = true; - return req; - } - - CommandResult ValidateArgsImpl(CommandContext& context); - CommandResult ExecuteImpl(const CommandContext& context); - void ShowHelpImpl(); - - private: - std::string BuildJoinCommand(const CommandContext& context); - std::string EscapeForShell(const std::string& arg); -}; - -} // namespace commands +#pragma once + +#include "base_command.h" +#include "utils/wsl_process.h" +#include + +namespace parallax { +namespace commands { + +// Run command - directly run Parallax Python script in WSL +class ModelRunCommand : public WSLCommand { + public: + std::string GetName() const override { return "run"; } + std::string GetDescription() const override { + return "Run Parallax inference server directly in WSL"; + } + + EnvironmentRequirements GetEnvironmentRequirements() { + EnvironmentRequirements req; + req.need_wsl = true; + req.sync_proxy = false; + return req; + } + + CommandResult ValidateArgsImpl(CommandContext& context) { + // Check if it's a help request + if (context.args.size() == 1 && + (context.args[0] == "--help" || context.args[0] == "-h")) { + this->ShowHelpImpl(); + return CommandResult::Success; + } + + // run command can be executed with any user-provided parameters + return CommandResult::Success; + } + + CommandResult ExecuteImpl(const CommandContext& context) { + // Check if launch.py exists + // if (!CheckLaunchScriptExists(context)) { + // this->ShowError( + // "Parallax launch script not found at " + // "~/parallax/src/parallax/launch.py"); + // this->ShowError( + // "Please run 'parallax check' to verify your environment " + // "setup."); + // return CommandResult::ExecutionError; + // } + + // Check if there are already running processes + // if (IsParallaxProcessRunning(context)) { + // this->ShowError( + // "Parallax server is already running. Use 'parallax stop' to " + // "stop it first."); + // return CommandResult::ExecutionError; + // } + + // Start Parallax server + this->ShowInfo("Starting Parallax inference server..."); + this->ShowInfo("Server will be accessible at http://localhost:3000"); + this->ShowInfo("Press Ctrl+C to stop the server\n"); + + if (!RunParallaxScript(context)) { + this->ShowError("Failed to start Parallax server"); + return CommandResult::ExecutionError; + } + + this->ShowInfo("Parallax server stopped."); + return CommandResult::Success; + } + + void ShowHelpImpl() { + std::cout << "Usage: parallax run [args...]\n\n"; + std::cout + << "Run Parallax distributed inference server directly in WSL.\n\n"; + std::cout << "This command will:\n"; + std::cout << " 1. Change to ~/parallax directory\n"; + std::cout << " 2. Activate the Python virtual environment\n"; + std::cout << " 3. Set proxy environment variables (if configured)\n"; + std::cout << " 4. Execute 'parallax run' with your arguments\n\n"; + std::cout << "Arguments:\n"; + std::cout << " args... Arguments to pass to parallax run " + "(optional)\n\n"; + std::cout << "Options:\n"; + std::cout << " --help, -h Show this help message\n\n"; + std::cout << "Examples:\n"; + std::cout + << " parallax run # Execute: parallax " + "run\n"; + std::cout << " parallax run -m Qwen/Qwen3-0.6B # Execute: parallax " + "run -m Qwen/Qwen3-0.6B\n"; + std::cout + << " parallax run --port 8080 # Execute: parallax " + "run --port 8080\n\n"; + std::cout << "Note: All arguments will be passed to the built-in " + "parallax run script\n"; + std::cout << " in the Parallax Python virtual environment.\n"; + } + + private: + bool CheckLaunchScriptExists(const CommandContext& context); + bool IsParallaxProcessRunning(const CommandContext& context); + bool RunParallaxScript(const CommandContext& context); + std::string BuildRunCommand(const CommandContext& context); +}; + +// Join command - join distributed inference cluster as a node +class ModelJoinCommand : public WSLCommand { + public: + std::string GetName() const override { return "join"; } + std::string GetDescription() const override { + return "Join distributed inference cluster as a node"; + } + + EnvironmentRequirements GetEnvironmentRequirements() { + EnvironmentRequirements req; + req.need_wsl = true; + req.sync_proxy = true; + return req; + } + + CommandResult ValidateArgsImpl(CommandContext& context); + CommandResult ExecuteImpl(const CommandContext& context); + void ShowHelpImpl(); + + private: + std::string BuildJoinCommand(const CommandContext& context); +}; + +} // namespace commands } // namespace parallax \ No newline at end of file