mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
Add JSON output for plugin subcommands (#26631)
## Summary - Follow-up to #25330 and #26417 - Add `--json` output for `codex plugin add` and `codex plugin remove` - Add `--json` output for `codex plugin marketplace add/list/upgrade/remove` - Keep existing human-readable output unchanged - Keep existing error handling/stderr behavior unchanged; `--json` changes successful stdout output only - Align marketplace add/remove JSON field names with the existing app-server protocol shape - Add CLI coverage for plugin and marketplace JSON outputs ## Validation - `just fmt` - `just fix -p codex-cli` - `just test -p codex-cli`
This commit is contained in:
1 parent
055c7a7c53
commit
bb7d19bc24
6 files changed
+438
-9
No files matched your search
@@ -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;
|
||||
@@ -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.
|
||||
///
|
||||
@@ -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)]
|
||||
@@ -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)]
|
||||
@@ -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 {
|
||||
@@ -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?,
|
||||
}
|
||||
@@ -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")?;
|
||||
@@ -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 {}.",
|
||||
@@ -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")?;
|
||||
@@ -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(());
|
||||
@@ -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")?;
|
||||
@@ -241,11 +332,18 @@ 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(),
|
||||
@@ -253,6 +351,12 @@ async fn run_remove(args: RemoveMarketplaceArgs) -> Result<()> {
|
||||
)
|
||||
.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!(
|
||||
@@ -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>,
|
||||
|
||||
Reference in new issue
Block a user