[codex] Add marketplace remove command and shared logic (#17752)

## Summary

Move the marketplace remove implementation into shared core logic so
both the CLI command and follow-up app-server RPC can reuse the same
behavior.

This change:
- adds a shared `codex_core::plugins::remove_marketplace(...)` flow
- moves validation, config removal, and installed-root deletion out of
the CLI
- keeps the CLI as a thin wrapper over the shared implementation
- adds focused core coverage for the shared remove path

## Validation

- `just fmt`
- focused local coverage for the shared remove path
- heavier follow-up validation deferred to stacked PR CI
This commit is contained in:
xli-oai
2026-04-17 21:44:47 -07:00
committed by GitHub
parent 6b39d0c657
commit e9c70fff3f
7 changed files with 629 additions and 0 deletions

View File

@@ -1729,6 +1729,15 @@ mod tests {
assert!(matches!(cli.subcommand, Some(Subcommand::Plugin(_))));
}
#[test]
fn plugin_marketplace_remove_parses_under_plugin() {
let cli =
MultitoolCli::try_parse_from(["codex", "plugin", "marketplace", "remove", "debug"])
.expect("parse");
assert!(matches!(cli.subcommand, Some(Subcommand::Plugin(_))));
}
#[test]
fn marketplace_no_longer_parses_at_top_level() {
let add_result =
@@ -1738,6 +1747,10 @@ mod tests {
let upgrade_result =
MultitoolCli::try_parse_from(["codex", "marketplace", "upgrade", "debug"]);
assert!(upgrade_result.is_err());
let remove_result =
MultitoolCli::try_parse_from(["codex", "marketplace", "remove", "debug"]);
assert!(remove_result.is_err());
}
fn sample_exit_info(conversation_id: Option<&str>, thread_name: Option<&str>) -> AppExitInfo {

View File

@@ -5,9 +5,11 @@ use clap::Parser;
use codex_core::config::Config;
use codex_core::config::find_codex_home;
use codex_core::plugins::MarketplaceAddRequest;
use codex_core::plugins::MarketplaceRemoveRequest;
use codex_core::plugins::PluginMarketplaceUpgradeOutcome;
use codex_core::plugins::PluginsManager;
use codex_core::plugins::add_marketplace;
use codex_core::plugins::remove_marketplace;
use codex_utils_cli::CliConfigOverrides;
#[derive(Debug, Parser)]
@@ -23,6 +25,7 @@ pub struct MarketplaceCli {
enum MarketplaceSubcommand {
Add(AddMarketplaceArgs),
Upgrade(UpgradeMarketplaceArgs),
Remove(RemoveMarketplaceArgs),
}
#[derive(Debug, Parser)]
@@ -47,6 +50,12 @@ struct UpgradeMarketplaceArgs {
marketplace_name: Option<String>,
}
#[derive(Debug, Parser)]
struct RemoveMarketplaceArgs {
/// Configured marketplace name to remove.
marketplace_name: String,
}
impl MarketplaceCli {
pub async fn run(self) -> Result<()> {
let MarketplaceCli {
@@ -61,6 +70,7 @@ impl MarketplaceCli {
match subcommand {
MarketplaceSubcommand::Add(args) => run_add(args).await?,
MarketplaceSubcommand::Upgrade(args) => run_upgrade(overrides, args).await?,
MarketplaceSubcommand::Remove(args) => run_remove(args).await?,
}
Ok(())
@@ -120,6 +130,26 @@ async fn run_upgrade(
print_upgrade_outcome(&outcome, marketplace_name.as_deref())
}
async fn run_remove(args: RemoveMarketplaceArgs) -> Result<()> {
let RemoveMarketplaceArgs { marketplace_name } = args;
let codex_home = find_codex_home().context("failed to resolve CODEX_HOME")?;
let outcome = remove_marketplace(
codex_home.to_path_buf(),
MarketplaceRemoveRequest { marketplace_name },
)
.await?;
println!("Removed marketplace `{}`.", outcome.marketplace_name);
if let Some(installed_root) = outcome.removed_installed_root {
println!(
"Removed installed marketplace root: {}",
installed_root.as_path().display()
);
}
Ok(())
}
fn print_upgrade_outcome(
outcome: &PluginMarketplaceUpgradeOutcome,
marketplace_name: Option<&str>,
@@ -201,4 +231,10 @@ mod tests {
let upgrade_one = UpgradeMarketplaceArgs::try_parse_from(["upgrade", "debug"]).unwrap();
assert_eq!(upgrade_one.marketplace_name.as_deref(), Some("debug"));
}
#[test]
fn remove_subcommand_parses_marketplace_name() {
let remove = RemoveMarketplaceArgs::try_parse_from(["remove", "debug"]).unwrap();
assert_eq!(remove.marketplace_name, "debug");
}
}