1use crate::Codex;
5use crate::command::CodexCommand;
6use crate::error::Result;
7use crate::exec::{self, CommandOutput};
8use crate::types::{ApprovalPolicy, SandboxMode};
9
10#[derive(Debug, Clone)]
12pub struct ForkCommand {
13 session_id: Option<String>,
14 prompt: Option<String>,
15 last: bool,
16 all: bool,
17 config_overrides: Vec<String>,
18 enabled_features: Vec<String>,
19 disabled_features: Vec<String>,
20 images: Vec<String>,
21 model: Option<String>,
22 oss: bool,
23 local_provider: Option<String>,
24 profile: Option<String>,
25 sandbox: Option<SandboxMode>,
26 approval_policy: Option<ApprovalPolicy>,
27 full_auto: bool,
28 dangerously_bypass_approvals_and_sandbox: bool,
29 dangerously_bypass_hook_trust: bool,
30 strict_config: bool,
31 no_alt_screen: bool,
32 remote: Option<String>,
33 remote_auth_token_env: Option<String>,
34 cd: Option<String>,
35 search: bool,
36 add_dirs: Vec<String>,
37}
38
39impl ForkCommand {
40 #[must_use]
41 pub fn new() -> Self {
42 Self {
43 session_id: None,
44 prompt: None,
45 last: false,
46 all: false,
47 config_overrides: Vec::new(),
48 enabled_features: Vec::new(),
49 disabled_features: Vec::new(),
50 images: Vec::new(),
51 model: None,
52 oss: false,
53 local_provider: None,
54 profile: None,
55 sandbox: None,
56 approval_policy: None,
57 full_auto: false,
58 dangerously_bypass_approvals_and_sandbox: false,
59 dangerously_bypass_hook_trust: false,
60 strict_config: false,
61 no_alt_screen: false,
62 remote: None,
63 remote_auth_token_env: None,
64 cd: None,
65 search: false,
66 add_dirs: Vec::new(),
67 }
68 }
69
70 #[must_use]
72 pub fn session_id(mut self, id: impl Into<String>) -> Self {
73 self.session_id = Some(id.into());
74 self
75 }
76
77 #[must_use]
79 pub fn prompt(mut self, prompt: impl Into<String>) -> Self {
80 self.prompt = Some(prompt.into());
81 self
82 }
83
84 #[must_use]
86 pub fn last(mut self) -> Self {
87 self.last = true;
88 self
89 }
90
91 #[must_use]
93 pub fn all(mut self) -> Self {
94 self.all = true;
95 self
96 }
97
98 #[must_use]
99 pub fn config(mut self, key_value: impl Into<String>) -> Self {
100 self.config_overrides.push(key_value.into());
101 self
102 }
103
104 #[must_use]
105 pub fn enable(mut self, feature: impl Into<String>) -> Self {
106 self.enabled_features.push(feature.into());
107 self
108 }
109
110 #[must_use]
111 pub fn disable(mut self, feature: impl Into<String>) -> Self {
112 self.disabled_features.push(feature.into());
113 self
114 }
115
116 #[must_use]
117 pub fn image(mut self, path: impl Into<String>) -> Self {
118 self.images.push(path.into());
119 self
120 }
121
122 #[must_use]
123 pub fn model(mut self, model: impl Into<String>) -> Self {
124 self.model = Some(model.into());
125 self
126 }
127
128 #[must_use]
129 pub fn oss(mut self) -> Self {
130 self.oss = true;
131 self
132 }
133
134 #[must_use]
135 pub fn local_provider(mut self, provider: impl Into<String>) -> Self {
136 self.local_provider = Some(provider.into());
137 self
138 }
139
140 #[must_use]
141 pub fn profile(mut self, profile: impl Into<String>) -> Self {
142 self.profile = Some(profile.into());
143 self
144 }
145
146 #[must_use]
147 pub fn sandbox(mut self, sandbox: SandboxMode) -> Self {
148 self.sandbox = Some(sandbox);
149 self
150 }
151
152 #[must_use]
153 pub fn approval_policy(mut self, policy: ApprovalPolicy) -> Self {
154 self.approval_policy = Some(policy);
155 self
156 }
157
158 #[must_use]
159 pub fn full_auto(mut self) -> Self {
160 self.full_auto = true;
161 self
162 }
163
164 #[must_use]
165 pub fn dangerously_bypass_approvals_and_sandbox(mut self) -> Self {
166 self.dangerously_bypass_approvals_and_sandbox = true;
167 self
168 }
169
170 #[must_use]
172 pub fn dangerously_bypass_hook_trust(mut self) -> Self {
173 self.dangerously_bypass_hook_trust = true;
174 self
175 }
176
177 #[must_use]
179 pub fn strict_config(mut self) -> Self {
180 self.strict_config = true;
181 self
182 }
183
184 #[must_use]
186 pub fn no_alt_screen(mut self) -> Self {
187 self.no_alt_screen = true;
188 self
189 }
190
191 #[must_use]
193 pub fn remote(mut self, addr: impl Into<String>) -> Self {
194 self.remote = Some(addr.into());
195 self
196 }
197
198 #[must_use]
201 pub fn remote_auth_token_env(mut self, env_var: impl Into<String>) -> Self {
202 self.remote_auth_token_env = Some(env_var.into());
203 self
204 }
205
206 #[must_use]
207 pub fn cd(mut self, dir: impl Into<String>) -> Self {
208 self.cd = Some(dir.into());
209 self
210 }
211
212 #[must_use]
214 pub fn search(mut self) -> Self {
215 self.search = true;
216 self
217 }
218
219 #[must_use]
220 pub fn add_dir(mut self, dir: impl Into<String>) -> Self {
221 self.add_dirs.push(dir.into());
222 self
223 }
224}
225
226impl Default for ForkCommand {
227 fn default() -> Self {
228 Self::new()
229 }
230}
231
232impl CodexCommand for ForkCommand {
233 type Output = CommandOutput;
234
235 fn args(&self) -> Vec<String> {
236 let mut args = vec!["fork".into()];
237
238 for v in &self.config_overrides {
239 args.push("-c".into());
240 args.push(v.clone());
241 }
242 for v in &self.enabled_features {
243 args.push("--enable".into());
244 args.push(v.clone());
245 }
246 for v in &self.disabled_features {
247 args.push("--disable".into());
248 args.push(v.clone());
249 }
250 if self.last {
251 args.push("--last".into());
252 }
253 if self.all {
254 args.push("--all".into());
255 }
256 for v in &self.images {
257 args.push("--image".into());
258 args.push(v.clone());
259 }
260 if let Some(model) = &self.model {
261 args.push("--model".into());
262 args.push(model.clone());
263 }
264 if self.oss {
265 args.push("--oss".into());
266 }
267 if let Some(provider) = &self.local_provider {
268 args.push("--local-provider".into());
269 args.push(provider.clone());
270 }
271 if let Some(profile) = &self.profile {
272 args.push("--profile".into());
273 args.push(profile.clone());
274 }
275 if let Some(sandbox) = self.sandbox {
276 args.push("--sandbox".into());
277 args.push(sandbox.as_arg().into());
278 }
279 if let Some(policy) = self.approval_policy {
280 args.push("--ask-for-approval".into());
281 args.push(policy.as_arg().into());
282 }
283 if self.full_auto {
284 args.push("--full-auto".into());
285 }
286 if self.dangerously_bypass_approvals_and_sandbox {
287 args.push("--dangerously-bypass-approvals-and-sandbox".into());
288 }
289 if self.dangerously_bypass_hook_trust {
290 args.push("--dangerously-bypass-hook-trust".into());
291 }
292 if self.strict_config {
293 args.push("--strict-config".into());
294 }
295 if self.no_alt_screen {
296 args.push("--no-alt-screen".into());
297 }
298 if let Some(remote) = &self.remote {
299 args.push("--remote".into());
300 args.push(remote.clone());
301 }
302 if let Some(env_var) = &self.remote_auth_token_env {
303 args.push("--remote-auth-token-env".into());
304 args.push(env_var.clone());
305 }
306 if let Some(cd) = &self.cd {
307 args.push("--cd".into());
308 args.push(cd.clone());
309 }
310 if self.search {
311 args.push("--search".into());
312 }
313 for v in &self.add_dirs {
314 args.push("--add-dir".into());
315 args.push(v.clone());
316 }
317 if let Some(id) = &self.session_id {
318 args.push(id.clone());
319 }
320 if let Some(prompt) = &self.prompt {
321 args.push(prompt.clone());
322 }
323 args
324 }
325
326 async fn execute(&self, codex: &Codex) -> Result<CommandOutput> {
327 exec::run_codex(codex, self.args()).await
328 }
329}
330
331#[cfg(test)]
332mod tests {
333 use super::*;
334
335 #[test]
336 fn fork_last_args() {
337 let args = ForkCommand::new()
338 .last()
339 .model("gpt-5")
340 .prompt("take a different approach")
341 .args();
342 assert_eq!(
343 args,
344 vec![
345 "fork",
346 "--last",
347 "--model",
348 "gpt-5",
349 "take a different approach"
350 ]
351 );
352 }
353
354 #[test]
355 fn fork_session_id_args() {
356 let args = ForkCommand::new()
357 .session_id("abc-123")
358 .full_auto()
359 .search()
360 .args();
361 assert_eq!(args, vec!["fork", "--full-auto", "--search", "abc-123"]);
362 }
363
364 #[test]
365 fn fork_new_flags_args() {
366 let args = ForkCommand::new()
367 .last()
368 .strict_config()
369 .dangerously_bypass_hook_trust()
370 .no_alt_screen()
371 .remote("ws://host:9000")
372 .remote_auth_token_env("CODEX_TOKEN")
373 .args();
374 assert_eq!(
375 args,
376 vec![
377 "fork",
378 "--last",
379 "--dangerously-bypass-hook-trust",
380 "--strict-config",
381 "--no-alt-screen",
382 "--remote",
383 "ws://host:9000",
384 "--remote-auth-token-env",
385 "CODEX_TOKEN",
386 ]
387 );
388 }
389}