Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions crates/launcher/src/console.rs
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,9 @@ fn desktop_json(installation: &DesktopInstallation, process_ids: &[u32]) -> Valu
"version": installation.version,
"build": installation.build,
"installRoot": installation.install_root.display().to_string(),
"packagedCodexCli": installation.packaged_codex_cli.display().to_string(),
"executableCodexCli": installation.executable_codex_cli.display().to_string(),
"codexCliSource": installation.codex_cli_source.as_str(),
"processIds": process_ids,
})
}
Expand Down Expand Up @@ -362,6 +365,38 @@ pub fn inspect_json(
mod tests {
use super::*;

#[test]
fn inspect_desktop_distinguishes_packaged_and_selected_cli_with_source() {
let root = std::env::temp_dir().join("inspect-cli");
let installation = DesktopInstallation {
identity: DesktopIdentity::LinuxPackage {
package_name: "chatgpt".into(),
brand: "chatgpt".into(),
flavor: "prod".into(),
},
version: "1.2.3".into(),
build: "1.2.3".into(),
asar_integrity: "sha256:fixture".into(),
install_root: root.clone(),
desktop_launcher: root.join("launcher"),
desktop_executable: root.join("ChatGPT"),
packaged_codex_cli: root.join("resources/codex"),
executable_codex_cli: root.join("independent/codex"),
codex_cli_source: codexhost_platform::CodexCliSource::Environment,
};
let document = desktop_json(&installation, &[123]);
assert_eq!(
document["packagedCodexCli"],
installation.packaged_codex_cli.display().to_string()
);
assert_eq!(
document["executableCodexCli"],
installation.executable_codex_cli.display().to_string()
);
assert_eq!(document["codexCliSource"], "environment");
assert_eq!(document["processIds"], json!([123]));
}

#[test]
fn console_node_suppresses_only_the_experimental_proxy_warning() {
let command = node_command(&ConsoleCommand {
Expand Down
116 changes: 103 additions & 13 deletions crates/launcher/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ use codexhost_platform::{
APPX_RESUME_ARGUMENT, DesktopProcess, launch_desktop, resume_packaged_application,
};
use codexhost_platform::{
DesktopIdentity, DesktopInstallation, DesktopLaunchMode, SupervisedChild,
CodexCliOverride, DesktopIdentity, DesktopInstallation, DesktopLaunchMode, SupervisedChild,
canonical_existing_file, configure_background_command,
desktop_root_process_ids_for_installation, discover_codex_desktop, node_entrypoint_path,
spawn_supervised,
desktop_root_process_ids_for_installation, discover_codex_desktop_with_cli,
node_entrypoint_path, resolve_codex_cli_override, spawn_supervised,
};
#[cfg(any(target_os = "macos", target_os = "linux"))]
use codexhost_platform::{DesktopSession, launch_desktop_session};
Expand Down Expand Up @@ -129,7 +129,7 @@ impl Error for UnmanagedDesktopConflict {}

fn usage() {
eprintln!(
"usage:\n codexhost\n codexhost inspect [--json] [--custom-install <absolute-directory>]\n codexhost console\n codexhost update\n codexhost launch [--shim <absolute-file>] [--node <absolute-file>] [--host-runtime <absolute-file>] [--desktop-controller <absolute-file>] [--renderer <absolute-file>] [--pi <absolute-file>] [--custom-install <absolute-directory>]\n codexhost broker install|status|stop|uninstall\n codexhost delegate --help\n codexhost harness inspect ...\n codexhost delegate start ...\n codexhost thread send|cancel|read|wait|list ..."
"usage:\n codexhost\n codexhost inspect [--json] [--codex-cli <absolute-file>] [--custom-install <absolute-directory>]\n codexhost console\n codexhost update\n codexhost launch [--shim <absolute-file>] [--node <absolute-file>] [--host-runtime <absolute-file>] [--desktop-controller <absolute-file>] [--renderer <absolute-file>] [--pi <absolute-file>] [--codex-cli <absolute-file>] [--custom-install <absolute-directory>]\n codexhost broker install|status|stop|uninstall\n codexhost delegate --help\n codexhost harness inspect ...\n codexhost delegate start ...\n codexhost thread send|cancel|read|wait|list ..."
);
}

Expand Down Expand Up @@ -286,6 +286,10 @@ fn print_installation(installation: &DesktopInstallation, process_ids: &[u32]) {
"executable_codex_cli={}",
installation.executable_codex_cli.display()
);
println!(
"codex_cli_source={}",
installation.codex_cli_source.as_str()
);
let process_list = process_ids
.iter()
.map(u32::to_string)
Expand All @@ -302,23 +306,40 @@ fn print_installation(installation: &DesktopInstallation, process_ids: &[u32]) {
/// PackageManager.
fn discover_desktop(
custom_install_root: Option<&Path>,
selected_cli: Option<&CodexCliOverride>,
) -> Result<DesktopInstallation, Box<dyn Error>> {
match custom_install_root {
#[cfg(target_os = "windows")]
Some(root) => Ok(codexhost_platform::discover_codex_desktop_from_root(root)?),
Some(root) => {
Ok(codexhost_platform::discover_codex_desktop_from_root_with_cli(root, selected_cli)?)
}
#[cfg(not(target_os = "windows"))]
Some(_) => Err("--custom-install is supported on Windows only".into()),
None => Ok(discover_codex_desktop()?),
None => Ok(discover_codex_desktop_with_cli(selected_cli)?),
}
}

fn inspect(custom_install_root: Option<&Path>, json: bool) -> Result<(), Box<dyn Error>> {
fn inspect(
custom_install_root: Option<&Path>,
codex_cli: Option<&Path>,
json: bool,
) -> Result<(), Box<dyn Error>> {
let discovered = (|| {
let installed = InstalledResources::from_current_executable()?;
let selected_cli = resolve_codex_cli_override(
codex_cli,
env::var_os(codexhost_platform::CODEX_CLI_PATH_ENV).as_deref(),
env::var_os(codexhost_platform::STOCK_CODEX_PATH_ENV).as_deref(),
installed.shim.is_file().then_some(installed.shim.as_path()),
)?;
discover_desktop(custom_install_root, selected_cli.as_ref())
})();
if json {
let document = console::inspect_json(discover_desktop(custom_install_root))?;
let document = console::inspect_json(discovered)?;
println!("{}", serde_json::to_string(&document)?);
return Ok(());
}
let installation = discover_desktop(custom_install_root)?;
let installation = discovered?;
let process_ids = codexhost_platform::desktop_process_ids_for_installation(&installation)?;
print_installation(&installation, &process_ids);
Ok(())
Expand All @@ -333,6 +354,7 @@ struct LaunchOptions {
renderer_extension: Option<PathBuf>,
pi: Option<PathBuf>,
custom_install_root: Option<PathBuf>,
codex_cli: Option<PathBuf>,
}

#[derive(Debug)]
Expand All @@ -344,6 +366,7 @@ struct ResolvedLaunchOptions {
renderer_extension: PathBuf,
pi: Option<PathBuf>,
custom_install_root: Option<PathBuf>,
codex_cli: Option<CodexCliOverride>,
}

fn required_path(arguments: &[String], index: &mut usize, option: &str) -> Result<PathBuf, String> {
Expand All @@ -362,6 +385,7 @@ fn parse_launch_options(arguments: &[String]) -> Result<LaunchOptions, String> {
let mut renderer_extension = None;
let mut pi = None;
let mut custom_install_root = None;
let mut codex_cli = None;
let mut index = 0;
while index < arguments.len() {
match arguments[index].as_str() {
Expand All @@ -381,6 +405,12 @@ fn parse_launch_options(arguments: &[String]) -> Result<LaunchOptions, String> {
renderer_extension = Some(required_path(arguments, &mut index, "--renderer")?)
}
"--pi" => pi = Some(required_path(arguments, &mut index, "--pi")?),
"--codex-cli" => {
if codex_cli.is_some() {
return Err("--codex-cli may only be provided once".into());
}
codex_cli = Some(required_path(arguments, &mut index, "--codex-cli")?);
}
"--custom-install" => {
custom_install_root =
Some(required_path(arguments, &mut index, "--custom-install")?)
Expand All @@ -397,13 +427,15 @@ fn parse_launch_options(arguments: &[String]) -> Result<LaunchOptions, String> {
renderer_extension,
pi,
custom_install_root,
codex_cli,
})
}

/// Parse the options accepted by `codexhost inspect`.
#[derive(Debug, Default, PartialEq, Eq)]
struct InspectOptions {
custom_install_root: Option<PathBuf>,
codex_cli: Option<PathBuf>,
json: bool,
}

Expand All @@ -416,6 +448,12 @@ fn parse_inspect_options(arguments: &[String]) -> Result<InspectOptions, String>
options.custom_install_root =
Some(required_path(arguments, &mut index, "--custom-install")?)
}
"--codex-cli" => {
if options.codex_cli.is_some() {
return Err("--codex-cli may only be provided once".into());
}
options.codex_cli = Some(required_path(arguments, &mut index, "--codex-cli")?);
}
"--json" => options.json = true,
unknown => return Err(format!("unknown inspect option: {unknown}")),
}
Expand Down Expand Up @@ -457,8 +495,16 @@ fn resolve_resource_path(
impl LaunchOptions {
fn resolve(self) -> Result<ResolvedLaunchOptions, Box<dyn Error>> {
let installed = InstalledResources::from_current_executable()?;
let shim = resolve_resource_path(self.shim, &installed.shim, "--shim", "bundled Shim")?;
let codex_cli = resolve_codex_cli_override(
self.codex_cli.as_deref(),
env::var_os(codexhost_platform::CODEX_CLI_PATH_ENV).as_deref(),
env::var_os(codexhost_platform::STOCK_CODEX_PATH_ENV).as_deref(),
Some(&shim),
)?;
Ok(ResolvedLaunchOptions {
shim: resolve_resource_path(self.shim, &installed.shim, "--shim", "bundled Shim")?,
shim,
codex_cli,
node: resolve_resource_path(
self.node,
&installed.node,
Expand Down Expand Up @@ -1029,7 +1075,10 @@ fn launch(
start_launch_console(&options);
let options = options.resolve()?;
startup_trace("resources resolved");
let installation = discover_desktop(options.custom_install_root.as_deref())?;
let installation = discover_desktop(
options.custom_install_root.as_deref(),
options.codex_cli.as_ref(),
)?;
startup_record::desktop(
&installation.version,
&installation.build,
Expand Down Expand Up @@ -1166,7 +1215,10 @@ fn launch(
start_launch_console(&options);
let options = options.resolve()?;
startup_trace("resources resolved");
let installation = discover_desktop(options.custom_install_root.as_deref())?;
let installation = discover_desktop(
options.custom_install_root.as_deref(),
options.codex_cli.as_ref(),
)?;
startup_record::desktop(
&installation.version,
&installation.build,
Expand Down Expand Up @@ -1293,6 +1345,7 @@ fn default_launch_options() -> LaunchOptions {
renderer_extension: None,
pi: None,
custom_install_root: None,
codex_cli: None,
}
}

Expand All @@ -1310,7 +1363,11 @@ fn run(arguments: &[String]) -> Result<(), Box<dyn Error>> {
.custom_install_root
.map(|path| absolute_directory(&path, "--custom-install"))
.transpose()?;
inspect(custom_install_root.as_deref(), options.json)
inspect(
custom_install_root.as_deref(),
options.codex_cli.as_deref(),
options.json,
)
}
Some("console") if arguments.len() == 1 => run_console_command("open"),
Some("update") if arguments.len() == 1 => run_console_command("update"),
Expand Down Expand Up @@ -1611,6 +1668,38 @@ mod tests {
assert!(parse_inspect_options(&["--unknown".into()]).is_err());
}

#[test]
fn native_cli_option_is_accepted_by_launch_and_inspect() {
let path = std::env::temp_dir().join("native-codex");
let arguments = ["--codex-cli".into(), path.display().to_string()];
assert_eq!(
parse_launch_options(&arguments)
.unwrap()
.codex_cli
.as_deref(),
Some(path.as_path())
);
assert_eq!(
parse_inspect_options(&arguments)
.unwrap()
.codex_cli
.as_deref(),
Some(path.as_path())
);
assert!(parse_launch_options(&[]).unwrap().codex_cli.is_none());
assert!(parse_inspect_options(&[]).unwrap().codex_cli.is_none());
assert!(parse_launch_options(&["--codex-cli".into()]).is_err());
assert!(parse_inspect_options(&["--codex-cli".into()]).is_err());
let duplicate = [
"--codex-cli".into(),
path.display().to_string(),
"--codex-cli".into(),
path.display().to_string(),
];
assert!(parse_launch_options(&duplicate).is_err());
assert!(parse_inspect_options(&duplicate).is_err());
}

#[test]
fn custom_install_root_must_be_an_existing_absolute_directory() {
assert!(absolute_directory(Path::new("relative/path"), "--custom-install").is_err());
Expand All @@ -1633,6 +1722,7 @@ mod tests {
renderer_extension: PathBuf::from("/opt/renderer-extension.js"),
pi: None,
custom_install_root: None,
codex_cli: None,
}
}

Expand Down
Loading
Loading