1use crate::Codex;
5use crate::command::CodexCommand;
6use crate::command::exec::effective_sandbox;
7use crate::error::Result;
8use crate::exec::{self, CommandOutput};
9use crate::types::{ApprovalPolicy, SandboxMode};
10
11#[derive(Debug, Clone)]
13pub struct ResumeCommand {
14 approve_for_me: bool,
15 session_id: Option<String>,
16 prompt: Option<String>,
17 last: bool,
18 all: bool,
19 config_overrides: Vec<String>,
20 enabled_features: Vec<String>,
21 disabled_features: Vec<String>,
22 images: Vec<String>,
23 model: Option<String>,
24 oss: bool,
25 local_provider: Option<String>,
26 profile: Option<String>,
27 sandbox: Option<SandboxMode>,
28 approval_policy: Option<ApprovalPolicy>,
29 full_auto: bool,
30 dangerously_bypass_approvals_and_sandbox: bool,
31 dangerously_bypass_hook_trust: bool,
32 strict_config: bool,
33 no_alt_screen: bool,
34 include_non_interactive: bool,
35 remote: Option<String>,
36 remote_auth_token_env: Option<String>,
37 cd: Option<String>,
38 search: bool,
39 add_dirs: Vec<String>,
40}
41
42impl ResumeCommand {
43 #[must_use]
44 pub fn new() -> Self {
45 Self {
46 approve_for_me: false,
47 session_id: None,
48 prompt: None,
49 last: false,
50 all: false,
51 config_overrides: Vec::new(),
52 enabled_features: Vec::new(),
53 disabled_features: Vec::new(),
54 images: Vec::new(),
55 model: None,
56 oss: false,
57 local_provider: None,
58 profile: None,
59 sandbox: None,
60 approval_policy: None,
61 full_auto: false,
62 dangerously_bypass_approvals_and_sandbox: false,
63 dangerously_bypass_hook_trust: false,
64 strict_config: false,
65 no_alt_screen: false,
66 include_non_interactive: false,
67 remote: None,
68 remote_auth_token_env: None,
69 cd: None,
70 search: false,
71 add_dirs: Vec::new(),
72 }
73 }
74
75 #[must_use]
77 pub fn session_id(mut self, id: impl Into<String>) -> Self {
78 self.session_id = Some(id.into());
79 self
80 }
81
82 #[must_use]
84 pub fn prompt(mut self, prompt: impl Into<String>) -> Self {
85 self.prompt = Some(prompt.into());
86 self
87 }
88
89 #[must_use]
91 pub fn last(mut self) -> Self {
92 self.last = true;
93 self
94 }
95
96 #[must_use]
98 pub fn all(mut self) -> Self {
99 self.all = true;
100 self
101 }
102
103 #[must_use]
104 pub fn config(mut self, key_value: impl Into<String>) -> Self {
105 self.config_overrides.push(key_value.into());
106 self
107 }
108
109 #[must_use]
110 pub fn enable(mut self, feature: impl Into<String>) -> Self {
111 self.enabled_features.push(feature.into());
112 self
113 }
114
115 #[must_use]
116 pub fn disable(mut self, feature: impl Into<String>) -> Self {
117 self.disabled_features.push(feature.into());
118 self
119 }
120
121 #[must_use]
122 pub fn image(mut self, path: impl Into<String>) -> Self {
123 self.images.push(path.into());
124 self
125 }
126
127 #[must_use]
128 pub fn model(mut self, model: impl Into<String>) -> Self {
129 self.model = Some(model.into());
130 self
131 }
132
133 #[must_use]
134 pub fn oss(mut self) -> Self {
135 self.oss = true;
136 self
137 }
138
139 #[must_use]
140 pub fn local_provider(mut self, provider: impl Into<String>) -> Self {
141 self.local_provider = Some(provider.into());
142 self
143 }
144
145 #[must_use]
146 pub fn profile(mut self, profile: impl Into<String>) -> Self {
147 self.profile = Some(profile.into());
148 self
149 }
150
151 #[must_use]
152 pub fn sandbox(mut self, sandbox: SandboxMode) -> Self {
153 self.sandbox = Some(sandbox);
154 self
155 }
156
157 #[must_use]
158 pub fn approval_policy(mut self, policy: ApprovalPolicy) -> Self {
159 self.approval_policy = Some(policy);
160 self
161 }
162
163 #[must_use]
170 pub fn full_auto(mut self) -> Self {
171 self.full_auto = true;
172 self
173 }
174
175 #[must_use]
183 pub fn approve_for_me(mut self) -> Self {
184 self.approve_for_me = true;
185 self
186 }
187
188 #[must_use]
189 pub(crate) fn set_bypass_approvals_and_sandbox(mut self) -> Self {
190 self.dangerously_bypass_approvals_and_sandbox = true;
191 self
192 }
193
194 #[must_use]
196 pub(crate) fn set_bypass_hook_trust(mut self) -> Self {
197 self.dangerously_bypass_hook_trust = true;
198 self
199 }
200
201 #[must_use]
203 pub fn strict_config(mut self) -> Self {
204 self.strict_config = true;
205 self
206 }
207
208 #[must_use]
210 pub fn no_alt_screen(mut self) -> Self {
211 self.no_alt_screen = true;
212 self
213 }
214
215 #[must_use]
218 pub fn include_non_interactive(mut self) -> Self {
219 self.include_non_interactive = true;
220 self
221 }
222
223 #[must_use]
225 pub fn remote(mut self, addr: impl Into<String>) -> Self {
226 self.remote = Some(addr.into());
227 self
228 }
229
230 #[must_use]
233 pub fn remote_auth_token_env(mut self, env_var: impl Into<String>) -> Self {
234 self.remote_auth_token_env = Some(env_var.into());
235 self
236 }
237
238 #[must_use]
239 pub fn cd(mut self, dir: impl Into<String>) -> Self {
240 self.cd = Some(dir.into());
241 self
242 }
243
244 #[must_use]
246 pub fn search(mut self) -> Self {
247 self.search = true;
248 self
249 }
250
251 #[must_use]
252 pub fn add_dir(mut self, dir: impl Into<String>) -> Self {
253 self.add_dirs.push(dir.into());
254 self
255 }
256}
257
258impl Default for ResumeCommand {
259 fn default() -> Self {
260 Self::new()
261 }
262}
263
264impl CodexCommand for ResumeCommand {
265 type Output = CommandOutput;
266
267 fn args(&self) -> Vec<String> {
268 let mut args = vec!["resume".into()];
269
270 for v in &self.config_overrides {
271 args.push("-c".into());
272 args.push(v.clone());
273 }
274 for v in &self.enabled_features {
275 args.push("--enable".into());
276 args.push(v.clone());
277 }
278 for v in &self.disabled_features {
279 args.push("--disable".into());
280 args.push(v.clone());
281 }
282 if self.last {
283 args.push("--last".into());
284 }
285 if self.all {
286 args.push("--all".into());
287 }
288 for v in &self.images {
289 args.push("--image".into());
290 args.push(v.clone());
291 }
292 if let Some(model) = &self.model {
293 args.push("--model".into());
294 args.push(model.clone());
295 }
296 if self.oss {
297 args.push("--oss".into());
298 }
299 if let Some(provider) = &self.local_provider {
300 args.push("--local-provider".into());
301 args.push(provider.clone());
302 }
303 if let Some(profile) = &self.profile {
304 args.push("--profile".into());
305 args.push(profile.clone());
306 }
307 if let Some(sandbox) = effective_sandbox(self.sandbox, self.full_auto) {
308 args.push("--sandbox".into());
309 args.push(sandbox.as_arg().into());
310 }
311 if let Some(policy) = self.approval_policy {
312 args.push("--ask-for-approval".into());
313 args.push(policy.as_arg().into());
314 }
315 if self.approve_for_me {
316 args.push("--approve-for-me".into());
317 }
318 if self.dangerously_bypass_approvals_and_sandbox {
319 args.push("--dangerously-bypass-approvals-and-sandbox".into());
320 }
321 if self.dangerously_bypass_hook_trust {
322 args.push("--dangerously-bypass-hook-trust".into());
323 }
324 if self.strict_config {
325 args.push("--strict-config".into());
326 }
327 if self.no_alt_screen {
328 args.push("--no-alt-screen".into());
329 }
330 if self.include_non_interactive {
331 args.push("--include-non-interactive".into());
332 }
333 if let Some(remote) = &self.remote {
334 args.push("--remote".into());
335 args.push(remote.clone());
336 }
337 if let Some(env_var) = &self.remote_auth_token_env {
338 args.push("--remote-auth-token-env".into());
339 args.push(env_var.clone());
340 }
341 if let Some(cd) = &self.cd {
342 args.push("--cd".into());
343 args.push(cd.clone());
344 }
345 if self.search {
346 args.push("--search".into());
347 }
348 for v in &self.add_dirs {
349 args.push("--add-dir".into());
350 args.push(v.clone());
351 }
352 if let Some(id) = &self.session_id {
353 args.push(id.clone());
354 }
355 if let Some(prompt) = &self.prompt {
356 args.push(prompt.clone());
357 }
358 args
359 }
360
361 async fn execute(&self, codex: &Codex) -> Result<CommandOutput> {
362 exec::run_codex(codex, self.args()).await
363 }
364}
365
366#[cfg(test)]
367mod tests {
368 use super::*;
369
370 #[test]
371 fn resume_last_args() {
372 let args = ResumeCommand::new()
373 .last()
374 .model("gpt-5")
375 .prompt("continue")
376 .args();
377 assert_eq!(
378 args,
379 vec!["resume", "--last", "--model", "gpt-5", "continue"]
380 );
381 }
382
383 #[test]
384 fn resume_session_id_args() {
385 let args = ResumeCommand::new()
386 .session_id("abc-123")
387 .sandbox(SandboxMode::WorkspaceWrite)
388 .search()
389 .args();
390 assert_eq!(
391 args,
392 vec![
393 "resume",
394 "--sandbox",
395 "workspace-write",
396 "--search",
397 "abc-123"
398 ]
399 );
400 }
401
402 #[test]
403 fn resume_new_flags_args() {
404 let args = ResumeCommand::new()
405 .last()
406 .strict_config()
407 .include_non_interactive()
408 .no_alt_screen()
409 .remote("ws://host:9000")
410 .args();
411 assert_eq!(
412 args,
413 vec![
414 "resume",
415 "--last",
416 "--strict-config",
417 "--no-alt-screen",
418 "--include-non-interactive",
419 "--remote",
420 "ws://host:9000",
421 ]
422 );
423 }
424
425 #[test]
427 fn resume_full_auto_emits_sandbox_workspace_write() {
428 let args = ResumeCommand::new().last().full_auto().args();
429 assert_eq!(
430 args,
431 vec!["resume", "--last", "--sandbox", "workspace-write"]
432 );
433 assert!(!args.iter().any(|a| a == "--full-auto"));
434 }
435
436 #[test]
437 fn resume_explicit_sandbox_wins_over_full_auto() {
438 let args = ResumeCommand::new()
439 .last()
440 .full_auto()
441 .sandbox(SandboxMode::DangerFullAccess)
442 .args();
443 assert_eq!(
444 args,
445 vec!["resume", "--last", "--sandbox", "danger-full-access"]
446 );
447 }
448}