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