Add marketplace remove command

This commit is contained in:
xli-oai
2026-04-13 04:30:35 -07:00
parent bacb92b1d7
commit 6bb13354e3
5 changed files with 217 additions and 0 deletions
+49
View File
@@ -4,6 +4,7 @@ use anyhow::bail;
use clap::Parser;
use codex_config::MarketplaceConfigUpdate;
use codex_config::record_user_marketplace;
use codex_config::remove_user_marketplace;
use codex_core::config::find_codex_home;
use codex_core::plugins::OPENAI_CURATED_MARKETPLACE_NAME;
use codex_core::plugins::marketplace_install_root;
@@ -31,6 +32,9 @@ pub struct MarketplaceCli {
enum MarketplaceSubcommand {
/// Add a remote marketplace repository.
Add(AddMarketplaceArgs),
/// Remove a configured marketplace.
Remove(RemoveMarketplaceArgs),
}
#[derive(Debug, Parser)]
@@ -51,6 +55,12 @@ struct AddMarketplaceArgs {
sparse_paths: Vec<String>,
}
#[derive(Debug, Parser)]
struct RemoveMarketplaceArgs {
/// Configured marketplace name to remove.
marketplace_name: String,
}
#[derive(Debug, PartialEq, Eq)]
pub(super) enum MarketplaceSource {
Git {
@@ -74,6 +84,7 @@ impl MarketplaceCli {
match subcommand {
MarketplaceSubcommand::Add(args) => run_add(args).await?,
MarketplaceSubcommand::Remove(args) => run_remove(args).await?,
}
Ok(())
@@ -177,6 +188,38 @@ async fn run_add(args: AddMarketplaceArgs) -> Result<()> {
Ok(())
}
async fn run_remove(args: RemoveMarketplaceArgs) -> Result<()> {
let RemoveMarketplaceArgs { marketplace_name } = args;
validate_plugin_segment(&marketplace_name, "marketplace name").map_err(anyhow::Error::msg)?;
let codex_home = find_codex_home().context("failed to resolve CODEX_HOME")?;
let install_root = marketplace_install_root(&codex_home);
let destination = install_root.join(safe_marketplace_dir_name(&marketplace_name)?);
let removed_root = ops::remove_marketplace_root(&destination).with_context(|| {
format!(
"failed to remove installed marketplace root {}",
destination.display()
)
})?;
let removed_config =
remove_user_marketplace(&codex_home, &marketplace_name).with_context(|| {
format!("failed to remove marketplace `{marketplace_name}` from user config.toml")
})?;
if !removed_root && !removed_config {
bail!("marketplace `{marketplace_name}` is not configured or installed");
}
println!("Removed marketplace `{marketplace_name}`.");
if removed_root {
println!(
"Removed installed marketplace root: {}",
destination.display()
);
}
Ok(())
}
fn record_added_marketplace(
codex_home: &Path,
marketplace_name: &str,
@@ -559,4 +602,10 @@ mod tests {
vec!["plugins/foo", "skills/bar"]
);
}
#[test]
fn remove_subcommand_parses_marketplace_name() {
let remove = RemoveMarketplaceArgs::try_parse_from(["remove", "debug"]).unwrap();
assert_eq!(remove.marketplace_name, "debug");
}
}
+18
View File
@@ -79,6 +79,15 @@ pub(super) fn replace_marketplace_root(staged_root: &Path, destination: &Path) -
fs::rename(staged_root, destination).map_err(Into::into)
}
pub(super) fn remove_marketplace_root(root: &Path) -> Result<bool> {
if !root.exists() {
return Ok(false);
}
fs::remove_dir_all(root)?;
Ok(true)
}
pub(super) fn marketplace_staging_root(install_root: &Path) -> PathBuf {
install_root.join(".staging")
}
@@ -115,4 +124,13 @@ mod tests {
"installed"
);
}
#[test]
fn remove_marketplace_root_returns_false_when_missing() {
let temp_dir = TempDir::new().unwrap();
let removed = remove_marketplace_root(&temp_dir.path().join("missing")).unwrap();
assert!(!removed);
}
}
+69
View File
@@ -0,0 +1,69 @@
use anyhow::Result;
use codex_config::MarketplaceConfigUpdate;
use codex_config::record_user_marketplace;
use codex_core::plugins::marketplace_install_root;
use predicates::str::contains;
use std::path::Path;
use tempfile::TempDir;
fn codex_command(codex_home: &Path) -> Result<assert_cmd::Command> {
let mut cmd = assert_cmd::Command::new(codex_utils_cargo_bin::cargo_bin("codex")?);
cmd.env("CODEX_HOME", codex_home);
Ok(cmd)
}
fn configured_marketplace_update() -> MarketplaceConfigUpdate<'static> {
MarketplaceConfigUpdate {
last_updated: "2026-04-13T00:00:00Z",
source_type: "git",
source: "https://github.com/owner/repo.git",
ref_name: Some("main"),
sparse_paths: &[],
}
}
fn write_installed_marketplace(codex_home: &Path, marketplace_name: &str) -> Result<()> {
let root = marketplace_install_root(codex_home).join(marketplace_name);
std::fs::create_dir_all(root.join(".agents/plugins"))?;
std::fs::write(root.join(".agents/plugins/marketplace.json"), "{}")?;
std::fs::write(root.join("marker.txt"), "installed")?;
Ok(())
}
#[tokio::test]
async fn marketplace_remove_deletes_config_and_installed_root() -> Result<()> {
let codex_home = TempDir::new()?;
record_user_marketplace(codex_home.path(), "debug", &configured_marketplace_update())?;
write_installed_marketplace(codex_home.path(), "debug")?;
codex_command(codex_home.path())?
.args(["marketplace", "remove", "debug"])
.assert()
.success()
.stdout(contains("Removed marketplace `debug`."));
let config_path = codex_home.path().join("config.toml");
let config = std::fs::read_to_string(config_path)?;
assert!(!config.contains("[marketplaces.debug]"));
assert!(
!marketplace_install_root(codex_home.path())
.join("debug")
.exists()
);
Ok(())
}
#[tokio::test]
async fn marketplace_remove_rejects_unknown_marketplace() -> Result<()> {
let codex_home = TempDir::new()?;
codex_command(codex_home.path())?
.args(["marketplace", "remove", "debug"])
.assert()
.failure()
.stderr(contains(
"marketplace `debug` is not configured or installed",
));
Ok(())
}