feat: added support for loading sbx mixins that are dynamically discovered in the users workspace and config directory

This commit is contained in:
2026-06-17 14:39:32 -06:00
parent 8e0b07c9fb
commit 6ae474c79e
6 changed files with 399 additions and 24 deletions
+115 -21
View File
@@ -7,7 +7,10 @@ use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use which::which;
mod mixins;
use crate::config::paths;
use crate::sandbox::mixins::DiscoveredMixin;
use crate::utils::run_command_with_output;
use crate::vault::Vault;
@@ -19,26 +22,39 @@ const SANDBOX_AGENT: &str = "coyote";
#[folder = "assets/sbx-kit/"]
struct EmbeddedKit;
pub fn launch(name: Option<String>, fresh: bool) -> Result<()> {
pub fn launch(name: Option<String>, fresh: bool, no_mixins: bool) -> Result<()> {
ensure_sbx_installed()?;
bail_if_nested()?;
let name = resolve_name(name)?;
let kit_path = resolve_kit_path()?;
let discovered = if no_mixins {
Vec::new()
} else {
mixins::discover()?
};
if sandbox_exists(&name)? {
info!("Re-attaching to existing sandbox '{name}'");
if fresh {
debug!("--fresh ignored: re-attaching to existing sandbox '{name}'");
}
} else if fresh {
let msg = format!("Creating fresh sandbox '{name}' (no host config will be copied)");
info!("{msg}");
println!("{msg}");
create_sandbox(&name, &kit_path)?;
if no_mixins {
debug!("--no-mixins ignored: re-attaching to existing sandbox '{name}'");
}
} else {
create_sandbox(&name, &kit_path)?;
copy_host_files(&name)?;
mixins::log_discovery(&discovered, no_mixins);
if fresh {
let msg = format!("Creating fresh sandbox '{name}' (no host config will be copied)");
info!("{msg}");
println!("{msg}");
create_sandbox(&name, &kit_path, &discovered)?;
} else {
create_sandbox(&name, &kit_path, &discovered)?;
copy_host_files(&name)?;
}
}
exec_run(&name, &kit_path)
@@ -192,21 +208,11 @@ fn sandbox_exists(name: &str) -> Result<bool> {
.any(|line| line.split_whitespace().next() == Some(name)))
}
fn create_sandbox(name: &str, kit_path: &Path) -> Result<()> {
fn create_sandbox(name: &str, kit_path: &Path, mixins: &[DiscoveredMixin]) -> Result<()> {
info!("Creating sandbox '{name}'");
let kit_str = kit_path
.to_str()
.ok_or_else(|| anyhow!("Kit path is not valid UTF-8: {}", kit_path.display()))?;
let args = build_create_args(name, kit_path, mixins)?;
let status = Command::new(SBX_BINARY)
.args([
"create",
"--kit",
kit_str,
SANDBOX_AGENT,
"--name",
name,
".",
])
.args(&args)
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
@@ -220,6 +226,38 @@ fn create_sandbox(name: &str, kit_path: &Path) -> Result<()> {
Ok(())
}
fn build_create_args(
name: &str,
kit_path: &Path,
mixins: &[DiscoveredMixin],
) -> Result<Vec<String>> {
let kit_str = kit_path
.to_str()
.ok_or_else(|| anyhow!("Kit path is not valid UTF-8: {}", kit_path.display()))?;
let mut args = vec![
"create".to_string(),
"--kit".to_string(),
kit_str.to_string(),
];
for mixin in mixins {
let mixin_str = mixin
.path
.to_str()
.ok_or_else(|| anyhow!("Mixin path is not valid UTF-8: {}", mixin.path.display()))?;
args.push("--kit".to_string());
args.push(mixin_str.to_string());
}
args.push(SANDBOX_AGENT.to_string());
args.push("--name".to_string());
args.push(name.to_string());
args.push(".".to_string());
Ok(args)
}
fn copy_host_files(name: &str) -> Result<()> {
let config_dir = paths::config_dir();
let home_dir = dirs::home_dir().context("Could not determine home directory")?;
@@ -393,4 +431,60 @@ mod tests {
assert_eq!(h1, h2);
assert_eq!(h1.len(), 64);
}
#[test]
fn build_create_args_emits_base_kit_before_mixins() {
let kit = PathBuf::from("/cache/sbx-kit");
let mixins = vec![
DiscoveredMixin {
path: PathBuf::from("/cfg/sbx-mixin.yaml"),
label: "user".into(),
install_count: 0,
domain_count: 0,
},
DiscoveredMixin {
path: PathBuf::from("/cfg/agents/sql/sbx-mixin.yaml"),
label: "sql".into(),
install_count: 0,
domain_count: 0,
},
];
let args = build_create_args("my-box", &kit, &mixins).unwrap();
assert_eq!(
args,
vec![
"create".to_string(),
"--kit".to_string(),
"/cache/sbx-kit".to_string(),
"--kit".to_string(),
"/cfg/sbx-mixin.yaml".to_string(),
"--kit".to_string(),
"/cfg/agents/sql/sbx-mixin.yaml".to_string(),
"coyote".to_string(),
"--name".to_string(),
"my-box".to_string(),
".".to_string(),
]
);
}
#[test]
fn build_create_args_with_no_mixins_omits_mixin_kits() {
let kit = PathBuf::from("/cache/sbx-kit");
let args = build_create_args("box", &kit, &[]).unwrap();
assert_eq!(
args,
vec![
"create".to_string(),
"--kit".to_string(),
"/cache/sbx-kit".to_string(),
"coyote".to_string(),
"--name".to_string(),
"box".to_string(),
".".to_string(),
]
);
}
}