mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
deedf3b2c4
## Why `--profile-v2 <name>` gives launchers and runtime entry points a named profile config without making each profile duplicate the base user config. The base `$CODEX_HOME/config.toml` still loads first, then `$CODEX_HOME/<name>.config.toml` layers above it and becomes the active writable user config for that session. That keeps shared defaults, plugin/MCP setup, and managed/user constraints in one place while letting a named profile override only the pieces that need to differ. ## What Changed - Added the shared `--profile-v2 <name>` runtime option with validated plain names, now represented by `ProfileV2Name`. - Extended config layer state so the base user config and selected profile config are both `User` layers; APIs expose the active user layer and merged effective user config. - Threaded profile selection through runtime entry points: `codex`, `codex exec`, `codex review`, `codex resume`, `codex fork`, and `codex debug prompt-input`. - Made user-facing config writes go to the selected profile file when active, including TUI/settings persistence, app-server config writes, and MCP/app tool approval persistence. - Made plugin, marketplace, MCP, hooks, and config reload paths read from the merged user config so base and profile layers both participate. - Updated app-server config layer schemas to mark profile-backed user layers. ## Limits `--profile-v2` is still rejected for config-management subcommands such as feature, MCP, and marketplace edits. Those paths remain tied to the base `config.toml` until they have explicit profile-selection semantics. Some adjacent background writes may still update base or global state rather than the selected profile: - marketplace auto-upgrade metadata - automatic MCP dependency installs from skills - remote plugin sync or uninstall config edits - personality migration marker/default writes ## Verification Added targeted coverage for profile name validation, layer ordering/merging, selected-profile writes, app-server config writes, session hot reload, plugin config merging, hooks/config fixture updates, and MCP/app approval persistence. --------- Co-authored-by: Codex <noreply@openai.com>
191 lines
6.3 KiB
Rust
191 lines
6.3 KiB
Rust
//! Shared command-line flags used by both interactive and non-interactive Codex entry points.
|
|
|
|
use crate::SandboxModeCliArg;
|
|
use clap::Args;
|
|
use codex_protocol::config_types::ProfileV2Name;
|
|
use std::path::PathBuf;
|
|
|
|
#[derive(Args, Debug, Default)]
|
|
pub struct SharedCliOptions {
|
|
/// Optional image(s) to attach to the initial prompt.
|
|
#[arg(
|
|
long = "image",
|
|
short = 'i',
|
|
value_name = "FILE",
|
|
value_delimiter = ',',
|
|
num_args = 1..
|
|
)]
|
|
pub images: Vec<PathBuf>,
|
|
|
|
/// Model the agent should use.
|
|
#[arg(long, short = 'm')]
|
|
pub model: Option<String>,
|
|
|
|
/// Use open-source provider.
|
|
#[arg(long = "oss", default_value_t = false)]
|
|
pub oss: bool,
|
|
|
|
/// Specify which local provider to use (lmstudio or ollama).
|
|
/// If not specified with --oss, will use config default or show selection.
|
|
#[arg(long = "local-provider")]
|
|
pub oss_provider: Option<String>,
|
|
|
|
/// Configuration profile from config.toml to specify default options.
|
|
#[arg(long = "profile", short = 'p')]
|
|
pub config_profile: Option<String>,
|
|
|
|
/// Layer $CODEX_HOME/<name>.config.toml on top of the base user config.
|
|
#[arg(long = "profile-v2")]
|
|
pub config_profile_v2: Option<ProfileV2Name>,
|
|
|
|
/// Select the sandbox policy to use when executing model-generated shell
|
|
/// commands.
|
|
#[arg(long = "sandbox", short = 's')]
|
|
pub sandbox_mode: Option<SandboxModeCliArg>,
|
|
|
|
/// Skip all confirmation prompts and execute commands without sandboxing.
|
|
/// EXTREMELY DANGEROUS. Intended solely for running in environments that are externally sandboxed.
|
|
#[arg(
|
|
long = "dangerously-bypass-approvals-and-sandbox",
|
|
alias = "yolo",
|
|
default_value_t = false
|
|
)]
|
|
pub dangerously_bypass_approvals_and_sandbox: bool,
|
|
|
|
/// Run enabled hooks without requiring persisted hook trust for this invocation.
|
|
/// DANGEROUS. Intended only for automation that already vets hook sources.
|
|
#[arg(long = "dangerously-bypass-hook-trust", default_value_t = false)]
|
|
pub bypass_hook_trust: bool,
|
|
|
|
/// Tell the agent to use the specified directory as its working root.
|
|
#[clap(long = "cd", short = 'C', value_name = "DIR")]
|
|
pub cwd: Option<PathBuf>,
|
|
|
|
/// Additional directories that should be writable alongside the primary workspace.
|
|
#[arg(long = "add-dir", value_name = "DIR", value_hint = clap::ValueHint::DirPath)]
|
|
pub add_dir: Vec<PathBuf>,
|
|
}
|
|
|
|
impl SharedCliOptions {
|
|
pub fn inherit_exec_root_options(&mut self, root: &Self) {
|
|
let self_selected_sandbox_mode =
|
|
self.sandbox_mode.is_some() || self.dangerously_bypass_approvals_and_sandbox;
|
|
let Self {
|
|
images,
|
|
model,
|
|
oss,
|
|
oss_provider,
|
|
config_profile,
|
|
config_profile_v2,
|
|
sandbox_mode,
|
|
dangerously_bypass_approvals_and_sandbox,
|
|
bypass_hook_trust,
|
|
cwd,
|
|
add_dir,
|
|
} = self;
|
|
let Self {
|
|
images: root_images,
|
|
model: root_model,
|
|
oss: root_oss,
|
|
oss_provider: root_oss_provider,
|
|
config_profile: root_config_profile,
|
|
config_profile_v2: root_config_profile_v2,
|
|
sandbox_mode: root_sandbox_mode,
|
|
dangerously_bypass_approvals_and_sandbox: root_dangerously_bypass_approvals_and_sandbox,
|
|
bypass_hook_trust: root_bypass_hook_trust,
|
|
cwd: root_cwd,
|
|
add_dir: root_add_dir,
|
|
} = root;
|
|
|
|
if model.is_none() {
|
|
model.clone_from(root_model);
|
|
}
|
|
if *root_oss {
|
|
*oss = true;
|
|
}
|
|
if oss_provider.is_none() {
|
|
oss_provider.clone_from(root_oss_provider);
|
|
}
|
|
if config_profile.is_none() {
|
|
config_profile.clone_from(root_config_profile);
|
|
}
|
|
if config_profile_v2.is_none() {
|
|
config_profile_v2.clone_from(root_config_profile_v2);
|
|
}
|
|
if sandbox_mode.is_none() {
|
|
*sandbox_mode = *root_sandbox_mode;
|
|
}
|
|
if !self_selected_sandbox_mode {
|
|
*dangerously_bypass_approvals_and_sandbox =
|
|
*root_dangerously_bypass_approvals_and_sandbox;
|
|
}
|
|
if !*bypass_hook_trust {
|
|
*bypass_hook_trust = *root_bypass_hook_trust;
|
|
}
|
|
if cwd.is_none() {
|
|
cwd.clone_from(root_cwd);
|
|
}
|
|
if !root_images.is_empty() {
|
|
let mut merged_images = root_images.clone();
|
|
merged_images.append(images);
|
|
*images = merged_images;
|
|
}
|
|
if !root_add_dir.is_empty() {
|
|
let mut merged_add_dir = root_add_dir.clone();
|
|
merged_add_dir.append(add_dir);
|
|
*add_dir = merged_add_dir;
|
|
}
|
|
}
|
|
|
|
pub fn apply_subcommand_overrides(&mut self, subcommand: Self) {
|
|
let subcommand_selected_sandbox_mode = subcommand.sandbox_mode.is_some()
|
|
|| subcommand.dangerously_bypass_approvals_and_sandbox;
|
|
let Self {
|
|
images,
|
|
model,
|
|
oss,
|
|
oss_provider,
|
|
config_profile,
|
|
config_profile_v2,
|
|
sandbox_mode,
|
|
dangerously_bypass_approvals_and_sandbox,
|
|
bypass_hook_trust,
|
|
cwd,
|
|
add_dir,
|
|
} = subcommand;
|
|
|
|
if let Some(model) = model {
|
|
self.model = Some(model);
|
|
}
|
|
if oss {
|
|
self.oss = true;
|
|
}
|
|
if let Some(oss_provider) = oss_provider {
|
|
self.oss_provider = Some(oss_provider);
|
|
}
|
|
if let Some(config_profile) = config_profile {
|
|
self.config_profile = Some(config_profile);
|
|
}
|
|
if let Some(config_profile_v2) = config_profile_v2 {
|
|
self.config_profile_v2 = Some(config_profile_v2);
|
|
}
|
|
if subcommand_selected_sandbox_mode {
|
|
self.sandbox_mode = sandbox_mode;
|
|
self.dangerously_bypass_approvals_and_sandbox =
|
|
dangerously_bypass_approvals_and_sandbox;
|
|
}
|
|
if bypass_hook_trust {
|
|
self.bypass_hook_trust = true;
|
|
}
|
|
if let Some(cwd) = cwd {
|
|
self.cwd = Some(cwd);
|
|
}
|
|
if !images.is_empty() {
|
|
self.images = images;
|
|
}
|
|
if !add_dir.is_empty() {
|
|
self.add_dir.extend(add_dir);
|
|
}
|
|
}
|
|
}
|