Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 0 additions & 98 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 0 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,10 @@ shellexpand = "3"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }

# Process management
nix = { version = "0.31", features = ["process", "signal"] }

# Utilities
thiserror = "2"
anyhow = "1"
tempfile = "3"
uuid = { version = "1", features = ["v4"] }

[dev-dependencies]
assert_cmd = "2"
Expand Down
3 changes: 1 addition & 2 deletions cliff.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ body = """
### {{ group | striptags | trim | upper_first }}
{% for commit in commits %}
- {% if commit.scope %}**{{ commit.scope }}:** {% endif %}\
{{ commit.message | upper_first }}\
{% if commit.github.username %} by @{{ commit.github.username }}{%- endif %}\
{{ commit.message | upper_first }}
{% endfor %}
{% endfor %}\n
"""
Expand Down
21 changes: 4 additions & 17 deletions src/cli/args.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,8 @@
use clap::Parser;
use std::path::PathBuf;

/// Network access mode for the sandbox
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum NetworkMode {
/// Block all network access (default)
#[default]
Offline,
/// Allow all network access
Online,
/// Allow localhost only
Localhost,
}
// Re-export NetworkMode from config schema to avoid duplication
pub use crate::config::schema::NetworkMode;

/// sx - Lightweight sandbox for macOS development
///
Expand All @@ -24,13 +15,9 @@ pub enum NetworkMode {
base Minimal sandbox (always included)\n \
online Full network access\n \
localhost Localhost network only\n \
node Node.js/npm toolchain\n \
python Python toolchain\n \
rust Rust/Cargo toolchain\n \
go Go toolchain\n \
claude Claude Code (~/.claude access)\n \
gpg GPG signing support\n \
git Git with signing support")]
gpg GPG signing support")]
pub struct Args {
/// Enable verbose output (show sandbox config)
#[arg(short, long)]
Expand Down Expand Up @@ -95,7 +82,7 @@ pub struct Args {
#[arg(long = "deny-read", value_name = "PATH")]
pub deny_read: Vec<String>,

/// Profiles to apply (e.g., online, node, claude)
/// Profiles to apply (e.g., online, rust, claude)
#[arg(value_name = "PROFILES")]
pub profiles: Vec<String>,

Expand Down
21 changes: 6 additions & 15 deletions src/cli/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@ pub fn explain(args: &Args) -> Result<()> {
/// Print generated sandbox profile without executing
pub fn dry_run(args: &Args) -> Result<()> {
let context = build_sandbox_context(args)?;
let profile = generate_seatbelt_profile(&context.params);
let profile = generate_seatbelt_profile(&context.params)
.context("Failed to generate seatbelt profile")?;

if args.verbose {
println!("# Profiles: {}", context.profile_names.join(", "));
Expand Down Expand Up @@ -291,9 +292,8 @@ fn build_sandbox_params(
/// Determine network mode with precedence: CLI > profile > config
fn determine_network_mode(args: &Args, profile: &Profile, config: &Config) -> NetworkMode {
// CLI flags take highest precedence
let cli_mode = args.network_mode();
if args.online || args.localhost || args.offline {
return convert_network_mode(cli_mode);
return args.network_mode();
}

// Profile network mode
Expand All @@ -308,15 +308,6 @@ fn determine_network_mode(args: &Args, profile: &Profile, config: &Config) -> Ne
.unwrap_or(config.sandbox.default_network)
}

/// Convert CLI NetworkMode to config NetworkMode
fn convert_network_mode(mode: super::args::NetworkMode) -> NetworkMode {
match mode {
super::args::NetworkMode::Offline => NetworkMode::Offline,
super::args::NetworkMode::Online => NetworkMode::Online,
super::args::NetworkMode::Localhost => NetworkMode::Localhost,
}
}

/// Collect allow-read paths from config, profile, and CLI
fn collect_allow_read_paths(config: &Config, profile: &Profile, cli: &[String]) -> Vec<String> {
let mut paths = Vec::new();
Expand Down Expand Up @@ -355,7 +346,7 @@ fn generate_config_template() -> &'static str {
inherit_global = true

# Profiles to apply for this project
# Available: base, online, localhost, node, python, rust, go, claude, gpg
# Available: base, online, localhost, rust, claude, gpg
profiles = []

# Default network mode: "offline", "online", or "localhost"
Expand Down Expand Up @@ -411,13 +402,13 @@ mod tests {

#[test]
fn test_collect_profile_names_adds_cli_profiles() {
let args = Args::try_parse_from(["sx", "online", "node"]).unwrap();
let args = Args::try_parse_from(["sx", "online", "rust"]).unwrap();
let config = Config::default();
let working_dir = PathBuf::from("/tmp");

let names = collect_profile_names(&args, &config, &working_dir);
assert!(names.contains(&"online".to_string()));
assert!(names.contains(&"node".to_string()));
assert!(names.contains(&"rust".to_string()));
}

#[test]
Expand Down
7 changes: 5 additions & 2 deletions src/config/merge.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::schema::{Config, FilesystemConfig, SandboxConfig, ShellConfig};
use std::collections::HashSet;

/// Merge two configurations, with project taking precedence
///
Expand Down Expand Up @@ -68,11 +69,13 @@ fn merge_shell(global: &ShellConfig, project: &ShellConfig) -> ShellConfig {
}
}

/// Merge two string vectors, keeping unique values
/// Merge two string vectors, keeping unique values.
/// Uses HashSet for O(1) lookups instead of O(n) contains() checks.
fn merge_unique_strings(a: &[String], b: &[String]) -> Vec<String> {
let mut seen: HashSet<&str> = a.iter().map(|s| s.as_str()).collect();
let mut result = a.to_vec();
for item in b {
if !result.contains(item) {
if seen.insert(item.as_str()) {
result.push(item.clone());
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ pub mod schema;
pub use global::load_global_config;
pub use merge::merge_configs;
pub use profile::{
compose_profiles, load_profile, load_profiles, BuiltinProfile, Profile, ProfileFilesystem,
ProfileShell,
compose_profiles, load_profile, load_profiles, BuiltinProfile, Profile, ProfileError,
ProfileFilesystem, ProfileShell,
};
pub use project::load_project_config;
pub use schema::{Config, NetworkMode};
Loading
Loading