diff --git a/.rubocop.yml b/.rubocop.yml index 03a3013..b02e5cc 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -76,6 +76,10 @@ RSpec/IndexedLet: - '^error\d+$' # Allow error1, error2, etc. - '^suggestion\d+$' # Allow suggestion1, suggestion2, etc. +RSpec/SpecFilePathFormat: + CustomTransform: + RCon: rcon + Style/HashEachMethods: Exclude: - lib/factorix/cli/commands/cache/stat.rb # cache.each is not Hash#each, it's Cache::Base#each diff --git a/CHANGELOG.md b/CHANGELOG.md index 04647e1..830025f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ ## [Unreleased] +### Added + +- Add `rcon exec` command to execute a Factorio console command via RCon (#87) +- Add `rcon eval` command to evaluate a Lua script in a running Factorio server via RCon (#87) + ## [0.12.0] - 2026-04-21 ### Added diff --git a/README.md b/README.md index ff6bc0f..b8a4113 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ Factorix simplifies Factorio MOD management by providing: - **Blueprint Conversion**: Decode/encode Factorio blueprint strings to/from JSON - **MOD Portal Integration**: Upload new MODs or update existing ones, edit metadata - **Game Control**: Launch Factorio from the command line +- **RCon**: Execute console commands and Lua scripts on a running Factorio server via RCon - **Game Download**: Download Factorio game files (alpha, expansion, demo, headless) - **Cross-platform Support**: Works on Windows, Linux, macOS, and WSL diff --git a/completion/_factorix.bash b/completion/_factorix.bash index 3969353..2fd962c 100644 --- a/completion/_factorix.bash +++ b/completion/_factorix.bash @@ -17,7 +17,7 @@ _factorix() { local confirmable_opts="-y --yes" # Top-level commands - local commands="version man launch path download blueprint mod cache completion" + local commands="version man launch path download blueprint mod cache rcon completion" # blueprint subcommands local blueprint_commands="decode encode" @@ -28,6 +28,10 @@ _factorix() { # cache subcommands local cache_commands="stat evict" + # rcon subcommands + local rcon_commands="exec eval" + local rcon_opts="--host --port --password" + # mod image subcommands local image_commands="list add edit" @@ -110,6 +114,21 @@ _factorix() { fi return ;; + rcon) + if [[ $cword -eq 2 ]]; then + COMPREPLY=($(compgen -W "$rcon_commands" -- "$cur")) + else + case "${words[2]}" in + exec|eval) + COMPREPLY=($(compgen -W "$global_opts $rcon_opts" -- "$cur")) + ;; + *) + COMPREPLY=($(compgen -W "$global_opts" -- "$cur")) + ;; + esac + fi + return + ;; mod) if [[ $cword -eq 2 ]]; then COMPREPLY=($(compgen -W "$mod_commands" -- "$cur")) diff --git a/completion/_factorix.fish b/completion/_factorix.fish index 0e889fa..5043cb3 100644 --- a/completion/_factorix.fish +++ b/completion/_factorix.fish @@ -107,15 +107,16 @@ complete -c factorix -l log-level -d 'Set log level' -xa 'debug info warn error complete -c factorix -s q -l quiet -d 'Suppress non-essential output' # Top-level commands -complete -c factorix -n "not __factorix_using_command version; and not __factorix_using_command man; and not __factorix_using_command launch; and not __factorix_using_command path; and not __factorix_using_command download; and not __factorix_using_command blueprint; and not __factorix_using_command mod; and not __factorix_using_command cache; and not __factorix_using_command completion" -a version -d 'Display Factorix version' -complete -c factorix -n "not __factorix_using_command version; and not __factorix_using_command man; and not __factorix_using_command launch; and not __factorix_using_command path; and not __factorix_using_command download; and not __factorix_using_command blueprint; and not __factorix_using_command mod; and not __factorix_using_command cache; and not __factorix_using_command completion" -a man -d 'Display the Factorix manual page' -complete -c factorix -n "not __factorix_using_command version; and not __factorix_using_command man; and not __factorix_using_command launch; and not __factorix_using_command path; and not __factorix_using_command download; and not __factorix_using_command blueprint; and not __factorix_using_command mod; and not __factorix_using_command cache; and not __factorix_using_command completion" -a launch -d 'Launch Factorio game' -complete -c factorix -n "not __factorix_using_command version; and not __factorix_using_command man; and not __factorix_using_command launch; and not __factorix_using_command path; and not __factorix_using_command download; and not __factorix_using_command blueprint; and not __factorix_using_command mod; and not __factorix_using_command cache; and not __factorix_using_command completion" -a path -d 'Display Factorio and Factorix paths' -complete -c factorix -n "not __factorix_using_command version; and not __factorix_using_command man; and not __factorix_using_command launch; and not __factorix_using_command path; and not __factorix_using_command download; and not __factorix_using_command blueprint; and not __factorix_using_command mod; and not __factorix_using_command cache; and not __factorix_using_command completion" -a download -d 'Download Factorio game files' -complete -c factorix -n "not __factorix_using_command version; and not __factorix_using_command man; and not __factorix_using_command launch; and not __factorix_using_command path; and not __factorix_using_command download; and not __factorix_using_command blueprint; and not __factorix_using_command mod; and not __factorix_using_command cache; and not __factorix_using_command completion" -a blueprint -d 'Blueprint decode/encode commands' -complete -c factorix -n "not __factorix_using_command version; and not __factorix_using_command man; and not __factorix_using_command launch; and not __factorix_using_command path; and not __factorix_using_command download; and not __factorix_using_command blueprint; and not __factorix_using_command mod; and not __factorix_using_command cache; and not __factorix_using_command completion" -a mod -d 'MOD management commands' -complete -c factorix -n "not __factorix_using_command version; and not __factorix_using_command man; and not __factorix_using_command launch; and not __factorix_using_command path; and not __factorix_using_command download; and not __factorix_using_command blueprint; and not __factorix_using_command mod; and not __factorix_using_command cache; and not __factorix_using_command completion" -a cache -d 'Cache management commands' -complete -c factorix -n "not __factorix_using_command version; and not __factorix_using_command man; and not __factorix_using_command launch; and not __factorix_using_command path; and not __factorix_using_command download; and not __factorix_using_command blueprint; and not __factorix_using_command mod; and not __factorix_using_command cache; and not __factorix_using_command completion" -a completion -d 'Generate shell completion script' +complete -c factorix -n "not __factorix_using_command version; and not __factorix_using_command man; and not __factorix_using_command launch; and not __factorix_using_command path; and not __factorix_using_command download; and not __factorix_using_command blueprint; and not __factorix_using_command mod; and not __factorix_using_command cache; and not __factorix_using_command rcon; and not __factorix_using_command completion" -a version -d 'Display Factorix version' +complete -c factorix -n "not __factorix_using_command version; and not __factorix_using_command man; and not __factorix_using_command launch; and not __factorix_using_command path; and not __factorix_using_command download; and not __factorix_using_command blueprint; and not __factorix_using_command mod; and not __factorix_using_command cache; and not __factorix_using_command rcon; and not __factorix_using_command completion" -a man -d 'Display the Factorix manual page' +complete -c factorix -n "not __factorix_using_command version; and not __factorix_using_command man; and not __factorix_using_command launch; and not __factorix_using_command path; and not __factorix_using_command download; and not __factorix_using_command blueprint; and not __factorix_using_command mod; and not __factorix_using_command cache; and not __factorix_using_command rcon; and not __factorix_using_command completion" -a launch -d 'Launch Factorio game' +complete -c factorix -n "not __factorix_using_command version; and not __factorix_using_command man; and not __factorix_using_command launch; and not __factorix_using_command path; and not __factorix_using_command download; and not __factorix_using_command blueprint; and not __factorix_using_command mod; and not __factorix_using_command cache; and not __factorix_using_command rcon; and not __factorix_using_command completion" -a path -d 'Display Factorio and Factorix paths' +complete -c factorix -n "not __factorix_using_command version; and not __factorix_using_command man; and not __factorix_using_command launch; and not __factorix_using_command path; and not __factorix_using_command download; and not __factorix_using_command blueprint; and not __factorix_using_command mod; and not __factorix_using_command cache; and not __factorix_using_command rcon; and not __factorix_using_command completion" -a download -d 'Download Factorio game files' +complete -c factorix -n "not __factorix_using_command version; and not __factorix_using_command man; and not __factorix_using_command launch; and not __factorix_using_command path; and not __factorix_using_command download; and not __factorix_using_command blueprint; and not __factorix_using_command mod; and not __factorix_using_command cache; and not __factorix_using_command rcon; and not __factorix_using_command completion" -a blueprint -d 'Blueprint decode/encode commands' +complete -c factorix -n "not __factorix_using_command version; and not __factorix_using_command man; and not __factorix_using_command launch; and not __factorix_using_command path; and not __factorix_using_command download; and not __factorix_using_command blueprint; and not __factorix_using_command mod; and not __factorix_using_command cache; and not __factorix_using_command rcon; and not __factorix_using_command completion" -a mod -d 'MOD management commands' +complete -c factorix -n "not __factorix_using_command version; and not __factorix_using_command man; and not __factorix_using_command launch; and not __factorix_using_command path; and not __factorix_using_command download; and not __factorix_using_command blueprint; and not __factorix_using_command mod; and not __factorix_using_command cache; and not __factorix_using_command rcon; and not __factorix_using_command completion" -a cache -d 'Cache management commands' +complete -c factorix -n "not __factorix_using_command version; and not __factorix_using_command man; and not __factorix_using_command launch; and not __factorix_using_command path; and not __factorix_using_command download; and not __factorix_using_command blueprint; and not __factorix_using_command mod; and not __factorix_using_command cache; and not __factorix_using_command rcon; and not __factorix_using_command completion" -a rcon -d 'RCon commands' +complete -c factorix -n "not __factorix_using_command version; and not __factorix_using_command man; and not __factorix_using_command launch; and not __factorix_using_command path; and not __factorix_using_command download; and not __factorix_using_command blueprint; and not __factorix_using_command mod; and not __factorix_using_command cache; and not __factorix_using_command rcon; and not __factorix_using_command completion" -a completion -d 'Generate shell completion script' # blueprint subcommands complete -c factorix -n "__factorix_using_command blueprint" -a decode -d 'Decode a Factorio blueprint string to JSON' @@ -143,6 +144,20 @@ complete -c factorix -n "__factorix_using_command download" -s c -l channel -d ' complete -c factorix -n "__factorix_using_command download" -s d -l directory -d 'Download directory' -ra '(__fish_complete_directories)' complete -c factorix -n "__factorix_using_command download" -s o -l output -d 'Output filename' -r +# rcon subcommands +complete -c factorix -n "__factorix_using_command rcon" -a exec -d 'Execute a Factorio console command' +complete -c factorix -n "__factorix_using_command rcon" -a eval -d 'Evaluate a Lua script' + +# rcon exec options +complete -c factorix -n "__factorix_using_subcommand rcon exec" -l host -d 'RCon host' -r +complete -c factorix -n "__factorix_using_subcommand rcon exec" -l port -d 'RCon port' -r +complete -c factorix -n "__factorix_using_subcommand rcon exec" -l password -d 'RCon password' -r + +# rcon eval options +complete -c factorix -n "__factorix_using_subcommand rcon eval" -l host -d 'RCon host' -r +complete -c factorix -n "__factorix_using_subcommand rcon eval" -l port -d 'RCon port' -r +complete -c factorix -n "__factorix_using_subcommand rcon eval" -l password -d 'RCon password' -r + # cache subcommands complete -c factorix -n "__factorix_using_command cache" -a stat -d 'Display cache statistics' complete -c factorix -n "__factorix_using_command cache" -a evict -d 'Evict cache entries' diff --git a/completion/_factorix.zsh b/completion/_factorix.zsh index 467efb0..2ac01b4 100644 --- a/completion/_factorix.zsh +++ b/completion/_factorix.zsh @@ -43,6 +43,7 @@ _factorix() { 'blueprint:Blueprint decode/encode commands' 'mod:MOD management commands' 'cache:Cache management commands' + 'rcon:RCon commands' 'completion:Generate shell completion script' ) _describe -t commands 'factorix command' commands @@ -78,6 +79,9 @@ _factorix() { cache) _factorix_cache ;; + rcon) + _factorix_rcon + ;; esac ;; esac @@ -461,6 +465,56 @@ _factorix_mod_settings() { esac } +_factorix_rcon() { + local context state state_descr line + typeset -A opt_args + + local -a global_opts + global_opts=( + '(-c --config-path)'{-c,--config-path}'[Path to configuration file]:config file:_files' + '--log-level[Set log level]:level:(debug info warn error fatal)' + '(-q --quiet)'{-q,--quiet}'[Suppress non-essential output]' + ) + + local -a rcon_opts + rcon_opts=( + '--host[RCon host]:host:' + '--port[RCon port]:port:' + '--password[RCon password]:password:' + ) + + _arguments -C \ + '1:subcommand:->subcommand' \ + '*::arg:->args' + + case $state in + subcommand) + local -a subcommands + subcommands=( + 'exec:Execute a Factorio console command' + 'eval:Evaluate a Lua script' + ) + _describe -t subcommands 'rcon subcommand' subcommands + ;; + args) + case $line[1] in + exec) + _arguments \ + $global_opts \ + $rcon_opts \ + '1:console command:' + ;; + eval) + _arguments \ + $global_opts \ + $rcon_opts \ + '1:Lua script:' + ;; + esac + ;; + esac +} + _factorix_cache() { local context state state_descr line typeset -A opt_args diff --git a/doc/components/cli.md b/doc/components/cli.md index 2366d4f..dbad239 100644 --- a/doc/components/cli.md +++ b/doc/components/cli.md @@ -571,6 +571,58 @@ Validate the structure of a MOD changelog.txt file. **Use case**: CI validation of changelog structure before release +### RCon::Exec + +Execute a Factorio console command via RCon. + +**Requirements**: Factorio server must be running with RCon enabled. + +**Arguments**: +- `command` (required) - Console command to execute (e.g. `/server-save`) + +**Options**: +- `--host` - RCon host (default: from config, fallback `localhost`) +- `--port` - RCon port (default: from config, fallback `27015`) +- `--password` - RCon password (default: from config) + +**Configuration**: Connection settings can be set in the config file: +```ruby +config.rcon.host = "192.168.1.10" +config.rcon.port = 25575 +config.rcon.password = "secret" +``` + +**Output**: Server response, if any. Empty responses are suppressed. + +**Errors**: +- `RConConnectionError` - server unreachable +- `RConAuthenticationError` - wrong password + +**Examples**: +```bash +factorix rcon exec /server-save +factorix rcon exec --host 192.168.1.10 --port 25575 --password secret /server-save +``` + +### RCon::Eval + +Evaluate a Lua script in a running Factorio server via RCon. Wraps the script in `/c