Skip to content
Open
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
10 changes: 10 additions & 0 deletions packages/sandbox/Cargo.lock

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

1 change: 1 addition & 0 deletions packages/sandbox/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ async-trait = "0.1"
axum = { version = "0.8", features = ["macros", "json", "http1", "http2", "ws"] }
chrono = { version = "0.4", features = ["serde", "clock"] }
clap = { version = "4.5", features = ["derive", "env"] }
clap_complete = "4.5"
crossterm = { version = "0.28", features = ["event-stream"] }
futures = "0.3"
ignore = "0.4.25"
Expand Down
83 changes: 82 additions & 1 deletion packages/sandbox/src/bin/cli.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use chrono::SecondsFormat;
use clap::{Args, Parser, Subcommand, ValueEnum};
use clap::{Args, CommandFactory, Parser, Subcommand, ValueEnum};
use clap_complete::{generate, Shell};
use cmux_sandbox::models::{
CreateSandboxRequest, EnvVar, ExecRequest, ExecResponse, NotificationLogEntry, SandboxSummary,
};
Expand Down Expand Up @@ -165,6 +166,17 @@ enum Command {

/// Remove old sandboxes and optionally prune Docker resources
Prune(PruneArgs),

/// Generate shell completions (or install with --install)
Completions {
/// Shell to generate completions for
#[arg(value_enum)]
shell: Shell,

/// Install completions to the standard location (~/.zfunc for zsh)
#[arg(long)]
install: bool,
},
}

#[derive(Args, Debug)]
Expand Down Expand Up @@ -1119,6 +1131,75 @@ async fn run() -> anyhow::Result<()> {
Command::Prune(args) => {
handle_prune(&client, &cli.base_url, args).await?;
}
Command::Completions { shell, install } => {
// Determine the binary name based on how we were invoked
let bin_name = std::env::args()
.next()
.and_then(|s| {
std::path::Path::new(&s)
.file_name()
.map(|os| os.to_string_lossy().into_owned())
})
.unwrap_or_else(|| "cmux".to_string());
let mut cmd = Cli::command();

if install {
// Install completions to standard location
match shell {
Shell::Zsh => {
let home = std::env::var("HOME")
.map_err(|_| anyhow::anyhow!("HOME environment variable not set"))?;
let zfunc_dir = PathBuf::from(&home).join(".zfunc");
std::fs::create_dir_all(&zfunc_dir)?;
let completion_file = zfunc_dir.join(format!("_{}", bin_name));
let mut file = std::fs::File::create(&completion_file)?;
generate(shell, &mut cmd, &bin_name, &mut file);
println!("Installed zsh completions to {}", completion_file.display());
println!();
println!("To enable, add this to your ~/.zshrc (before compinit):");
println!(" fpath=(~/.zfunc $fpath)");
println!();
println!("Then reload your shell or run: exec zsh");
}
Shell::Bash => {
let home = std::env::var("HOME")
.map_err(|_| anyhow::anyhow!("HOME environment variable not set"))?;
let completion_dir =
PathBuf::from(&home).join(".local/share/bash-completion/completions");
std::fs::create_dir_all(&completion_dir)?;
let completion_file = completion_dir.join(&bin_name);
let mut file = std::fs::File::create(&completion_file)?;
generate(shell, &mut cmd, &bin_name, &mut file);
println!(
"Installed bash completions to {}",
completion_file.display()
);
println!("Reload your shell to enable completions.");
}
Shell::Fish => {
let home = std::env::var("HOME")
.map_err(|_| anyhow::anyhow!("HOME environment variable not set"))?;
let completion_dir = PathBuf::from(&home).join(".config/fish/completions");
std::fs::create_dir_all(&completion_dir)?;
let completion_file = completion_dir.join(format!("{}.fish", bin_name));
let mut file = std::fs::File::create(&completion_file)?;
generate(shell, &mut cmd, &bin_name, &mut file);
println!(
"Installed fish completions to {}",
completion_file.display()
);
}
_ => {
anyhow::bail!(
"Install not supported for {:?}. Use stdout redirection instead.",
shell
);
}
}
} else {
generate(shell, &mut cmd, &bin_name, &mut std::io::stdout());
}
}
Command::Sandboxes(cmd) => {
match cmd {
SandboxCommand::List => {
Expand Down
6 changes: 6 additions & 0 deletions packages/sandbox/src/bubblewrap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,12 @@ setopt PROMPT_SUBST
# Green user@host, blue path, magenta git branch
PROMPT='%F{green}%n@%m%f:%F{blue}%~%f${vcs_info_msg_0_}%F{yellow}#%f '

# Add user completion directories to fpath (synced from host)
# These must be added before compinit to be recognized
[[ -d ~/.zfunc ]] && fpath=(~/.zfunc $fpath)
[[ -d ~/.local/share/zsh/completions ]] && fpath=(~/.local/share/zsh/completions $fpath)
[[ -d ~/.zsh/completions ]] && fpath=(~/.zsh/completions $fpath)

# Enable completion
autoload -Uz compinit && compinit
zstyle ':completion:*' menu select
Expand Down
14 changes: 14 additions & 0 deletions packages/sandbox/src/sync_files.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,20 @@ pub const SYNC_FILES: &[SyncFileDef] = &[
sandbox_path: "/root/.zsh",
is_dir: true,
},
// Zsh completions - common locations for user-installed completions
// These contain completion scripts (_command files) that provide tab-completion
SyncFileDef {
name: "Zsh Completions (zfunc)",
host_path: ".zfunc",
sandbox_path: "/root/.zfunc",
is_dir: true,
},
SyncFileDef {
name: "Zsh Completions (XDG)",
host_path: ".local/share/zsh/completions",
sandbox_path: "/root/.local/share/zsh/completions",
is_dir: true,
},
];

pub fn detect_sync_files() -> Vec<SyncFileToUpload> {
Expand Down
14 changes: 14 additions & 0 deletions scripts/snapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -1613,6 +1613,20 @@ async def task_configure_zsh(ctx: TaskContext) -> None:
cat >> /root/.zshrc <<'EOF'
HISTFILE=~/.zsh_history
setopt HIST_IGNORE_DUPS HIST_VERIFY

# Add user completion directories to fpath (synced from host)
# These must be added before compinit to be recognized
[[ -d ~/.zfunc ]] && fpath=(~/.zfunc $fpath)
[[ -d ~/.local/share/zsh/completions ]] && fpath=(~/.local/share/zsh/completions $fpath)
[[ -d ~/.zsh/completions ]] && fpath=(~/.zsh/completions $fpath)

# Enable completion system
autoload -Uz compinit && compinit
zstyle ':completion:*' menu select
zstyle ':completion:*' matcher-list 'm:{a-zA-Z}={A-Za-z}'

# Source user's local customizations (synced from host)
[[ -f ~/.zshrc.local ]] && source ~/.zshrc.local
EOF
cat > /root/.zprofile <<'EOF'
[[ -f ~/.zshrc ]] && source ~/.zshrc
Expand Down