Download codex-rs/cli/src/marketplace_cmd.rs from SaylorTwift/codex: direct link, hf CLI and curl.
- Browser
- Download file 18.9 kB
-
https://huggingface.co/SaylorTwift/codex/resolve/main/codex-rs/cli/src/marketplace_cmd.rs
- Command line
-
hf download hf://SaylorTwift/codex/codex-rs/cli/src/marketplace_cmd.rs
-
curl -L -o marketplace_cmd.rs https://huggingface.co/SaylorTwift/codex/resolve/main/codex-rs/cli/src/marketplace_cmd.rs
18.9 kB
| use anyhow::Context; | |
| use anyhow::Result; | |
| use anyhow::bail; | |
| use clap::Parser; | |
| use codex_core::config::Config; | |
| use codex_core::config::ConfigOverrides; | |
| use codex_core::config::LoaderOverrides; | |
| use codex_core::plugins_manager_for_config; | |
| use codex_core_plugins::PluginMarketplaceUpgradeOutcome; | |
| use codex_core_plugins::PluginsConfigInput; | |
| use codex_core_plugins::installed_marketplaces::marketplace_install_root; | |
| use codex_core_plugins::installed_marketplaces::resolve_configured_marketplace_root; | |
| use codex_core_plugins::marketplace::marketplace_root_dir; | |
| use codex_core_plugins::marketplace_add::MarketplaceAddOutcome; | |
| use codex_core_plugins::marketplace_add::MarketplaceAddRequest; | |
| use codex_core_plugins::marketplace_add::add_marketplace; | |
| use codex_core_plugins::marketplace_remove::MarketplaceRemoveOutcome; | |
| use codex_core_plugins::marketplace_remove::MarketplaceRemoveRequest; | |
| use codex_core_plugins::marketplace_remove::remove_marketplace; | |
| use codex_utils_cli::CliConfigOverrides; | |
| use serde::Serialize; | |
| use std::collections::HashMap; | |
| use std::collections::HashSet; | |
| use std::path::Path; | |
| use std::path::PathBuf; | |
| use crate::cloud_config; | |
| use crate::plugin_cmd::JsonMarketplaceSource; | |
| use crate::plugin_cmd::configured_marketplace_snapshot_issues; | |
| use crate::plugin_cmd::configured_marketplace_sources; | |
| use crate::plugin_cmd::load_cli_auth_manager; | |
| pub struct MarketplaceCli { | |
| pub config_overrides: CliConfigOverrides, | |
| subcommand: MarketplaceSubcommand, | |
| } | |
| enum MarketplaceSubcommand { | |
| /// Add a local or Git marketplace to the configured marketplace sources. | |
| Add(AddMarketplaceArgs), | |
| /// List plugin marketplaces Codex is currently considering and their roots. | |
| List(ListMarketplaceArgs), | |
| /// Refresh configured Git marketplace snapshots. | |
| /// | |
| /// Omit MARKETPLACE_NAME to upgrade all configured Git marketplaces. | |
| Upgrade(UpgradeMarketplaceArgs), | |
| /// Remove a configured marketplace source by name. | |
| Remove(RemoveMarketplaceArgs), | |
| } | |
| struct AddMarketplaceArgs { | |
| /// Marketplace source: a local path, owner/repo[@ref], HTTPS Git URL, or SSH Git URL. | |
| source: String, | |
| /// Git ref to fetch for Git marketplace sources. | |
| ref_name: Option<String>, | |
| /// Sparse checkout path for Git marketplace sources. Can be repeated. | |
| sparse_paths: Vec<String>, | |
| /// Output add result as JSON. | |
| json: bool, | |
| } | |
| struct ListMarketplaceArgs { | |
| /// Output marketplace list as JSON. | |
| json: bool, | |
| } | |
| struct UpgradeMarketplaceArgs { | |
| /// Optional configured marketplace name to upgrade. Omit to upgrade all Git marketplaces. | |
| marketplace_name: Option<String>, | |
| /// Output upgrade result as JSON. | |
| json: bool, | |
| } | |
| struct RemoveMarketplaceArgs { | |
| /// Configured marketplace name to remove. | |
| marketplace_name: String, | |
| /// Output remove result as JSON. | |
| json: bool, | |
| } | |
| impl MarketplaceCli { | |
| pub async fn run(self) -> Result<()> { | |
| let MarketplaceCli { | |
| config_overrides, | |
| subcommand, | |
| } = self; | |
| let builder = cloud_config::config_builder( | |
| &config_overrides, | |
| LoaderOverrides::default(), | |
| ConfigOverrides::default(), | |
| ) | |
| .await?; | |
| let config = builder.clone().build().await?; | |
| match subcommand { | |
| MarketplaceSubcommand::Add(args) => run_add(config, args).await?, | |
| MarketplaceSubcommand::List(args) => run_list(config, args).await?, | |
| MarketplaceSubcommand::Upgrade(args) => run_upgrade(config, args, builder).await?, | |
| MarketplaceSubcommand::Remove(args) => run_remove(config, args).await?, | |
| } | |
| Ok(()) | |
| } | |
| } | |
| async fn run_add(config: Config, args: AddMarketplaceArgs) -> Result<()> { | |
| let AddMarketplaceArgs { | |
| source, | |
| ref_name, | |
| sparse_paths, | |
| json, | |
| } = args; | |
| let outcome = add_marketplace( | |
| config.codex_home.to_path_buf(), | |
| config.config_layer_stack.requirements().clone(), | |
| MarketplaceAddRequest { | |
| source, | |
| ref_name, | |
| sparse_paths, | |
| }, | |
| ) | |
| .await?; | |
| if json { | |
| let output = JsonMarketplaceAddOutput::from_outcome(outcome); | |
| println!("{}", serde_json::to_string_pretty(&output)?); | |
| return Ok(()); | |
| } | |
| if outcome.already_added { | |
| println!( | |
| "Marketplace `{}` is already added from {}.", | |
| outcome.marketplace_name, outcome.source_display | |
| ); | |
| } else { | |
| println!( | |
| "Added marketplace `{}` from {}.", | |
| outcome.marketplace_name, outcome.source_display | |
| ); | |
| } | |
| println!( | |
| "Installed marketplace root: {}", | |
| outcome.installed_root.as_path().display() | |
| ); | |
| Ok(()) | |
| } | |
| struct JsonMarketplaceAddOutput { | |
| marketplace_name: String, | |
| installed_root: String, | |
| already_added: bool, | |
| } | |
| impl JsonMarketplaceAddOutput { | |
| fn from_outcome(outcome: MarketplaceAddOutcome) -> Self { | |
| Self { | |
| marketplace_name: outcome.marketplace_name, | |
| installed_root: outcome.installed_root.as_path().display().to_string(), | |
| already_added: outcome.already_added, | |
| } | |
| } | |
| } | |
| async fn run_list(config: Config, args: ListMarketplaceArgs) -> Result<()> { | |
| let manager = plugins_manager_for_config(&config, load_cli_auth_manager(&config).await?); | |
| let plugins_input = config.plugins_config_input(); | |
| let marketplace_listing = manager | |
| .discover_marketplaces_for_config(&plugins_input, &[]) | |
| .context("failed to list plugin marketplaces")?; | |
| let mut load_issues = configured_marketplace_snapshot_issues( | |
| config.codex_home.as_path(), | |
| &plugins_input, | |
| &marketplace_listing.errors, | |
| /*marketplace_name*/ None, | |
| ); | |
| let mut issue_paths = load_issues | |
| .iter() | |
| .map(|issue| issue.path.clone()) | |
| .collect::<HashSet<_>>(); | |
| for error in &marketplace_listing.errors { | |
| if issue_paths.insert(error.path.to_path_buf()) { | |
| load_issues.push(crate::plugin_cmd::ConfiguredMarketplaceSnapshotIssue { | |
| marketplace_name: error.path.display().to_string(), | |
| path: error.path.to_path_buf(), | |
| message: error.message.clone(), | |
| }); | |
| } | |
| } | |
| if !load_issues.is_empty() { | |
| let issue_lines = load_issues | |
| .iter() | |
| .map(|issue| { | |
| format!( | |
| "- `{}` at {}: {}", | |
| issue.marketplace_name, | |
| issue.path.display(), | |
| issue.message | |
| ) | |
| }) | |
| .collect::<Vec<_>>() | |
| .join("\n"); | |
| bail!("failed to load marketplace(s):\n{issue_lines}"); | |
| } | |
| let marketplaces = marketplace_listing.marketplaces; | |
| if args.json { | |
| let marketplace_sources = | |
| configured_marketplace_sources_by_root(config.codex_home.as_path(), &plugins_input); | |
| let output = | |
| JsonMarketplaceListOutput::from_marketplaces(marketplaces, &marketplace_sources); | |
| println!("{}", serde_json::to_string_pretty(&output)?); | |
| return Ok(()); | |
| } | |
| if marketplaces.is_empty() { | |
| println!("No plugin marketplaces in scope."); | |
| return Ok(()); | |
| } | |
| let mut seen_roots = HashSet::new(); | |
| let mut rows = Vec::new(); | |
| for marketplace in marketplaces { | |
| let Ok(root) = marketplace_root_dir(&marketplace.path) else { | |
| continue; | |
| }; | |
| if !seen_roots.insert(root.clone()) { | |
| continue; | |
| } | |
| rows.push((marketplace.name, root)); | |
| } | |
| let marketplace_width = rows | |
| .iter() | |
| .map(|(name, _)| name.len()) | |
| .max() | |
| .unwrap_or("MARKETPLACE".len()) | |
| .max("MARKETPLACE".len()); | |
| println!("{:<marketplace_width$} ROOT", "MARKETPLACE"); | |
| for (marketplace_name, root) in rows { | |
| println!( | |
| "{:<marketplace_width$} {}", | |
| marketplace_name, | |
| root.display() | |
| ); | |
| } | |
| Ok(()) | |
| } | |
| struct JsonMarketplaceListOutput { | |
| marketplaces: Vec<JsonMarketplaceListEntry>, | |
| } | |
| impl JsonMarketplaceListOutput { | |
| fn from_marketplaces( | |
| marketplaces: Vec<codex_core_plugins::marketplace::Marketplace>, | |
| marketplace_sources: &HashMap<PathBuf, JsonMarketplaceSource>, | |
| ) -> Self { | |
| let mut seen_roots = HashSet::new(); | |
| let marketplaces = marketplaces | |
| .into_iter() | |
| .filter_map(|marketplace| { | |
| let root = marketplace_root_dir(&marketplace.path).ok()?; | |
| if !seen_roots.insert(root.clone()) { | |
| return None; | |
| } | |
| Some(JsonMarketplaceListEntry { | |
| marketplace_source: marketplace_sources.get(root.as_path()).cloned(), | |
| name: marketplace.name, | |
| root: root.display().to_string(), | |
| }) | |
| }) | |
| .collect(); | |
| Self { marketplaces } | |
| } | |
| } | |
| struct JsonMarketplaceListEntry { | |
| name: String, | |
| root: String, | |
| marketplace_source: Option<JsonMarketplaceSource>, | |
| } | |
| fn configured_marketplace_sources_by_root( | |
| codex_home: &Path, | |
| plugins_input: &PluginsConfigInput, | |
| ) -> HashMap<PathBuf, JsonMarketplaceSource> { | |
| let marketplace_sources = configured_marketplace_sources(plugins_input, codex_home); | |
| let effective_config = plugins_input.config_layer_stack.effective_config(); | |
| let Some(marketplaces) = effective_config | |
| .get("marketplaces") | |
| .and_then(toml::Value::as_table) | |
| else { | |
| return HashMap::new(); | |
| }; | |
| let default_install_root = marketplace_install_root(codex_home); | |
| marketplaces | |
| .iter() | |
| .filter_map(|(marketplace_name, marketplace)| { | |
| let marketplace_source = marketplace_sources.get(marketplace_name)?; | |
| let root = resolve_configured_marketplace_root( | |
| marketplace_name, | |
| marketplace, | |
| &default_install_root, | |
| )?; | |
| Some((root, marketplace_source.clone())) | |
| }) | |
| .collect() | |
| } | |
| async fn run_upgrade( | |
| config: Config, | |
| args: UpgradeMarketplaceArgs, | |
| builder: codex_core::config::ConfigBuilder, | |
| ) -> Result<()> { | |
| let UpgradeMarketplaceArgs { | |
| marketplace_name, | |
| json, | |
| } = args; | |
| let manager = plugins_manager_for_config(&config, load_cli_auth_manager(&config).await?); | |
| let plugins_input = config.plugins_config_input(); | |
| let runtime = tokio::runtime::Handle::current(); | |
| let reload_config: codex_core_plugins::ConfigLayerReload = std::sync::Arc::new(move || { | |
| runtime | |
| .block_on(builder.clone().build()) | |
| .map(|config| config.config_layer_stack) | |
| }); | |
| let requested_name = marketplace_name.clone(); | |
| let outcome = tokio::task::spawn_blocking(move || { | |
| manager.upgrade_configured_marketplaces_for_config( | |
| &plugins_input, | |
| requested_name.as_deref(), | |
| &reload_config, | |
| ) | |
| }) | |
| .await? | |
| .map_err(anyhow::Error::msg)?; | |
| if json { | |
| print_upgrade_outcome_json(&outcome) | |
| } else { | |
| print_upgrade_outcome(&outcome, marketplace_name.as_deref()) | |
| } | |
| } | |
| async fn run_remove(config: Config, args: RemoveMarketplaceArgs) -> Result<()> { | |
| let RemoveMarketplaceArgs { | |
| marketplace_name, | |
| json, | |
| } = args; | |
| let outcome = remove_marketplace( | |
| config.codex_home.to_path_buf(), | |
| config.config_layer_stack, | |
| MarketplaceRemoveRequest { marketplace_name }, | |
| ) | |
| .await?; | |
| if json { | |
| let output = JsonMarketplaceRemoveOutput::from_outcome(outcome); | |
| println!("{}", serde_json::to_string_pretty(&output)?); | |
| return Ok(()); | |
| } | |
| 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(()) | |
| } | |
| struct JsonMarketplaceRemoveOutput { | |
| marketplace_name: String, | |
| installed_root: Option<String>, | |
| } | |
| impl JsonMarketplaceRemoveOutput { | |
| fn from_outcome(outcome: MarketplaceRemoveOutcome) -> Self { | |
| Self { | |
| marketplace_name: outcome.marketplace_name, | |
| installed_root: outcome | |
| .removed_installed_root | |
| .map(|root| root.as_path().display().to_string()), | |
| } | |
| } | |
| } | |
| fn print_upgrade_outcome_json(outcome: &PluginMarketplaceUpgradeOutcome) -> Result<()> { | |
| for error in &outcome.errors { | |
| eprintln!( | |
| "Failed to upgrade marketplace `{}`: {}", | |
| error.marketplace_name, error.message | |
| ); | |
| } | |
| if !outcome.all_succeeded() { | |
| bail!("{} upgrade failure(s) occurred.", outcome.errors.len()); | |
| } | |
| let output = JsonMarketplaceUpgradeOutput::from_outcome(outcome); | |
| println!("{}", serde_json::to_string_pretty(&output)?); | |
| Ok(()) | |
| } | |
| struct JsonMarketplaceUpgradeOutput { | |
| selected_marketplaces: Vec<String>, | |
| upgraded_roots: Vec<String>, | |
| errors: Vec<JsonMarketplaceUpgradeError>, | |
| } | |
| impl JsonMarketplaceUpgradeOutput { | |
| fn from_outcome(outcome: &PluginMarketplaceUpgradeOutcome) -> Self { | |
| Self { | |
| selected_marketplaces: outcome.selected_marketplaces.clone(), | |
| upgraded_roots: outcome | |
| .upgraded_roots | |
| .iter() | |
| .map(|root| root.display().to_string()) | |
| .collect(), | |
| errors: outcome | |
| .errors | |
| .iter() | |
| .map(|error| JsonMarketplaceUpgradeError { | |
| marketplace_name: error.marketplace_name.clone(), | |
| message: error.message.clone(), | |
| }) | |
| .collect(), | |
| } | |
| } | |
| } | |
| struct JsonMarketplaceUpgradeError { | |
| marketplace_name: String, | |
| message: String, | |
| } | |
| fn print_upgrade_outcome( | |
| outcome: &PluginMarketplaceUpgradeOutcome, | |
| marketplace_name: Option<&str>, | |
| ) -> Result<()> { | |
| for error in &outcome.errors { | |
| eprintln!( | |
| "Failed to upgrade marketplace `{}`: {}", | |
| error.marketplace_name, error.message | |
| ); | |
| } | |
| if !outcome.all_succeeded() { | |
| bail!("{} upgrade failure(s) occurred.", outcome.errors.len()); | |
| } | |
| let selection_label = marketplace_name.unwrap_or("all configured Git marketplaces"); | |
| if outcome.selected_marketplaces.is_empty() { | |
| println!("No configured Git marketplaces to upgrade."); | |
| } else if outcome.upgraded_roots.is_empty() { | |
| if marketplace_name.is_some() { | |
| println!("Marketplace `{selection_label}` is already up to date."); | |
| } else { | |
| println!("All configured Git marketplaces are already up to date."); | |
| } | |
| } else if marketplace_name.is_some() { | |
| println!("Upgraded marketplace `{selection_label}` to the latest configured revision."); | |
| for root in &outcome.upgraded_roots { | |
| println!("Installed marketplace root: {}", root.display()); | |
| } | |
| } else { | |
| println!("Upgraded {} marketplace(s).", outcome.upgraded_roots.len()); | |
| for root in &outcome.upgraded_roots { | |
| println!("Installed marketplace root: {}", root.display()); | |
| } | |
| } | |
| Ok(()) | |
| } | |
| mod tests { | |
| use super::*; | |
| use pretty_assertions::assert_eq; | |
| fn sparse_paths_parse_before_or_after_source() { | |
| let sparse_before_source = | |
| AddMarketplaceArgs::try_parse_from(["add", "--sparse", "plugins/foo", "owner/repo"]) | |
| .unwrap(); | |
| assert_eq!(sparse_before_source.source, "owner/repo"); | |
| assert_eq!(sparse_before_source.sparse_paths, vec!["plugins/foo"]); | |
| let sparse_after_source = | |
| AddMarketplaceArgs::try_parse_from(["add", "owner/repo", "--sparse", "plugins/foo"]) | |
| .unwrap(); | |
| assert_eq!(sparse_after_source.source, "owner/repo"); | |
| assert_eq!(sparse_after_source.sparse_paths, vec!["plugins/foo"]); | |
| let repeated_sparse = AddMarketplaceArgs::try_parse_from([ | |
| "add", | |
| "--sparse", | |
| "plugins/foo", | |
| "--sparse", | |
| "skills/bar", | |
| "owner/repo", | |
| ]) | |
| .unwrap(); | |
| assert_eq!(repeated_sparse.source, "owner/repo"); | |
| assert_eq!( | |
| repeated_sparse.sparse_paths, | |
| vec!["plugins/foo", "skills/bar"] | |
| ); | |
| } | |
| fn upgrade_subcommand_parses_optional_marketplace_name() { | |
| let upgrade_all = UpgradeMarketplaceArgs::try_parse_from(["upgrade"]).unwrap(); | |
| assert_eq!(upgrade_all.marketplace_name, None); | |
| let upgrade_one = UpgradeMarketplaceArgs::try_parse_from(["upgrade", "debug"]).unwrap(); | |
| assert_eq!(upgrade_one.marketplace_name.as_deref(), Some("debug")); | |
| } | |
| fn remove_subcommand_parses_marketplace_name() { | |
| let remove = RemoveMarketplaceArgs::try_parse_from(["remove", "debug"]).unwrap(); | |
| assert_eq!(remove.marketplace_name, "debug"); | |
| } | |
| } | |