mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Improve remote-control daemon UX (#22562)
## Why `codex remote-control` manages the app-server daemon with `remote_control` enabled, but it previously only exposed an implicit start path. Once started, there was no obvious top-level `remote-control` command for stopping the daemon; users had to know about the lower-level `codex app-server daemon stop` command. The startup failure for missing managed installs was also ambiguous. `codex remote-control` and daemon bootstrap require the standalone Codex install under `CODEX_HOME/packages/standalone/current/codex`, but the old error only said to install Codex first, which is unclear when another `codex` binary is already on PATH. Now we add an explicit instruction for how to get the standalone Codex install. ## What changed - Converts `codex remote-control` into a command group while preserving bare `codex remote-control` as the existing start behavior. - Adds `codex remote-control start` as the explicit start path. - Adds `codex remote-control stop`, which maps to app-server daemon stop. - Updates the shared daemon managed-install error to name the missing standalone path, explain why that install is required, provide the installer command, and tell users to rerun the command they just tried. ## Verification - `cargo test -p codex-app-server-daemon` - `cargo test -p codex-cli` - `./target/debug/codex remote-control --help`
This commit is contained in:
committed by
GitHub
Unverified
parent
e33cf9ae28
commit
512f8f8012
@@ -567,9 +567,13 @@ impl Daemon {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let managed_codex_path = self.managed_codex_bin.display();
|
||||
Err(anyhow!(
|
||||
"managed standalone Codex install not found at {}; install Codex first",
|
||||
self.managed_codex_bin.display()
|
||||
"managed standalone Codex install not found at {managed_codex_path}\n\n\
|
||||
This command requires the standalone install managed by the Codex installer, because \
|
||||
the daemon starts and updates app-server from that fixed path.\n\n\
|
||||
Install it with:\n curl -fsSL https://chatgpt.com/codex/install.sh | sh\n\n\
|
||||
Then rerun the command you just tried."
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@@ -131,8 +131,8 @@ enum Subcommand {
|
||||
/// [experimental] Run the app server or related tooling.
|
||||
AppServer(AppServerCommand),
|
||||
|
||||
/// [experimental] Ensure the app-server daemon is running with remote control enabled.
|
||||
RemoteControl,
|
||||
/// [experimental] Manage the app-server daemon with remote control enabled.
|
||||
RemoteControl(RemoteControlCommand),
|
||||
|
||||
/// Launch the Codex desktop app (opens the app installer if missing).
|
||||
#[cfg(any(target_os = "macos", target_os = "windows"))]
|
||||
@@ -563,6 +563,21 @@ 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
|
||||
@@ -1024,14 +1039,25 @@ async fn cli_main(arg0_paths: Arg0DispatchPaths) -> anyhow::Result<()> {
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(Subcommand::RemoteControl) => {
|
||||
Some(Subcommand::RemoteControl(remote_control_cli)) => {
|
||||
let subcommand_name = remote_control_subcommand_name(&remote_control_cli);
|
||||
reject_remote_mode_for_subcommand(
|
||||
root_remote.as_deref(),
|
||||
root_remote_auth_token_env.as_deref(),
|
||||
"remote-control",
|
||||
subcommand_name,
|
||||
)?;
|
||||
let output = codex_app_server_daemon::ensure_remote_control_started().await?;
|
||||
println!("{}", serde_json::to_string(&output)?);
|
||||
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?;
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(any(target_os = "macos", target_os = "windows"))]
|
||||
Some(Subcommand::App(app_cli)) => {
|
||||
@@ -1713,7 +1739,9 @@ 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) => Some("remote-control"),
|
||||
Some(Subcommand::RemoteControl(remote_control)) => {
|
||||
Some(remote_control_subcommand_name(remote_control))
|
||||
}
|
||||
Some(Subcommand::Mcp(_)) => Some("mcp"),
|
||||
Some(Subcommand::Plugin(_)) => Some("plugin"),
|
||||
#[cfg(any(target_os = "macos", target_os = "windows"))]
|
||||
@@ -1766,6 +1794,14 @@ 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",
|
||||
@@ -2659,7 +2695,12 @@ 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));
|
||||
assert_matches!(
|
||||
cli.subcommand,
|
||||
Some(Subcommand::RemoteControl(RemoteControlCommand {
|
||||
subcommand: None
|
||||
}))
|
||||
);
|
||||
|
||||
let err = reject_remote_mode_for_subcommand(
|
||||
cli.remote.remote.as_deref(),
|
||||
|
||||
Reference in New Issue
Block a user