use crate::cli::{RunArgs, SandboxArgs, ShellArgs, WrapArgs};
use crate::exec_strategy;
use crate::execution_runtime::execute_sandboxed;
use crate::launch_runtime::{
ExecutionFlags, LaunchPlan, SessionLaunchOptions, load_configured_detach_sequence,
load_configured_redaction_policy, prepare_run_launch_plan, resolve_requested_workdir,
select_exec_strategy,
};
use crate::output;
use crate::profile;
use crate::proxy_runtime::prepare_proxy_launch_options;
use crate::sandbox_prepare::{
prepare_sandbox, print_allow_gpu_warning, print_allow_launch_services_warning,
should_auto_enable_claude_launch_services, validate_external_proxy_bypass,
};
use crate::theme;
use nono::{NonoError, Result};
use std::ffi::OsString;
use std::path::PathBuf;
use tracing::warn;
pub(crate) fn run_sandbox(mut run_args: RunArgs, silent: bool) -> Result<()> {
let command = run_args.command.clone();
if command.is_empty() {
return Err(NonoError::NoCommand);
}
let mut command_iter = command.into_iter();
let program = OsString::from(command_iter.next().ok_or(NonoError::NoCommand)?);
let mut cmd_args: Vec<OsString> = command_iter.map(OsString::from).collect();
if should_auto_enable_claude_launch_services(&run_args.sandbox, &program, &cmd_args) {
warn!(
"Auto-enabling --allow-launch-services for Claude Code because no refresh-capable local auth was detected"
);
run_args.sandbox.allow_launch_services = true;
}
let args = run_args.sandbox.clone();
if let Some(ref profile_name) = args.profile {
let loaded = profile::load_profile(profile_name)?;
if !loaded.command_args.is_empty() {
let all_packs_installed = loaded.packs.iter().all(|pack_ref| {
let parts: Vec<&str> = pack_ref.splitn(2, '/').collect();
if parts.len() != 2 {
return false;
}
crate::package::package_install_dir(parts[0], parts[1])
.map(|dir| dir.exists())
.unwrap_or(false)
});
if all_packs_installed || loaded.packs.is_empty() {
let workdir = args
.workdir
.clone()
.or_else(|| std::env::current_dir().ok())
.unwrap_or_else(|| PathBuf::from("."));
for arg in &loaded.command_args {
let expanded = profile::expand_vars(arg, &workdir)?;
cmd_args.push(OsString::from(expanded));
}
}
}
}
if args.dry_run {
let prepared = prepare_sandbox(&args, silent)?;
validate_external_proxy_bypass(&args, &prepared)?;
if !prepared.secrets.is_empty() && !silent {
eprintln!(
" Would inject {} credential(s) as environment variables",
prepared.secrets.len()
);
}
let redaction_policy = load_configured_redaction_policy()?;
output::print_dry_run(&program, &cmd_args, &redaction_policy, silent);
return Ok(());
}
let launch_plan = prepare_run_launch_plan(run_args, program, cmd_args, silent)?;
execute_sandboxed(launch_plan)
}
pub(crate) fn run_shell(args: ShellArgs, silent: bool) -> Result<()> {
let shell_path = args
.shell
.or_else(|| {
std::env::var("SHELL")
.ok()
.filter(|shell| !shell.is_empty())
.map(std::path::PathBuf::from)
})
.unwrap_or_else(|| std::path::PathBuf::from("/bin/sh"));
if args.sandbox.dry_run {
let prepared = prepare_sandbox(&args.sandbox, silent)?;
if !prepared.secrets.is_empty() && !silent {
eprintln!(
" Would inject {} credential(s) as environment variables",
prepared.secrets.len()
);
}
let redaction_policy = load_configured_redaction_policy()?;
output::print_dry_run(shell_path.as_os_str(), &[], &redaction_policy, silent);
return Ok(());
}
let prepared = prepare_sandbox(&args.sandbox, silent)?;
if prepared.allow_launch_services_active {
print_allow_launch_services_warning(silent);
}
if prepared.allow_gpu_active {
print_allow_gpu_warning(silent);
}
if !silent {
eprintln!("{}", {
let theme = theme::current();
theme::fg("Exit the shell with Ctrl-D or 'exit'.", theme.subtext)
});
eprintln!();
}
let proxy = prepare_proxy_launch_options(&args.sandbox, &prepared, silent)?;
let strategy = select_exec_strategy(
false,
proxy.active,
prepared.capability_elevation,
false,
false,
);
execute_sandboxed(LaunchPlan {
program: shell_path.into_os_string(),
cmd_args: vec![],
caps: prepared.caps,
loaded_secrets: prepared.secrets,
flags: ExecutionFlags {
strategy,
workdir: resolve_requested_workdir(args.sandbox.workdir.as_ref()),
no_diagnostics: true,
capability_elevation: prepared.capability_elevation,
#[cfg(target_os = "linux")]
wsl2_proxy_policy: prepared.wsl2_proxy_policy,
bypass_protection_paths: prepared.bypass_protection_paths,
ignored_denial_paths: prepared.ignored_denial_paths,
allowed_env_vars: prepared.allowed_env_vars,
denied_env_vars: prepared.denied_env_vars,
proxy,
redaction_policy: load_configured_redaction_policy()?,
session: SessionLaunchOptions {
session_name: args.name,
detach_sequence: load_configured_detach_sequence()?,
..SessionLaunchOptions::default()
},
..ExecutionFlags::defaults(silent)?
},
})
}
pub(crate) fn run_wrap(wrap_args: WrapArgs, silent: bool) -> Result<()> {
let args: SandboxArgs = wrap_args.sandbox.into();
let command = wrap_args.command;
let no_diagnostics = wrap_args.no_diagnostics;
if command.is_empty() {
return Err(NonoError::NoCommand);
}
let mut command_iter = command.into_iter();
let program = OsString::from(command_iter.next().ok_or(NonoError::NoCommand)?);
let cmd_args: Vec<OsString> = command_iter.map(OsString::from).collect();
if args.dry_run {
let prepared = prepare_sandbox(&args, silent)?;
if !prepared.secrets.is_empty() && !silent {
eprintln!(
" Would inject {} credential(s) as environment variables",
prepared.secrets.len()
);
}
let redaction_policy = load_configured_redaction_policy()?;
output::print_dry_run(&program, &cmd_args, &redaction_policy, silent);
return Ok(());
}
let prepared = prepare_sandbox(&args, silent)?;
if prepared.upstream_proxy.is_some()
|| matches!(
prepared.caps.network_mode(),
nono::NetworkMode::ProxyOnly { .. }
)
{
return Err(NonoError::ConfigParse(
"nono wrap does not support proxy mode (activated by profile network settings). \
Use `nono run` instead."
.to_string(),
));
}
if prepared.allow_launch_services_active {
print_allow_launch_services_warning(silent);
}
if prepared.allow_gpu_active {
print_allow_gpu_warning(silent);
}
execute_sandboxed(LaunchPlan {
program,
cmd_args,
caps: prepared.caps,
loaded_secrets: prepared.secrets,
flags: ExecutionFlags {
strategy: exec_strategy::ExecStrategy::Direct,
workdir: resolve_requested_workdir(args.workdir.as_ref()),
no_diagnostics,
bypass_protection_paths: prepared.bypass_protection_paths,
ignored_denial_paths: prepared.ignored_denial_paths,
allowed_env_vars: prepared.allowed_env_vars,
denied_env_vars: prepared.denied_env_vars,
..ExecutionFlags::defaults(silent)?
},
})
}