From d887f332494893848a07523e12eb28b4c8e024c7 Mon Sep 17 00:00:00 2001 From: Casey Chow Date: Tue, 12 May 2026 09:23:16 -0700 Subject: [PATCH] fix(cli): require configured plugin marketplaces --- codex-rs/cli/src/plugin_cmd.rs | 9 +-- codex-rs/cli/tests/plugin_cli.rs | 102 +++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 7 deletions(-) diff --git a/codex-rs/cli/src/plugin_cmd.rs b/codex-rs/cli/src/plugin_cmd.rs index 4c380792df..fcb549f884 100644 --- a/codex-rs/cli/src/plugin_cmd.rs +++ b/codex-rs/cli/src/plugin_cmd.rs @@ -9,7 +9,6 @@ use codex_core_plugins::PluginInstallRequest; use codex_core_plugins::PluginsConfigInput; use codex_core_plugins::PluginsManager; use codex_plugin::PluginId; -use codex_utils_absolute_path::AbsolutePathBuf; use codex_utils_cli::CliConfigOverrides; use crate::marketplace_cmd::MarketplaceCli; @@ -112,10 +111,8 @@ pub async fn run_plugin_list( manager, .. } = load_plugin_command_context(overrides).await?; - let current_dir = AbsolutePathBuf::try_from(std::env::current_dir()?) - .context("failed to resolve current directory")?; let outcome = manager - .list_marketplaces_for_config(&plugins_input, &[current_dir]) + .list_marketplaces_for_config(&plugins_input, &[]) .context("failed to list marketplace plugins")?; let marketplaces = outcome @@ -248,10 +245,8 @@ fn find_marketplace_for_plugin( marketplace_name: &str, plugin_name: &str, ) -> Result { - let current_dir = AbsolutePathBuf::try_from(std::env::current_dir()?) - .context("failed to resolve current directory")?; let matches = manager - .list_marketplaces_for_config(plugins_input, &[current_dir]) + .list_marketplaces_for_config(plugins_input, &[]) .context("failed to list marketplace plugins")? .marketplaces .into_iter() diff --git a/codex-rs/cli/tests/plugin_cli.rs b/codex-rs/cli/tests/plugin_cli.rs index f47178f875..a27be4d550 100644 --- a/codex-rs/cli/tests/plugin_cli.rs +++ b/codex-rs/cli/tests/plugin_cli.rs @@ -2,6 +2,7 @@ use anyhow::Result; use codex_config::CONFIG_TOML_FILE; use codex_config::MarketplaceConfigUpdate; use codex_config::record_user_marketplace; +use predicates::prelude::PredicateBooleanExt; use predicates::str::contains; use std::path::Path; use tempfile::TempDir; @@ -12,6 +13,12 @@ fn codex_command(codex_home: &Path) -> Result { Ok(cmd) } +fn codex_command_in(codex_home: &Path, current_dir: &Path) -> Result { + let mut cmd = codex_command(codex_home)?; + cmd.current_dir(current_dir); + Ok(cmd) +} + fn configured_local_marketplace(source: &str) -> MarketplaceConfigUpdate<'_> { MarketplaceConfigUpdate { last_updated: "2026-05-06T00:00:00Z", @@ -72,6 +79,14 @@ fn setup_local_marketplace() -> Result<(TempDir, TempDir)> { Ok((codex_home, source)) } +fn setup_unconfigured_local_marketplace() -> Result<(TempDir, TempDir)> { + let codex_home = TempDir::new()?; + let source = TempDir::new()?; + write_plugins_enabled_config(codex_home.path())?; + write_marketplace_source(source.path())?; + Ok((codex_home, source)) +} + #[tokio::test] async fn marketplace_list_shows_configured_marketplace_names() -> Result<()> { let (codex_home, source) = setup_local_marketplace()?; @@ -100,6 +115,20 @@ async fn plugin_list_shows_plugins_grouped_by_marketplace() -> Result<()> { Ok(()) } +#[tokio::test] +async fn plugin_list_excludes_unconfigured_repo_local_marketplaces() -> Result<()> { + let (codex_home, source) = setup_unconfigured_local_marketplace()?; + + codex_command_in(codex_home.path(), source.path())? + .args(["plugin", "list"]) + .assert() + .success() + .stdout(contains("No marketplace plugins found.")) + .stdout(predicates::str::is_match("sample@debug").unwrap().not()); + + Ok(()) +} + #[tokio::test] async fn plugin_add_and_remove_updates_installed_plugin_config() -> Result<()> { let (codex_home, _source) = setup_local_marketplace()?; @@ -127,6 +156,46 @@ async fn plugin_add_and_remove_updates_installed_plugin_config() -> Result<()> { Ok(()) } +#[tokio::test] +async fn plugin_add_rejects_unconfigured_repo_local_marketplaces() -> Result<()> { + let (codex_home, source) = setup_unconfigured_local_marketplace()?; + + codex_command_in(codex_home.path(), source.path())? + .args(["plugin", "add", "sample@debug"]) + .assert() + .failure() + .stderr(contains( + "plugin `sample` was not found in marketplace `debug`", + )); + + Ok(()) +} + +#[tokio::test] +async fn plugin_add_reinstalls_from_configured_marketplace_snapshot() -> Result<()> { + let (codex_home, _source) = setup_local_marketplace()?; + + codex_command(codex_home.path())? + .args(["plugin", "add", "sample@debug"]) + .assert() + .success(); + + codex_command(codex_home.path())? + .args(["plugin", "add", "sample@debug"]) + .assert() + .success() + .stdout(contains("Added plugin `sample` from marketplace `debug`.")); + + assert!( + codex_home + .path() + .join("plugins/cache/debug/sample/local/.codex-plugin/plugin.json") + .is_file() + ); + + Ok(()) +} + #[tokio::test] async fn plugin_remove_works_after_marketplace_is_removed() -> Result<()> { let (codex_home, _source) = setup_local_marketplace()?; @@ -154,3 +223,36 @@ async fn plugin_remove_works_after_marketplace_is_removed() -> Result<()> { Ok(()) } + +#[tokio::test] +async fn plugin_add_rejects_cached_plugins_without_authorizing_marketplace_snapshot() -> Result<()> +{ + let (codex_home, _source) = setup_local_marketplace()?; + + codex_command(codex_home.path())? + .args(["plugin", "add", "sample@debug"]) + .assert() + .success(); + + codex_command(codex_home.path())? + .args(["plugin", "marketplace", "remove", "debug"]) + .assert() + .success(); + + assert!( + codex_home + .path() + .join("plugins/cache/debug/sample/local/.codex-plugin/plugin.json") + .is_file() + ); + + codex_command(codex_home.path())? + .args(["plugin", "add", "sample@debug"]) + .assert() + .failure() + .stderr(contains( + "plugin `sample` was not found in marketplace `debug`", + )); + + Ok(()) +}