Add entrypoint option
This commit is contained in:
167
src/config.rs
167
src/config.rs
@@ -21,11 +21,7 @@ pub fn build(args: Args, file_config: Option<FileConfig>) -> Result<SandboxConfi
|
||||
globals.validate_paths()?;
|
||||
profile.validate_paths()?;
|
||||
|
||||
let (command, command_args) = resolve_command(
|
||||
args.command_and_args,
|
||||
profile.command.clone(),
|
||||
globals.command.clone(),
|
||||
);
|
||||
let (command, command_args) = resolve_command(args.command_and_args, &profile, &globals);
|
||||
let command = resolve_binary(&command)
|
||||
.ok_or_else(|| SandboxError::CommandNotFound(PathBuf::from(&command)))?;
|
||||
|
||||
@@ -119,20 +115,31 @@ fn merge_vecs<T: Clone>(cli: Vec<T>, profile: &[T], globals: &[T]) -> Vec<T> {
|
||||
}
|
||||
|
||||
fn resolve_command(
|
||||
mut positional: Vec<OsString>,
|
||||
profile_cmd: Option<CommandValue>,
|
||||
globals_cmd: Option<CommandValue>,
|
||||
mut passthrough_args: Vec<OsString>,
|
||||
profile: &Options,
|
||||
globals: &Options,
|
||||
) -> (OsString, Vec<OsString>) {
|
||||
if !positional.is_empty() {
|
||||
let cmd = positional.remove(0);
|
||||
return (cmd, positional);
|
||||
}
|
||||
if let Some(config_cmd) = profile_cmd.or(globals_cmd) {
|
||||
let parts = config_cmd.into_vec();
|
||||
let cmd = OsString::from(&parts[0]);
|
||||
let args = parts[1..].iter().map(OsString::from).collect();
|
||||
let entrypoint = profile.entrypoint.clone().or(globals.entrypoint.clone());
|
||||
let command = profile.command.clone().or(globals.command.clone());
|
||||
|
||||
if let Some(ep) = entrypoint {
|
||||
let (cmd, mut args) = ep.into_binary_and_args();
|
||||
|
||||
if !passthrough_args.is_empty() {
|
||||
args.extend(passthrough_args);
|
||||
} else if let Some(default_cmd) = command {
|
||||
args.extend(default_cmd.into_vec().into_iter().map(OsString::from));
|
||||
}
|
||||
|
||||
return (cmd, args);
|
||||
}
|
||||
|
||||
if !passthrough_args.is_empty() {
|
||||
return (passthrough_args.remove(0), passthrough_args);
|
||||
}
|
||||
if let Some(config_cmd) = command {
|
||||
return config_cmd.into_binary_and_args();
|
||||
}
|
||||
if let Ok(cmd) = std::env::var("SANDBOX_CMD") {
|
||||
return (OsString::from(cmd), vec![]);
|
||||
}
|
||||
@@ -210,6 +217,7 @@ pub struct Options {
|
||||
pub whitelist: Option<bool>,
|
||||
pub hardened: Option<bool>,
|
||||
pub no_net: Option<bool>,
|
||||
pub entrypoint: Option<CommandValue>,
|
||||
pub command: Option<CommandValue>,
|
||||
pub dry_run: Option<bool>,
|
||||
pub chdir: Option<PathBuf>,
|
||||
@@ -261,6 +269,13 @@ impl CommandValue {
|
||||
Self::WithArgs(v) => v,
|
||||
}
|
||||
}
|
||||
|
||||
fn into_binary_and_args(self) -> (OsString, Vec<OsString>) {
|
||||
let mut parts = self.into_vec();
|
||||
let binary = OsString::from(parts.remove(0));
|
||||
let args = parts.into_iter().map(OsString::from).collect();
|
||||
(binary, args)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_config_path(explicit: Option<&Path>) -> Option<PathBuf> {
|
||||
@@ -334,6 +349,7 @@ mod tests {
|
||||
[profile.claude]
|
||||
rw = ["/home/user/.config/claude"]
|
||||
ro = ["/etc/claude", "/etc/shared"]
|
||||
entrypoint = ["claude", "--dangerously-skip-permissions"]
|
||||
command = ["bash", "-c", "echo hi"]
|
||||
|
||||
[profile.codex]
|
||||
@@ -380,6 +396,14 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn profile_entrypoint_with_args() {
|
||||
assert_command(
|
||||
&CONFIG.profile["claude"].entrypoint,
|
||||
&["claude", "--dangerously-skip-permissions"],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn profile_command_with_args() {
|
||||
assert_command(
|
||||
@@ -675,6 +699,117 @@ mod tests {
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn entrypoint_with_passthrough_args() {
|
||||
let (cmd, args) = resolve_command(
|
||||
vec!["--verbose".into(), "--model".into(), "opus".into()],
|
||||
&Options {
|
||||
entrypoint: Some(CommandValue::WithArgs(vec![
|
||||
"claude".into(),
|
||||
"--dangerously-skip-permissions".into(),
|
||||
])),
|
||||
command: Some(CommandValue::Simple("--default-flag".into())),
|
||||
..Options::default()
|
||||
},
|
||||
&Options::default(),
|
||||
);
|
||||
assert_eq!(cmd, "claude");
|
||||
assert_eq!(
|
||||
args,
|
||||
vec![
|
||||
"--dangerously-skip-permissions",
|
||||
"--verbose",
|
||||
"--model",
|
||||
"opus"
|
||||
]
|
||||
.into_iter()
|
||||
.map(OsString::from)
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn entrypoint_without_passthrough_uses_command_defaults() {
|
||||
let (cmd, args) = resolve_command(
|
||||
vec![],
|
||||
&Options {
|
||||
entrypoint: Some(CommandValue::Simple("claude".into())),
|
||||
command: Some(CommandValue::WithArgs(vec![
|
||||
"--dangerously-skip-permissions".into(),
|
||||
"--verbose".into(),
|
||||
])),
|
||||
..Options::default()
|
||||
},
|
||||
&Options::default(),
|
||||
);
|
||||
assert_eq!(cmd, "claude");
|
||||
assert_eq!(
|
||||
args,
|
||||
vec!["--dangerously-skip-permissions", "--verbose"]
|
||||
.into_iter()
|
||||
.map(OsString::from)
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn entrypoint_without_passthrough_or_command() {
|
||||
let (cmd, args) = resolve_command(
|
||||
vec![],
|
||||
&Options {
|
||||
entrypoint: Some(CommandValue::WithArgs(vec![
|
||||
"claude".into(),
|
||||
"--dangerously-skip-permissions".into(),
|
||||
])),
|
||||
..Options::default()
|
||||
},
|
||||
&Options::default(),
|
||||
);
|
||||
assert_eq!(cmd, "claude");
|
||||
assert_eq!(args, vec![OsString::from("--dangerously-skip-permissions")]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn profile_entrypoint_overrides_global() {
|
||||
let (cmd, _) = resolve_command(
|
||||
vec![],
|
||||
&Options {
|
||||
entrypoint: Some(CommandValue::Simple("claude".into())),
|
||||
..Options::default()
|
||||
},
|
||||
&Options {
|
||||
entrypoint: Some(CommandValue::Simple("codex".into())),
|
||||
..Options::default()
|
||||
},
|
||||
);
|
||||
assert_eq!(cmd, "claude");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn global_entrypoint_with_profile_command() {
|
||||
let (cmd, args) = resolve_command(
|
||||
vec![],
|
||||
&Options {
|
||||
command: Some(CommandValue::WithArgs(vec![
|
||||
"--dangerously-skip-permissions".into(),
|
||||
"--verbose".into(),
|
||||
])),
|
||||
..Options::default()
|
||||
},
|
||||
&Options {
|
||||
entrypoint: Some(CommandValue::Simple("claude".into())),
|
||||
..Options::default()
|
||||
},
|
||||
);
|
||||
assert_eq!(cmd, "claude");
|
||||
assert_eq!(
|
||||
args,
|
||||
vec!["--dangerously-skip-permissions", "--verbose"]
|
||||
.into_iter()
|
||||
.map(OsString::from)
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
fn assert_paths(actual: &[PathBuf], expected: &[&str]) {
|
||||
let expected: Vec<PathBuf> = expected.iter().map(PathBuf::from).collect();
|
||||
assert_eq!(actual, &expected);
|
||||
|
||||
Reference in New Issue
Block a user