From 9cd26d6fa1c1362f1d4db92dd362a53ebf57419a Mon Sep 17 00:00:00 2001 From: Owen Lin Date: Wed, 6 May 2026 15:01:49 -0700 Subject: [PATCH] add remote-control command --- codex-rs/cli/src/main.rs | 68 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/codex-rs/cli/src/main.rs b/codex-rs/cli/src/main.rs index f3545629c1..b197790e8a 100644 --- a/codex-rs/cli/src/main.rs +++ b/codex-rs/cli/src/main.rs @@ -130,6 +130,9 @@ enum Subcommand { /// [experimental] Run the app server or related tooling. AppServer(AppServerCommand), + /// [experimental] Start a headless app-server with remote control enabled. + RemoteControl, + /// Launch the Codex desktop app (opens the app installer if missing). #[cfg(any(target_os = "macos", target_os = "windows"))] App(app_cmd::AppCommand), @@ -736,6 +739,14 @@ struct FeatureSetArgs { feature: String, } +const REMOTE_CONTROL_FEATURE_OVERRIDE: &str = "features.remote_control=true"; + +fn enable_remote_control_for_invocation(config_overrides: &mut CliConfigOverrides) { + config_overrides + .raw_overrides + .push(REMOTE_CONTROL_FEATURE_OVERRIDE.to_string()); +} + fn stage_str(stage: Stage) -> &'static str { match stage { Stage::UnderDevelopment => "under development", @@ -916,6 +927,24 @@ async fn cli_main(arg0_paths: Arg0DispatchPaths) -> anyhow::Result<()> { } } } + Some(Subcommand::RemoteControl) => { + reject_remote_mode_for_subcommand( + root_remote.as_deref(), + root_remote_auth_token_env.as_deref(), + "remote-control", + )?; + enable_remote_control_for_invocation(&mut root_config_overrides); + codex_app_server::run_main_with_transport( + arg0_paths.clone(), + root_config_overrides, + codex_config::LoaderOverrides::default(), + /*default_analytics_enabled*/ false, + codex_app_server::AppServerTransport::Off, + codex_protocol::protocol::SessionSource::VSCode, + codex_app_server::AppServerWebsocketAuthSettings::default(), + ) + .await?; + } #[cfg(any(target_os = "macos", target_os = "windows"))] Some(Subcommand::App(app_cli)) => { reject_remote_mode_for_subcommand( @@ -2296,6 +2325,45 @@ mod tests { assert!(app_server.analytics_default_enabled); } + #[test] + fn remote_control_override_is_appended_after_root_toggles() { + let mut config_overrides = CliConfigOverrides::default(); + config_overrides + .raw_overrides + .push("features.remote_control=false".to_string()); + + enable_remote_control_for_invocation(&mut config_overrides); + + assert_eq!( + config_overrides.raw_overrides, + vec![ + "features.remote_control=false".to_string(), + REMOTE_CONTROL_FEATURE_OVERRIDE.to_string(), + ] + ); + } + + #[test] + fn reject_remote_flag_for_remote_control() { + let cli = MultitoolCli::try_parse_from([ + "codex", + "--remote", + "ws://127.0.0.1:1234", + "remote-control", + ]) + .expect("parse"); + assert_matches!(cli.subcommand, Some(Subcommand::RemoteControl)); + + let err = reject_remote_mode_for_subcommand( + cli.remote.remote.as_deref(), + cli.remote.remote_auth_token_env.as_deref(), + "remote-control", + ) + .expect_err("remote-control should reject root --remote"); + + assert!(err.to_string().contains("remote-control")); + } + #[test] fn remote_flag_parses_for_interactive_root() { let cli = MultitoolCli::try_parse_from(["codex", "--remote", "ws://127.0.0.1:4500"])