/// Codex headless command for secure-exec VM. /// /// The prompt mode remains a placeholder command. The ACP session-turn path is /// disabled until it can delegate to the real Codex agent package instead of a /// bespoke provider loop. use std::collections::HashMap; use std::io::{self, Read}; const VERSION: &str = env!("CARGO_PKG_VERSION"); const SESSION_TURN_DISABLED: &str = "codex-exec --session-turn is disabled until the real Codex agent package is wired"; const MAX_PROMPT_BYTES: usize = 64 * 1024; use codex_network_proxy::NetworkProxy; use codex_otel::SessionTelemetry; fn main() { let args: Vec = std::env::args().collect(); if args.iter().any(|a| a == "--help" || a == "-h") { print_help(); return; } if args.iter().any(|a| a == "--version" || a == "-V") { println!("codex-exec {}", VERSION); return; } if args.get(1).map(|s| s.as_str()) == Some("--http-test") { return http_test(&args[2..]); } if args.get(1).map(|s| s.as_str()) == Some("--stub-test") { return stub_test(); } if args.get(1).map(|s| s.as_str()) == Some("--session-turn") { emit_session_turn_disabled(); std::process::exit(1); } let prompt = if args.len() > 1 { let prompt = args[1..].join(" "); if prompt.len() > MAX_PROMPT_BYTES { eprintln!("codex-exec: prompt exceeds {} byte limit", MAX_PROMPT_BYTES); std::process::exit(1); } prompt } else { let mut input = Vec::new(); let mut stdin = io::stdin().take((MAX_PROMPT_BYTES + 1) as u64); match stdin.read_to_end(&mut input) { Ok(_) if input.len() > MAX_PROMPT_BYTES => { eprintln!( "codex-exec: stdin prompt exceeds {} byte limit", MAX_PROMPT_BYTES ); std::process::exit(1); } Ok(_) => match String::from_utf8(input) { Ok(input) => input.trim().to_string(), Err(error) => { eprintln!("codex-exec: stdin prompt is not valid UTF-8: {}", error); std::process::exit(1); } }, Err(error) => { eprintln!("codex-exec: failed to read stdin: {}", error); std::process::exit(1); } } }; if prompt.is_empty() { eprintln!("codex-exec: no prompt provided"); eprintln!("usage: codex-exec or echo '' | codex-exec"); std::process::exit(1); } eprintln!("codex-exec: headless prompt mode is not wired to the provider yet"); eprintln!("prompt received ({} bytes)", prompt.len()); std::process::exit(0); } fn emit_session_turn_disabled() { println!( "{{\"type\":\"error\",\"message\":\"{}\"}}", SESSION_TURN_DISABLED ); } fn print_help() { println!("codex-exec {} - headless Codex command", VERSION); println!(); println!("USAGE:"); println!(" codex-exec [OPTIONS] [PROMPT]"); println!(" echo '' | codex-exec"); println!(); println!("OPTIONS:"); println!(" -h, --help Print this help message"); println!(" -V, --version Print version information"); println!(" --http-test URL Test HTTP client via host_net"); println!(" --stub-test Validate WASI stub crates"); println!(" --session-turn Fail fast until the real Codex agent package is wired"); } fn stub_test() { let proxy = NetworkProxy; let mut env = HashMap::new(); proxy.apply_to_env(&mut env); println!("network-proxy: NetworkProxy is zero-size, apply_to_env is no-op"); let telemetry = SessionTelemetry::new(); telemetry.counter("test.counter", 1, &[]); telemetry.histogram("test.histogram", 42, &[]); println!("otel: SessionTelemetry metrics are no-ops"); let global = codex_otel::metrics::global(); assert!(global.is_none(), "global metrics should be None on WASI"); println!("otel: global() returns None (no exporter on WASI)"); println!("stub-test: all stubs validated successfully"); } fn http_test(args: &[String]) { if args.is_empty() { eprintln!("usage: codex-exec --http-test "); std::process::exit(1); } let url = &args[0]; match wasi_http::get(url) { Ok(resp) => { println!("status: {}", resp.status); match resp.text() { Ok(body) => println!("body: {}", body), Err(error) => eprintln!("body decode error: {}", error), } } Err(error) => { eprintln!("http error: {}", error); std::process::exit(1); } } }