mirror of
https://github.com/openai/codex.git
synced 2026-11-01 16:17:58 +00:00
Improve codex remote-control CLI UX (#22878)
## Description
This PR makes `codex remote-control` behave like a foreground CLI
command by default. Running it now starts remote control, waits for
readiness, prints a clear status message with the machine name, and
stays alive until Ctrl-C.
Users who want daemon behavior can use `codex remote-control start`, and
`codex remote-control stop` now prints concise human-readable output.
`--json` remains available for scripts.
Implementation-wise, this now verifies the real app-server state instead
of just assuming startup worked. The CLI starts or connects to
app-server, probes its control socket, calls the `remoteControl/enable`
API, and waits for the remote-control status response/notification
before printing success.
For daemon mode, `codex remote-control start` also reports which managed
app-server binary was used, including its path and best-effort `codex
--version`, so failures are easier to diagnose.
## Examples
Example output:
```
> codex remote-control
Starting app-server with remote control enabled...
This machine is available for remote control as com-97826.
Press Ctrl-C to stop.
```
Error case using daemon (currently expected based on our publicly
released CLI version):
```
> ./target/debug/codex remote-control start
Starting app-server daemon with remote control enabled...
Error: app server did not become ready on /Users/owen/.codex/app-server-control/app-server-control.sock
Daemon used app-server:
path: /Users/owen/.codex/packages/standalone/current/codex
version: 0.130.0
Managed app-server stderr (/Users/owen/.codex/app-server-daemon/app-server.stderr.log):
error: unexpected argument '--remote-control' found
Usage: codex app-server [OPTIONS] [COMMAND]
For more information, try '--help'.
Caused by:
0: failed to connect to /Users/owen/.codex/app-server-control/app-server-control.sock
1: No such file or directory (os error 2)
```
## What changed
- `codex remote-control` now runs remote control in the foreground and
prints a Ctrl-C stop hint.
- `codex remote-control start` starts the daemon and waits for remote
control readiness before reporting success.
- `codex remote-control stop` reports stopped/not-running status in
plain language.
- Startup failures now include recent managed app-server stderr to make
daemon issues easier to diagnose.
- Added coverage for CLI output, readiness waiting, foreground shutdown,
and stderr log tailing.
This commit is contained in:
+14
-45
@@ -52,6 +52,7 @@ mod doctor;
|
||||
mod marketplace_cmd;
|
||||
mod mcp_cmd;
|
||||
mod plugin_cmd;
|
||||
mod remote_control_cmd;
|
||||
mod state_db_recovery;
|
||||
#[cfg(not(windows))]
|
||||
mod wsl_paths;
|
||||
@@ -59,6 +60,7 @@ mod wsl_paths;
|
||||
use crate::mcp_cmd::McpCli;
|
||||
use crate::plugin_cmd::PluginCli;
|
||||
use crate::plugin_cmd::PluginSubcommand;
|
||||
use crate::remote_control_cmd::RemoteControlCommand;
|
||||
use doctor::DoctorCommand;
|
||||
use state_db_recovery as local_state_db;
|
||||
|
||||
@@ -565,21 +567,6 @@ struct AppServerBootstrapCommand {
|
||||
remote_control: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
struct RemoteControlCommand {
|
||||
#[command(subcommand)]
|
||||
subcommand: Option<RemoteControlSubcommand>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, clap::Subcommand)]
|
||||
enum RemoteControlSubcommand {
|
||||
/// Start the app-server daemon with remote control enabled.
|
||||
Start,
|
||||
|
||||
/// Stop the app-server daemon.
|
||||
Stop,
|
||||
}
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
struct GenerateTsCommand {
|
||||
/// Output directory where .ts files will be written
|
||||
@@ -1070,24 +1057,18 @@ async fn cli_main(arg0_paths: Arg0DispatchPaths) -> anyhow::Result<()> {
|
||||
}
|
||||
}
|
||||
Some(Subcommand::RemoteControl(remote_control_cli)) => {
|
||||
let subcommand_name = remote_control_subcommand_name(&remote_control_cli);
|
||||
let subcommand_name = remote_control_cli.subcommand_name();
|
||||
reject_remote_mode_for_subcommand(
|
||||
root_remote.as_deref(),
|
||||
root_remote_auth_token_env.as_deref(),
|
||||
subcommand_name,
|
||||
)?;
|
||||
match remote_control_cli
|
||||
.subcommand
|
||||
.unwrap_or(RemoteControlSubcommand::Start)
|
||||
{
|
||||
RemoteControlSubcommand::Start => {
|
||||
let output = codex_app_server_daemon::ensure_remote_control_started().await?;
|
||||
println!("{}", serde_json::to_string(&output)?);
|
||||
}
|
||||
RemoteControlSubcommand::Stop => {
|
||||
print_app_server_daemon_output(AppServerLifecycleCommand::Stop).await?;
|
||||
}
|
||||
}
|
||||
remote_control_cmd::run(
|
||||
remote_control_cli,
|
||||
arg0_paths.clone(),
|
||||
root_config_overrides,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
#[cfg(any(target_os = "macos", target_os = "windows"))]
|
||||
Some(Subcommand::App(app_cli)) => {
|
||||
@@ -1892,9 +1873,7 @@ fn unsupported_subcommand_name_for_strict_config(
|
||||
Some(Subcommand::AppServer(app_server)) => {
|
||||
Some(app_server_subcommand_name(app_server.subcommand.as_ref()))
|
||||
}
|
||||
Some(Subcommand::RemoteControl(remote_control)) => {
|
||||
Some(remote_control_subcommand_name(remote_control))
|
||||
}
|
||||
Some(Subcommand::RemoteControl(remote_control)) => Some(remote_control.subcommand_name()),
|
||||
Some(Subcommand::Mcp(_)) => Some("mcp"),
|
||||
Some(Subcommand::Plugin(_)) => Some("plugin"),
|
||||
#[cfg(any(target_os = "macos", target_os = "windows"))]
|
||||
@@ -1947,14 +1926,6 @@ fn reject_remote_mode_for_app_server_subcommand(
|
||||
reject_remote_mode_for_subcommand(remote, remote_auth_token_env, subcommand_name)
|
||||
}
|
||||
|
||||
fn remote_control_subcommand_name(command: &RemoteControlCommand) -> &'static str {
|
||||
match command.subcommand {
|
||||
None => "remote-control",
|
||||
Some(RemoteControlSubcommand::Start) => "remote-control start",
|
||||
Some(RemoteControlSubcommand::Stop) => "remote-control stop",
|
||||
}
|
||||
}
|
||||
|
||||
fn app_server_subcommand_name(subcommand: Option<&AppServerSubcommand>) -> &'static str {
|
||||
match subcommand {
|
||||
None => "app-server",
|
||||
@@ -2974,12 +2945,10 @@ mod tests {
|
||||
fn reject_remote_flag_for_remote_control() {
|
||||
let cli = MultitoolCli::try_parse_from(["codex", "--remote", "unix://", "remote-control"])
|
||||
.expect("parse");
|
||||
assert_matches!(
|
||||
cli.subcommand,
|
||||
Some(Subcommand::RemoteControl(RemoteControlCommand {
|
||||
subcommand: None
|
||||
}))
|
||||
);
|
||||
let Some(Subcommand::RemoteControl(remote_control)) = &cli.subcommand else {
|
||||
panic!("expected remote-control subcommand");
|
||||
};
|
||||
assert_eq!(remote_control.subcommand_name(), "remote-control");
|
||||
|
||||
let err = reject_remote_mode_for_subcommand(
|
||||
cli.remote.remote.as_deref(),
|
||||
|
||||
Reference in New Issue
Block a user