From 69589bd5e566431b0e05e797f8522569c6cc15b8 Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Wed, 17 Jun 2026 14:20:17 -0600 Subject: [PATCH] feat: Added a --fresh flag to let users create a truly bare bones sandbox without bootstrapping their config --- src/cli/mod.rs | 41 ++++++++++++++++++++++++++++++++++++++--- src/main.rs | 2 +- src/sandbox/mod.rs | 10 +++++++++- 3 files changed, 48 insertions(+), 5 deletions(-) diff --git a/src/cli/mod.rs b/src/cli/mod.rs index ce40693..7f802c3 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -6,7 +6,7 @@ use crate::cli::completer::{ }; use crate::config::{AssetCategory, InstallFilter, MemoryScope}; use anyhow::{Context, Result}; -use clap::ValueHint; +use clap::{ArgGroup, ValueHint}; use clap::{Parser, crate_authors, crate_description, crate_version}; use clap_complete::ArgValueCompleter; use is_terminal::IsTerminal; @@ -27,7 +27,20 @@ use std::io::{Read, stdin}; {usage-heading} {usage} {all-args}{after-help} -" +", + group( + ArgGroup::new("sbx-mode") + .args(["sandbox", "fresh"]) + .multiple(true) + .conflicts_with_all([ + "model", "prompt", "role", "session", "agent", "rag", "rebuild_rag", + "macro_name", "execute", "code", "file", "no_stream", "no_memory", + "init_memory", "dry_run", "info", "build_tools", "install", + "install_from", "sync_models", "list_models", "list_roles", + "list_sessions", "list_agents", "list_rags", "list_macros", + "list_skills", "skill", "tail_logs", "completions", "update", + ]) + ), )] pub struct Cli { /// Select a LLM model @@ -168,8 +181,11 @@ pub struct Cli { #[arg(long, requires = "update")] pub force: bool, /// Launch Coyote inside a Docker sandbox (via `sbx`); name defaults to current directory basename - #[arg(long, exclusive = true, value_name = "NAME")] + #[arg(long, value_name = "NAME")] pub sandbox: Option>, + /// Create the sandbox without copying host config or vault password file + #[arg(long, requires = "sandbox")] + pub fresh: bool, } impl Cli { @@ -515,4 +531,23 @@ mod tests { fn parse_sandbox_is_exclusive() { assert!(Cli::try_parse_from(["coyote", "--sandbox", "--agent", "foo"]).is_err()); } + + #[test] + fn parse_fresh_flag_requires_sandbox() { + assert!(Cli::try_parse_from(["coyote", "--fresh"]).is_err()); + } + + #[test] + fn parse_fresh_flag_with_sandbox() { + let cli = parse(&["--sandbox", "--fresh"]); + assert_eq!(cli.sandbox, Some(None)); + assert!(cli.fresh); + } + + #[test] + fn parse_fresh_flag_with_named_sandbox() { + let cli = parse(&["--sandbox", "foo", "--fresh"]); + assert_eq!(cli.sandbox, Some(Some("foo".to_string()))); + assert!(cli.fresh); + } } diff --git a/src/main.rs b/src/main.rs index 01f4d20..f31d934 100644 --- a/src/main.rs +++ b/src/main.rs @@ -95,7 +95,7 @@ async fn main() -> Result<()> { } if let Some(name) = &cli.sandbox { - return sandbox::launch(name.clone()); + return sandbox::launch(name.clone(), cli.fresh); } install_builtins()?; diff --git a/src/sandbox/mod.rs b/src/sandbox/mod.rs index 3d31b04..2379deb 100644 --- a/src/sandbox/mod.rs +++ b/src/sandbox/mod.rs @@ -19,7 +19,7 @@ const SANDBOX_AGENT: &str = "coyote"; #[folder = "assets/sbx-kit/"] struct EmbeddedKit; -pub fn launch(name: Option) -> Result<()> { +pub fn launch(name: Option, fresh: bool) -> Result<()> { ensure_sbx_installed()?; bail_if_nested()?; @@ -28,6 +28,14 @@ pub fn launch(name: Option) -> Result<()> { 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)?; } else { create_sandbox(&name, &kit_path)?; copy_host_files(&name)?;