Skip to content
Merged
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
186 changes: 180 additions & 6 deletions codex-rs/cli/src/marketplace_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ use codex_core::config::find_codex_home;
use codex_core_plugins::PluginMarketplaceUpgradeOutcome;
use codex_core_plugins::PluginsManager;
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::HashSet;

use crate::plugin_cmd::configured_marketplace_snapshot_issues;
Expand All @@ -32,7 +35,7 @@ enum MarketplaceSubcommand {
Add(AddMarketplaceArgs),

/// List plugin marketplaces Codex is currently considering and their roots.
List,
List(ListMarketplaceArgs),

/// Refresh configured Git marketplace snapshots.
///
Expand Down Expand Up @@ -64,6 +67,18 @@ struct AddMarketplaceArgs {
action = clap::ArgAction::Append
)]
sparse_paths: Vec<String>,

/// Output add result as JSON.
#[arg(long = "json")]
json: bool,
}

#[derive(Debug, Parser)]
#[command(bin_name = "codex plugin marketplace list")]
struct ListMarketplaceArgs {
/// Output marketplace list as JSON.
#[arg(long = "json")]
json: bool,
}

#[derive(Debug, Parser)]
Expand All @@ -75,6 +90,10 @@ struct UpgradeMarketplaceArgs {
/// Optional configured marketplace name to upgrade. Omit to upgrade all Git marketplaces.
#[arg(value_name = "MARKETPLACE_NAME")]
marketplace_name: Option<String>,

/// Output upgrade result as JSON.
#[arg(long = "json")]
json: bool,
}

#[derive(Debug, Parser)]
Expand All @@ -86,6 +105,10 @@ struct RemoveMarketplaceArgs {
/// Configured marketplace name to remove.
#[arg(value_name = "MARKETPLACE_NAME")]
marketplace_name: String,

/// Output remove result as JSON.
#[arg(long = "json")]
json: bool,
}

impl MarketplaceCli {
Expand All @@ -101,7 +124,7 @@ impl MarketplaceCli {

match subcommand {
MarketplaceSubcommand::Add(args) => run_add(args).await?,
MarketplaceSubcommand::List => run_list(overrides).await?,
MarketplaceSubcommand::List(args) => run_list(overrides, args).await?,
MarketplaceSubcommand::Upgrade(args) => run_upgrade(overrides, args).await?,
MarketplaceSubcommand::Remove(args) => run_remove(args).await?,
}
Expand All @@ -115,6 +138,7 @@ async fn run_add(args: AddMarketplaceArgs) -> Result<()> {
source,
ref_name,
sparse_paths,
json,
} = args;

let codex_home = find_codex_home().context("failed to resolve CODEX_HOME")?;
Expand All @@ -128,6 +152,12 @@ async fn run_add(args: AddMarketplaceArgs) -> Result<()> {
)
.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 {}.",
Expand All @@ -147,7 +177,25 @@ async fn run_add(args: AddMarketplaceArgs) -> Result<()> {
Ok(())
}

async fn run_list(overrides: Vec<(String, toml::Value)>) -> Result<()> {
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
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(overrides: Vec<(String, toml::Value)>, args: ListMarketplaceArgs) -> Result<()> {
let config = Config::load_with_cli_overrides(overrides)
.await
.context("failed to load configuration")?;
Expand Down Expand Up @@ -191,6 +239,12 @@ async fn run_list(overrides: Vec<(String, toml::Value)>) -> Result<()> {
bail!("failed to load marketplace(s):\n{issue_lines}");
}
let marketplaces = marketplace_listing.marketplaces;
if args.json {
let output = JsonMarketplaceListOutput::from_marketplaces(marketplaces);
println!("{}", serde_json::to_string_pretty(&output)?);
return Ok(());
}

if marketplaces.is_empty() {
println!("No plugin marketplaces in scope.");
return Ok(());
Expand Down Expand Up @@ -227,11 +281,48 @@ async fn run_list(overrides: Vec<(String, toml::Value)>) -> Result<()> {
Ok(())
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct JsonMarketplaceListOutput {
marketplaces: Vec<JsonMarketplaceListEntry>,
}

impl JsonMarketplaceListOutput {
fn from_marketplaces(marketplaces: Vec<codex_core_plugins::marketplace::Marketplace>) -> 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 {
name: marketplace.name,
root: root.display().to_string(),
})
})
.collect();

Self { marketplaces }
}
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct JsonMarketplaceListEntry {
name: String,
root: String,
}

async fn run_upgrade(
overrides: Vec<(String, toml::Value)>,
args: UpgradeMarketplaceArgs,
) -> Result<()> {
let UpgradeMarketplaceArgs { marketplace_name } = args;
let UpgradeMarketplaceArgs {
marketplace_name,
json,
} = args;
let config = Config::load_with_cli_overrides(overrides)
.await
.context("failed to load configuration")?;
Expand All @@ -241,18 +332,31 @@ async fn run_upgrade(
let outcome = manager
.upgrade_configured_marketplaces_for_config(&plugins_input, marketplace_name.as_deref())
.map_err(anyhow::Error::msg)?;
print_upgrade_outcome(&outcome, marketplace_name.as_deref())
if json {
print_upgrade_outcome_json(&outcome)
} else {
print_upgrade_outcome(&outcome, marketplace_name.as_deref())
}
}

async fn run_remove(args: RemoveMarketplaceArgs) -> Result<()> {
let RemoveMarketplaceArgs { marketplace_name } = args;
let RemoveMarketplaceArgs {
marketplace_name,
json,
} = 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?;

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!(
Expand All @@ -264,6 +368,76 @@ async fn run_remove(args: RemoveMarketplaceArgs) -> Result<()> {
Ok(())
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
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(())
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
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(),
}
}
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct JsonMarketplaceUpgradeError {
marketplace_name: String,
message: String,
}

fn print_upgrade_outcome(
outcome: &PluginMarketplaceUpgradeOutcome,
marketplace_name: Option<&str>,
Expand Down
Loading
Loading