mirror of
https://github.com/openai/codex.git
synced 2026-04-28 08:34:54 +00:00
Add marketplace command (#17087)
Added a new top-level `codex marketplace add` command for installing plugin marketplaces into Codex’s local marketplace cache. This change adds source parsing for local directories, GitHub shorthand, and git URLs, supports optional `--ref` and git-only `--sparse` checkout paths, stages the source in a temp directory, validates the marketplace manifest, and installs it under `$CODEX_HOME/marketplaces/<marketplace-name>` Included tests cover local install behavior in the CLI and marketplace discovery from installed roots in core. Scoped formatting and fix passes were run, and targeted CLI/core tests passed.
This commit is contained in:
@@ -12,6 +12,7 @@ use crate::types::AppsConfigToml;
|
||||
use crate::types::AuthCredentialsStoreMode;
|
||||
use crate::types::FeedbackConfigToml;
|
||||
use crate::types::History;
|
||||
use crate::types::MarketplaceConfig;
|
||||
use crate::types::McpServerConfig;
|
||||
use crate::types::MemoriesToml;
|
||||
use crate::types::Notice;
|
||||
@@ -325,6 +326,10 @@ pub struct ConfigToml {
|
||||
#[serde(default)]
|
||||
pub plugins: HashMap<String, PluginConfig>,
|
||||
|
||||
/// User-level marketplace entries keyed by marketplace name.
|
||||
#[serde(default)]
|
||||
pub marketplaces: HashMap<String, MarketplaceConfig>,
|
||||
|
||||
/// Centralized feature flags (new). Prefer this over individual toggles.
|
||||
#[serde(default)]
|
||||
// Injects known feature keys into the schema and forbids unknown keys.
|
||||
|
||||
@@ -4,6 +4,7 @@ pub mod config_toml;
|
||||
mod constraint;
|
||||
mod diagnostics;
|
||||
mod fingerprint;
|
||||
mod marketplace_edit;
|
||||
mod mcp_edit;
|
||||
mod mcp_types;
|
||||
mod merge;
|
||||
@@ -57,6 +58,8 @@ pub use diagnostics::format_config_error;
|
||||
pub use diagnostics::format_config_error_with_source;
|
||||
pub use diagnostics::io_error_from_config_error;
|
||||
pub use fingerprint::version_for_toml;
|
||||
pub use marketplace_edit::MarketplaceConfigUpdate;
|
||||
pub use marketplace_edit::record_user_marketplace;
|
||||
pub use mcp_edit::ConfigEditsBuilder;
|
||||
pub use mcp_edit::load_global_mcp_servers;
|
||||
pub use mcp_types::AppToolApproval;
|
||||
|
||||
83
codex-rs/config/src/marketplace_edit.rs
Normal file
83
codex-rs/config/src/marketplace_edit.rs
Normal file
@@ -0,0 +1,83 @@
|
||||
use std::fs;
|
||||
use std::io::ErrorKind;
|
||||
use std::path::Path;
|
||||
|
||||
use toml_edit::DocumentMut;
|
||||
use toml_edit::Item as TomlItem;
|
||||
use toml_edit::Table as TomlTable;
|
||||
use toml_edit::Value as TomlValue;
|
||||
use toml_edit::value;
|
||||
|
||||
use crate::CONFIG_TOML_FILE;
|
||||
|
||||
pub struct MarketplaceConfigUpdate<'a> {
|
||||
pub last_updated: &'a str,
|
||||
pub source_type: &'a str,
|
||||
pub source: &'a str,
|
||||
pub ref_name: Option<&'a str>,
|
||||
pub sparse_paths: &'a [String],
|
||||
}
|
||||
|
||||
pub fn record_user_marketplace(
|
||||
codex_home: &Path,
|
||||
marketplace_name: &str,
|
||||
update: &MarketplaceConfigUpdate<'_>,
|
||||
) -> std::io::Result<()> {
|
||||
let config_path = codex_home.join(CONFIG_TOML_FILE);
|
||||
let mut doc = read_or_create_document(&config_path)?;
|
||||
upsert_marketplace(&mut doc, marketplace_name, update);
|
||||
fs::create_dir_all(codex_home)?;
|
||||
fs::write(config_path, doc.to_string())
|
||||
}
|
||||
|
||||
fn read_or_create_document(config_path: &Path) -> std::io::Result<DocumentMut> {
|
||||
match fs::read_to_string(config_path) {
|
||||
Ok(raw) => raw
|
||||
.parse::<DocumentMut>()
|
||||
.map_err(|err| std::io::Error::new(ErrorKind::InvalidData, err)),
|
||||
Err(err) if err.kind() == ErrorKind::NotFound => Ok(DocumentMut::new()),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
fn upsert_marketplace(
|
||||
doc: &mut DocumentMut,
|
||||
marketplace_name: &str,
|
||||
update: &MarketplaceConfigUpdate<'_>,
|
||||
) {
|
||||
let root = doc.as_table_mut();
|
||||
if !root.contains_key("marketplaces") {
|
||||
root.insert("marketplaces", TomlItem::Table(new_implicit_table()));
|
||||
}
|
||||
|
||||
let Some(marketplaces_item) = root.get_mut("marketplaces") else {
|
||||
return;
|
||||
};
|
||||
if !marketplaces_item.is_table() {
|
||||
*marketplaces_item = TomlItem::Table(new_implicit_table());
|
||||
}
|
||||
|
||||
let Some(marketplaces) = marketplaces_item.as_table_mut() else {
|
||||
return;
|
||||
};
|
||||
let mut entry = TomlTable::new();
|
||||
entry.set_implicit(false);
|
||||
entry["last_updated"] = value(update.last_updated.to_string());
|
||||
entry["source_type"] = value(update.source_type.to_string());
|
||||
entry["source"] = value(update.source.to_string());
|
||||
if let Some(ref_name) = update.ref_name {
|
||||
entry["ref"] = value(ref_name.to_string());
|
||||
}
|
||||
if !update.sparse_paths.is_empty() {
|
||||
entry["sparse_paths"] = TomlItem::Value(TomlValue::Array(
|
||||
update.sparse_paths.iter().map(String::as_str).collect(),
|
||||
));
|
||||
}
|
||||
marketplaces.insert(marketplace_name, TomlItem::Table(entry));
|
||||
}
|
||||
|
||||
fn new_implicit_table() -> TomlTable {
|
||||
let mut table = TomlTable::new();
|
||||
table.set_implicit(true);
|
||||
table
|
||||
}
|
||||
@@ -608,6 +608,32 @@ pub struct PluginConfig {
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
|
||||
#[schemars(deny_unknown_fields)]
|
||||
pub struct MarketplaceConfig {
|
||||
/// Last time Codex successfully added or refreshed this marketplace.
|
||||
#[serde(default)]
|
||||
pub last_updated: Option<String>,
|
||||
/// Source kind used to install this marketplace.
|
||||
#[serde(default)]
|
||||
pub source_type: Option<MarketplaceSourceType>,
|
||||
/// Source location used when the marketplace was added.
|
||||
#[serde(default)]
|
||||
pub source: Option<String>,
|
||||
/// Git ref to check out when `source_type` is `git`.
|
||||
#[serde(default, rename = "ref")]
|
||||
pub ref_name: Option<String>,
|
||||
/// Sparse checkout paths used when `source_type` is `git`.
|
||||
#[serde(default)]
|
||||
pub sparse_paths: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum MarketplaceSourceType {
|
||||
Git,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)]
|
||||
#[schemars(deny_unknown_fields)]
|
||||
pub struct SandboxWorkspaceWrite {
|
||||
|
||||
Reference in New Issue
Block a user