Skip to main content

codex/cli/
sandbox.rs

1use crate::{ConfigOverride, FeatureToggles};
2use std::{ffi::OsString, path::PathBuf, process::ExitStatus};
3
4/// Sandbox platform variant; maps to platform subcommands of `codex sandbox`.
5#[derive(Clone, Copy, Debug, Eq, PartialEq)]
6pub enum SandboxPlatform {
7    Macos,
8    Linux,
9    Windows,
10}
11
12impl SandboxPlatform {
13    pub(crate) fn subcommand(self) -> &'static str {
14        match self {
15            SandboxPlatform::Macos => "macos",
16            SandboxPlatform::Linux => "linux",
17            SandboxPlatform::Windows => "windows",
18        }
19    }
20}
21
22/// Request to run an arbitrary command inside a Codex-provided sandbox.
23#[derive(Clone, Debug, Eq, PartialEq)]
24pub struct SandboxCommandRequest {
25    /// Target platform subcommand; maps to `macos` (alias `seatbelt`), `linux` (alias `landlock`), or `windows`.
26    pub platform: SandboxPlatform,
27    /// Trailing command arguments to execute. Must be non-empty to avoid the upstream CLI panic.
28    pub command: Vec<OsString>,
29    /// Request the workspace-write sandbox preset (`--full-auto`).
30    pub full_auto: bool,
31    /// Stream macOS sandbox denials after the child process exits (no-op on other platforms).
32    pub log_denials: bool,
33    /// Additional `--config key=value` overrides to pass through.
34    pub config_overrides: Vec<ConfigOverride>,
35    /// Feature toggles forwarded to `--enable`/`--disable`.
36    pub feature_toggles: FeatureToggles,
37    /// Working directory for the spawned command; falls back to the builder value, then the current process directory.
38    pub working_dir: Option<PathBuf>,
39}
40
41impl SandboxCommandRequest {
42    pub fn new<I, S>(platform: SandboxPlatform, command: I) -> Self
43    where
44        I: IntoIterator<Item = S>,
45        S: Into<OsString>,
46    {
47        Self {
48            platform,
49            command: command.into_iter().map(Into::into).collect(),
50            full_auto: false,
51            log_denials: false,
52            config_overrides: Vec::new(),
53            feature_toggles: FeatureToggles::default(),
54            working_dir: None,
55        }
56    }
57
58    pub fn full_auto(mut self, enable: bool) -> Self {
59        self.full_auto = enable;
60        self
61    }
62
63    pub fn log_denials(mut self, enable: bool) -> Self {
64        self.log_denials = enable;
65        self
66    }
67
68    pub fn config_override(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
69        self.config_overrides.push(ConfigOverride::new(key, value));
70        self
71    }
72
73    pub fn config_override_raw(mut self, raw: impl Into<String>) -> Self {
74        self.config_overrides.push(ConfigOverride::from_raw(raw));
75        self
76    }
77
78    pub fn enable_feature(mut self, name: impl Into<String>) -> Self {
79        self.feature_toggles.enable.push(name.into());
80        self
81    }
82
83    pub fn disable_feature(mut self, name: impl Into<String>) -> Self {
84        self.feature_toggles.disable.push(name.into());
85        self
86    }
87
88    pub fn working_dir(mut self, dir: impl Into<PathBuf>) -> Self {
89        self.working_dir = Some(dir.into());
90        self
91    }
92}
93
94/// Captured output from `codex sandbox <platform>`.
95#[derive(Clone, Debug)]
96pub struct SandboxRun {
97    /// Exit status returned by the inner command (mirrors the sandbox helper).
98    pub status: ExitStatus,
99    /// Captured stdout (mirrored to the console when `mirror_stdout` is true).
100    pub stdout: String,
101    /// Captured stderr (mirrored unless `quiet` is set).
102    pub stderr: String,
103}