Skip to main content

sbox/
cli.rs

1use std::path::PathBuf;
2
3use clap::{Args, CommandFactory, Parser, Subcommand, ValueEnum};
4use clap_complete::Shell;
5
6#[derive(Debug, Clone, Parser)]
7#[command(
8    name = "sbox",
9    version,
10    about = "Policy-driven sandboxed command runner"
11)]
12pub struct Cli {
13    #[arg(long, global = true)]
14    pub config: Option<PathBuf>,
15
16    #[arg(long, global = true)]
17    pub workspace: Option<PathBuf>,
18
19    #[arg(long, global = true, value_enum)]
20    pub backend: Option<CliBackendKind>,
21
22    #[arg(long, global = true)]
23    pub image: Option<String>,
24
25    #[arg(long, global = true)]
26    pub profile: Option<String>,
27
28    #[arg(long, global = true, value_enum)]
29    pub mode: Option<CliExecutionMode>,
30
31    #[arg(short = 'v', long, global = true, action = clap::ArgAction::Count)]
32    pub verbose: u8,
33
34    #[arg(long, global = true)]
35    pub quiet: bool,
36
37    #[arg(long, global = true)]
38    pub strict_security: bool,
39
40    #[command(subcommand)]
41    pub command: Commands,
42}
43
44#[derive(Debug, Clone, Subcommand)]
45pub enum Commands {
46    Init(InitCommand),
47    Run(RunCommand),
48    Exec(ExecCommand),
49    Shell(ShellCommand),
50    Plan(PlanCommand),
51    Doctor(DoctorCommand),
52    Clean(CleanCommand),
53    Shim(ShimCommand),
54    Bootstrap(BootstrapCommand),
55    Audit(AuditCommand),
56    Completions(CompletionsCommand),
57}
58
59#[derive(Debug, Clone, Args)]
60pub struct InitCommand {
61    #[arg(long)]
62    pub force: bool,
63
64    #[arg(long)]
65    pub preset: Option<String>,
66
67    #[arg(long)]
68    pub output: Option<PathBuf>,
69
70    /// Launch an interactive wizard to generate sbox.yaml
71    #[arg(long, short = 'i')]
72    pub interactive: bool,
73
74    /// Auto-detect the package manager from an existing lockfile in the current directory
75    /// and generate a matching preset config (skips the wizard)
76    #[arg(long, conflicts_with_all = ["preset", "interactive"])]
77    pub from_lockfile: bool,
78}
79
80#[derive(Debug, Clone, Args)]
81#[command(trailing_var_arg = true)]
82pub struct RunCommand {
83    /// Print the resolved plan and backend command without executing
84    #[arg(long)]
85    pub dry_run: bool,
86
87    /// Pass an extra environment variable into the sandbox, e.g. -e FOO=bar (repeatable)
88    #[arg(short = 'e', long = "env", value_name = "NAME=VALUE")]
89    pub env: Vec<String>,
90
91    #[arg(required = true, num_args = 1.., allow_hyphen_values = true)]
92    pub command: Vec<String>,
93}
94
95#[derive(Debug, Clone, Args)]
96#[command(trailing_var_arg = true)]
97pub struct ExecCommand {
98    pub profile: String,
99
100    #[arg(required = true, num_args = 1.., allow_hyphen_values = true)]
101    pub command: Vec<String>,
102}
103
104#[derive(Debug, Clone, Args, Default)]
105pub struct ShellCommand {
106    #[arg(long)]
107    pub shell: Option<String>,
108}
109
110#[derive(Debug, Clone, Args)]
111#[command(trailing_var_arg = true)]
112pub struct PlanCommand {
113    #[arg(long)]
114    pub show_command: bool,
115
116    /// Run the ecosystem's audit tool (npm audit, cargo audit, etc.) and append findings
117    #[arg(long)]
118    pub audit: bool,
119
120    /// Omit to show the policy for the profile selected by --profile without a specific command.
121    #[arg(num_args = 0.., allow_hyphen_values = true)]
122    pub command: Vec<String>,
123}
124
125#[derive(Debug, Clone, Args, Default)]
126pub struct DoctorCommand {
127    #[arg(long)]
128    pub strict: bool,
129}
130
131#[derive(Debug, Clone, Args, Default)]
132pub struct CleanCommand {
133    #[arg(long)]
134    pub sessions: bool,
135
136    #[arg(long)]
137    pub images: bool,
138
139    #[arg(long)]
140    pub caches: bool,
141
142    #[arg(long)]
143    pub all: bool,
144
145    #[arg(long = "global")]
146    pub global_scope: bool,
147}
148
149#[derive(Debug, Clone, Copy, ValueEnum)]
150pub enum CliBackendKind {
151    Podman,
152    Docker,
153}
154
155#[derive(Debug, Clone, Copy, ValueEnum)]
156pub enum CliExecutionMode {
157    Host,
158    Sandbox,
159}
160
161/// Scan the project's lockfile for known-malicious or vulnerable package versions.
162/// Delegates to the ecosystem's native audit tool (npm audit, cargo audit, etc.)
163/// and runs on the host (not in a sandbox) so it can reach advisory databases.
164#[derive(Debug, Clone, Args, Default)]
165pub struct AuditCommand {
166    /// Extra arguments forwarded to the underlying audit tool.
167    #[arg(num_args = 0.., allow_hyphen_values = true)]
168    pub extra_args: Vec<String>,
169}
170
171/// Generate the package lockfile inside the sandbox without running install scripts.
172/// Requires `package_manager:` to be configured in sbox.yaml.
173/// After bootstrap, run `sbox run -- <rebuild-command>` to execute scripts with network off.
174#[derive(Debug, Clone, Args, Default)]
175pub struct BootstrapCommand {}
176
177/// Print shell completion script to stdout.
178/// Pipe the output to your shell's completion directory, e.g.:
179///   sbox completions bash > /etc/bash_completion.d/sbox
180///   sbox completions zsh  > ~/.zsh/completions/_sbox
181#[derive(Debug, Clone, Args)]
182pub struct CompletionsCommand {
183    pub shell: Shell,
184}
185
186pub fn generate_completions(shell: Shell) {
187    use std::io;
188    clap_complete::generate(shell, &mut Cli::command(), "sbox", &mut io::stdout());
189}
190
191#[derive(Debug, Clone, Args)]
192pub struct ShimCommand {
193    /// Directory to write shim scripts into (default: ~/.local/bin)
194    #[arg(long)]
195    pub dir: Option<PathBuf>,
196
197    /// Overwrite existing shim files
198    #[arg(long)]
199    pub force: bool,
200
201    /// Print what would be created without writing anything
202    #[arg(long)]
203    pub dry_run: bool,
204
205    /// Check whether shims are installed and appear in PATH before the real binaries; exit 1 if any are missing or shadowed
206    #[arg(long)]
207    pub verify: bool,
208}