Files
codex/codex-rs/cli/src/marketplace_cmd.rs
T
xli-oai ff584c5a4b [codex] Refactor marketplace add into shared core flow (#17717)
## Summary

Move `codex marketplace add` onto a shared core implementation so the
CLI and app-server path can use one source of truth.

This change:
- adds shared marketplace-add orchestration in `codex-core`
- switches the CLI command to call that shared implementation
- removes duplicated CLI-only marketplace add helpers
- preserves focused parser and add-path coverage while moving the shared
behavior into core tests

## Why

The new `marketplace/add` RPC should reuse the same underlying
marketplace-add flow as the CLI. This refactor lands that consolidation
first so the follow-up app-server PR can be mostly protocol and handler
wiring.

## Validation

- `cargo test -p codex-core marketplace_add`
- `cargo test -p codex-cli marketplace_cmd`
- `just fix -p codex-core`
- `just fix -p codex-cli`
- `just fmt`
2026-04-13 20:37:11 -07:00

135 lines
3.7 KiB
Rust

use anyhow::Context;
use anyhow::Result;
use clap::Parser;
use codex_core::config::find_codex_home;
use codex_core::plugins::MarketplaceAddRequest;
use codex_core::plugins::add_marketplace;
use codex_utils_cli::CliConfigOverrides;
#[derive(Debug, Parser)]
pub struct MarketplaceCli {
#[clap(flatten)]
pub config_overrides: CliConfigOverrides,
#[command(subcommand)]
subcommand: MarketplaceSubcommand,
}
#[derive(Debug, clap::Subcommand)]
enum MarketplaceSubcommand {
/// Add a remote marketplace repository.
Add(AddMarketplaceArgs),
}
#[derive(Debug, Parser)]
struct AddMarketplaceArgs {
/// Marketplace source. Supports owner/repo[@ref], HTTP(S) Git URLs, or SSH URLs.
source: String,
/// Git ref to check out. Overrides any @ref or #ref suffix in SOURCE.
#[arg(long = "ref", value_name = "REF")]
ref_name: Option<String>,
/// Sparse-checkout path to use while cloning git sources. Repeat to include multiple paths.
#[arg(
long = "sparse",
value_name = "PATH",
action = clap::ArgAction::Append
)]
sparse_paths: Vec<String>,
}
impl MarketplaceCli {
pub async fn run(self) -> Result<()> {
let MarketplaceCli {
config_overrides,
subcommand,
} = self;
// Validate overrides now. This command writes to CODEX_HOME only; marketplace discovery
// happens from that cache root after the next plugin/list or app-server start.
config_overrides
.parse_overrides()
.map_err(anyhow::Error::msg)?;
match subcommand {
MarketplaceSubcommand::Add(args) => run_add(args).await?,
}
Ok(())
}
}
async fn run_add(args: AddMarketplaceArgs) -> Result<()> {
let AddMarketplaceArgs {
source,
ref_name,
sparse_paths,
} = args;
let codex_home = find_codex_home().context("failed to resolve CODEX_HOME")?;
let outcome = add_marketplace(
codex_home.to_path_buf(),
MarketplaceAddRequest {
source,
ref_name,
sparse_paths,
},
)
.await?;
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(())
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
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"]
);
}
}