1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
//! Command builders for every Codex CLI subcommand.
//!
//! Each subcommand is a builder struct that implements [`CodexCommand`].
//! Builders accumulate flags via method chaining, then call
//! [`CodexCommand::execute`] with a [`Codex`] client to run.
pub mod apply;
pub mod completion;
pub mod doctor;
pub mod exec;
pub mod features;
pub mod fork;
pub mod login;
pub mod mcp;
pub mod mcp_server;
pub mod plugin;
pub mod raw;
pub mod resume;
pub mod review;
pub mod sandbox;
pub mod session_mgmt;
pub mod update;
pub mod version;
use std::future::Future;
use crate::Codex;
use crate::error::Result;
/// Trait implemented by all Codex CLI command builders.
///
/// [`args`](CodexCommand::args) returns the CLI arguments the builder would
/// pass to the `codex` binary. [`execute`](CodexCommand::execute) spawns the
/// process and returns typed output.
pub trait CodexCommand: Send + Sync {
/// The type returned on success.
type Output: Send;
/// Build the argument list for this command.
fn args(&self) -> Vec<String>;
/// Execute the command against the given [`Codex`] client.
fn execute(&self, codex: &Codex) -> impl Future<Output = Result<Self::Output>> + Send;
/// Render the exact command line this builder will spawn, quoted for a
/// POSIX shell.
///
/// Useful for logging a reproduction or checking an invocation before
/// running it. The client's global args precede the command's own, the
/// same order the spawn uses, because both go through one assembly
/// function: the preview cannot drift from what runs.
///
/// ```no_run
/// use codex_wrapper::{Codex, CodexCommand, ExecCommand};
///
/// # fn example() -> codex_wrapper::Result<()> {
/// let codex = Codex::builder().build()?;
/// let cmd = ExecCommand::new("fix the failing tests").ephemeral();
/// println!("{}", cmd.to_command_string(&codex));
/// // codex exec --ephemeral 'fix the failing tests'
/// # Ok(())
/// # }
/// ```
///
/// The rendering is for humans. It is faithful to the argv, but the args
/// are passed to the process directly rather than through a shell, so a
/// shell is never involved at spawn time.
fn to_command_string(&self, codex: &Codex) -> String {
crate::exec::command_string(codex, self.args())
}
}
#[cfg(all(test, unix))]
mod tests {
use super::*;
use crate::command::exec::{ExecCommand, ExecResumeCommand};
/// Run a fake codex that echoes its argv, one argument per line.
async fn spawned_args(cmd: &impl CodexCommand, codex: &Codex) -> Vec<String> {
let output = crate::exec::run_codex(codex, cmd.args()).await.unwrap();
output.stdout.lines().map(str::to_string).collect()
}
fn echoing_codex() -> Codex {
let script = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests")
.join("fake-codex-echo-args.sh");
Codex::builder()
.binary("/bin/bash")
.arg(script.to_str().unwrap())
.config("model=\"gpt-5\"")
.build()
.expect("bash must exist")
}
/// The preview is only worth anything if it matches the real spawn. This
/// compares against the argv a process actually received, rather than
/// against the assembly function the preview itself calls: a test written
/// that way would still pass if the spawn path stopped using it.
///
/// Quoting is shared with the implementation here, and covered on its own
/// in `exec::tests`. What this pins is the part a shared function cannot
/// prove by itself: that the args reaching the process are the same ones,
/// in the same order, that the preview claims.
#[tokio::test]
async fn preview_matches_the_argv_a_spawn_receives() {
let codex = echoing_codex();
let cmd = ExecCommand::new("fix the failing tests").ephemeral();
let spawned = spawned_args(&cmd, &codex).await;
let preview = cmd.to_command_string(&codex);
// The fake is `bash <script>`, so the echoed argv is the preview with
// the binary and the script path removed from the front.
let rendered: Vec<String> = spawned
.iter()
.map(|a| crate::exec::shell_quote(a))
.collect();
assert!(
preview.ends_with(&rendered.join(" ")),
"preview {preview:?} does not end with the spawned argv {rendered:?}"
);
// The global -c pair from the client is in there, ahead of the args.
assert_eq!(spawned[0], "-c");
assert_eq!(spawned[1], "model=\"gpt-5\"");
assert_eq!(spawned[2], "exec");
}
#[test]
fn preview_puts_global_args_before_the_subcommand() {
let codex = echoing_codex();
let preview = ExecCommand::new("hi").ephemeral().to_command_string(&codex);
assert!(
preview.contains(r#"-c 'model="gpt-5"' exec"#),
"globals must precede the subcommand: {preview}"
);
assert!(preview.ends_with("--ephemeral hi"), "{preview}");
}
#[test]
fn preview_is_available_on_every_builder() {
let codex = echoing_codex();
// Provided on the trait, so a resume builder gets it too.
let preview = ExecResumeCommand::new().last().to_command_string(&codex);
assert!(preview.contains("exec resume --last"), "{preview}");
}
}