Skip to main content

codex_wrapper/command/
session_mgmt.rs

1//! Session-lifecycle commands: archive, delete, and unarchive a saved session.
2//!
3//! These wrap `codex archive`, `codex delete`, and `codex unarchive`. Each
4//! targets a saved session by id (UUID) or name; UUIDs take precedence when the
5//! value parses as one.
6//!
7//! The underlying subcommands inherit a large shared option block from the CLI
8//! (model, image, sandbox, remote, and so on), almost none of which is
9//! meaningful for a lifecycle operation. These builders expose only the useful
10//! surface: the session target, the `-c` / `--enable` / `--disable` config
11//! passthrough, and (for delete) `--force`.
12
13use crate::Codex;
14use crate::command::CodexCommand;
15use crate::error::Result;
16use crate::exec::{self, CommandOutput};
17
18/// Append `-c`/`--enable`/`--disable` passthrough args shared by the trio.
19fn push_config(
20    args: &mut Vec<String>,
21    config_overrides: &[String],
22    enabled: &[String],
23    disabled: &[String],
24) {
25    for value in config_overrides {
26        args.push("-c".into());
27        args.push(value.clone());
28    }
29    for value in enabled {
30        args.push("--enable".into());
31        args.push(value.clone());
32    }
33    for value in disabled {
34        args.push("--disable".into());
35        args.push(value.clone());
36    }
37}
38
39/// Archive a saved session (`codex archive <SESSION>`).
40#[derive(Debug, Clone)]
41pub struct ArchiveCommand {
42    session: String,
43    config_overrides: Vec<String>,
44    enabled_features: Vec<String>,
45    disabled_features: Vec<String>,
46    retry_policy: Option<crate::retry::RetryPolicy>,
47}
48
49impl ArchiveCommand {
50    /// Create an archive command targeting the given session id or name.
51    #[must_use]
52    pub fn new(session: impl Into<String>) -> Self {
53        Self {
54            session: session.into(),
55            config_overrides: Vec::new(),
56            enabled_features: Vec::new(),
57            disabled_features: Vec::new(),
58            retry_policy: None,
59        }
60    }
61
62    /// Override a config key (`-c key=value`). May be called multiple times.
63    #[must_use]
64    pub fn config(mut self, key_value: impl Into<String>) -> Self {
65        self.config_overrides.push(key_value.into());
66        self
67    }
68
69    /// Enable an optional feature flag (`--enable <feature>`).
70    #[must_use]
71    pub fn enable(mut self, feature: impl Into<String>) -> Self {
72        self.enabled_features.push(feature.into());
73        self
74    }
75
76    /// Disable an optional feature flag (`--disable <feature>`).
77    #[must_use]
78    pub fn disable(mut self, feature: impl Into<String>) -> Self {
79        self.disabled_features.push(feature.into());
80        self
81    }
82
83    /// Override the retry policy for this command.
84    #[must_use]
85    pub fn retry(mut self, policy: crate::retry::RetryPolicy) -> Self {
86        self.retry_policy = Some(policy);
87        self
88    }
89}
90
91impl CodexCommand for ArchiveCommand {
92    type Output = CommandOutput;
93
94    fn args(&self) -> Vec<String> {
95        let mut args = vec!["archive".to_string()];
96        push_config(
97            &mut args,
98            &self.config_overrides,
99            &self.enabled_features,
100            &self.disabled_features,
101        );
102        args.push(self.session.clone());
103        args
104    }
105
106    async fn execute(&self, codex: &Codex) -> Result<CommandOutput> {
107        exec::run_codex_with_retry(codex, self.args(), self.retry_policy.as_ref()).await
108    }
109}
110
111/// Permanently delete a saved session (`codex delete <SESSION>`).
112#[derive(Debug, Clone)]
113pub struct DeleteCommand {
114    session: String,
115    force: bool,
116    config_overrides: Vec<String>,
117    enabled_features: Vec<String>,
118    disabled_features: Vec<String>,
119    retry_policy: Option<crate::retry::RetryPolicy>,
120}
121
122impl DeleteCommand {
123    /// Create a delete command targeting the given session id or name.
124    #[must_use]
125    pub fn new(session: impl Into<String>) -> Self {
126        Self {
127            session: session.into(),
128            force: false,
129            config_overrides: Vec::new(),
130            enabled_features: Vec::new(),
131            disabled_features: Vec::new(),
132            retry_policy: None,
133        }
134    }
135
136    /// Skip the confirmation prompt (`--force`).
137    #[must_use]
138    pub fn force(mut self) -> Self {
139        self.force = true;
140        self
141    }
142
143    /// Override a config key (`-c key=value`). May be called multiple times.
144    #[must_use]
145    pub fn config(mut self, key_value: impl Into<String>) -> Self {
146        self.config_overrides.push(key_value.into());
147        self
148    }
149
150    /// Enable an optional feature flag (`--enable <feature>`).
151    #[must_use]
152    pub fn enable(mut self, feature: impl Into<String>) -> Self {
153        self.enabled_features.push(feature.into());
154        self
155    }
156
157    /// Disable an optional feature flag (`--disable <feature>`).
158    #[must_use]
159    pub fn disable(mut self, feature: impl Into<String>) -> Self {
160        self.disabled_features.push(feature.into());
161        self
162    }
163
164    /// Override the retry policy for this command.
165    #[must_use]
166    pub fn retry(mut self, policy: crate::retry::RetryPolicy) -> Self {
167        self.retry_policy = Some(policy);
168        self
169    }
170}
171
172impl CodexCommand for DeleteCommand {
173    type Output = CommandOutput;
174
175    fn args(&self) -> Vec<String> {
176        let mut args = vec!["delete".to_string()];
177        push_config(
178            &mut args,
179            &self.config_overrides,
180            &self.enabled_features,
181            &self.disabled_features,
182        );
183        if self.force {
184            args.push("--force".into());
185        }
186        args.push(self.session.clone());
187        args
188    }
189
190    async fn execute(&self, codex: &Codex) -> Result<CommandOutput> {
191        exec::run_codex_with_retry(codex, self.args(), self.retry_policy.as_ref()).await
192    }
193}
194
195/// Unarchive a previously archived session (`codex unarchive <SESSION>`).
196#[derive(Debug, Clone)]
197pub struct UnarchiveCommand {
198    session: String,
199    config_overrides: Vec<String>,
200    enabled_features: Vec<String>,
201    disabled_features: Vec<String>,
202    retry_policy: Option<crate::retry::RetryPolicy>,
203}
204
205impl UnarchiveCommand {
206    /// Create an unarchive command targeting the given session id or name.
207    #[must_use]
208    pub fn new(session: impl Into<String>) -> Self {
209        Self {
210            session: session.into(),
211            config_overrides: Vec::new(),
212            enabled_features: Vec::new(),
213            disabled_features: Vec::new(),
214            retry_policy: None,
215        }
216    }
217
218    /// Override a config key (`-c key=value`). May be called multiple times.
219    #[must_use]
220    pub fn config(mut self, key_value: impl Into<String>) -> Self {
221        self.config_overrides.push(key_value.into());
222        self
223    }
224
225    /// Enable an optional feature flag (`--enable <feature>`).
226    #[must_use]
227    pub fn enable(mut self, feature: impl Into<String>) -> Self {
228        self.enabled_features.push(feature.into());
229        self
230    }
231
232    /// Disable an optional feature flag (`--disable <feature>`).
233    #[must_use]
234    pub fn disable(mut self, feature: impl Into<String>) -> Self {
235        self.disabled_features.push(feature.into());
236        self
237    }
238
239    /// Override the retry policy for this command.
240    #[must_use]
241    pub fn retry(mut self, policy: crate::retry::RetryPolicy) -> Self {
242        self.retry_policy = Some(policy);
243        self
244    }
245}
246
247impl CodexCommand for UnarchiveCommand {
248    type Output = CommandOutput;
249
250    fn args(&self) -> Vec<String> {
251        let mut args = vec!["unarchive".to_string()];
252        push_config(
253            &mut args,
254            &self.config_overrides,
255            &self.enabled_features,
256            &self.disabled_features,
257        );
258        args.push(self.session.clone());
259        args
260    }
261
262    async fn execute(&self, codex: &Codex) -> Result<CommandOutput> {
263        exec::run_codex_with_retry(codex, self.args(), self.retry_policy.as_ref()).await
264    }
265}
266
267#[cfg(test)]
268mod tests {
269    use super::*;
270
271    #[test]
272    fn archive_args() {
273        let args = ArchiveCommand::new("sess-1").args();
274        assert_eq!(args, vec!["archive", "sess-1"]);
275    }
276
277    #[test]
278    fn archive_args_with_config() {
279        let args = ArchiveCommand::new("sess-1")
280            .config("foo=bar")
281            .enable("beta")
282            .args();
283        assert_eq!(
284            args,
285            vec!["archive", "-c", "foo=bar", "--enable", "beta", "sess-1"]
286        );
287    }
288
289    #[test]
290    fn delete_args() {
291        let args = DeleteCommand::new("sess-2").args();
292        assert_eq!(args, vec!["delete", "sess-2"]);
293    }
294
295    #[test]
296    fn delete_args_force() {
297        let args = DeleteCommand::new("sess-2").force().args();
298        assert_eq!(args, vec!["delete", "--force", "sess-2"]);
299    }
300
301    #[test]
302    fn unarchive_args() {
303        let args = UnarchiveCommand::new("sess-3").args();
304        assert_eq!(args, vec!["unarchive", "sess-3"]);
305    }
306}