## Summary
- Factor `load_config_as_toml` into `core::config_loader` so config
loading is reusable across callers.
- Layer `~/.codex/config.toml`, optional `~/.codex/managed_config.toml`,
and macOS managed preferences (base64) with recursive table merging and
scoped threads per source.
## Config Flow
```
Managed prefs (macOS profile: com.openai.codex/config_toml_base64)
▲
│
~/.codex/managed_config.toml │ (optional file-based override)
▲
│
~/.codex/config.toml (user-defined settings)
```
- The loader searches under the resolved `CODEX_HOME` directory
(defaults to `~/.codex`).
- Managed configs let administrators ship fleet-wide overrides via
device profiles which is useful for enforcing certain settings like
sandbox or approval defaults.
- For nested hash tables: overlays merge recursively. Child tables are
merged key-by-key, while scalar or array values replace the prior layer
entirely. This lets admins add or tweak individual fields without
clobbering unrelated user settings.
81 lines
2.4 KiB
Rust
81 lines
2.4 KiB
Rust
use std::path::PathBuf;
|
|
|
|
use clap::Parser;
|
|
use codex_common::CliConfigOverrides;
|
|
use codex_core::config::Config;
|
|
use codex_core::config::ConfigOverrides;
|
|
|
|
use crate::chatgpt_token::init_chatgpt_token_from_auth;
|
|
use crate::get_task::GetTaskResponse;
|
|
use crate::get_task::OutputItem;
|
|
use crate::get_task::PrOutputItem;
|
|
use crate::get_task::get_task;
|
|
|
|
/// Applies the latest diff from a Codex agent task.
|
|
#[derive(Debug, Parser)]
|
|
pub struct ApplyCommand {
|
|
pub task_id: String,
|
|
|
|
#[clap(flatten)]
|
|
pub config_overrides: CliConfigOverrides,
|
|
}
|
|
pub async fn run_apply_command(
|
|
apply_cli: ApplyCommand,
|
|
cwd: Option<PathBuf>,
|
|
) -> anyhow::Result<()> {
|
|
let config = Config::load_with_cli_overrides(
|
|
apply_cli
|
|
.config_overrides
|
|
.parse_overrides()
|
|
.map_err(anyhow::Error::msg)?,
|
|
ConfigOverrides::default(),
|
|
)
|
|
.await?;
|
|
|
|
init_chatgpt_token_from_auth(&config.codex_home).await?;
|
|
|
|
let task_response = get_task(&config, apply_cli.task_id).await?;
|
|
apply_diff_from_task(task_response, cwd).await
|
|
}
|
|
|
|
pub async fn apply_diff_from_task(
|
|
task_response: GetTaskResponse,
|
|
cwd: Option<PathBuf>,
|
|
) -> anyhow::Result<()> {
|
|
let diff_turn = match task_response.current_diff_task_turn {
|
|
Some(turn) => turn,
|
|
None => anyhow::bail!("No diff turn found"),
|
|
};
|
|
let output_diff = diff_turn.output_items.iter().find_map(|item| match item {
|
|
OutputItem::Pr(PrOutputItem { output_diff }) => Some(output_diff),
|
|
_ => None,
|
|
});
|
|
match output_diff {
|
|
Some(output_diff) => apply_diff(&output_diff.diff, cwd).await,
|
|
None => anyhow::bail!("No PR output item found"),
|
|
}
|
|
}
|
|
|
|
async fn apply_diff(diff: &str, cwd: Option<PathBuf>) -> anyhow::Result<()> {
|
|
let cwd = cwd.unwrap_or(std::env::current_dir().unwrap_or_else(|_| std::env::temp_dir()));
|
|
let req = codex_git_apply::ApplyGitRequest {
|
|
cwd,
|
|
diff: diff.to_string(),
|
|
revert: false,
|
|
preflight: false,
|
|
};
|
|
let res = codex_git_apply::apply_git_patch(&req)?;
|
|
if res.exit_code != 0 {
|
|
anyhow::bail!(
|
|
"Git apply failed (applied={}, skipped={}, conflicts={})\nstdout:\n{}\nstderr:\n{}",
|
|
res.applied_paths.len(),
|
|
res.skipped_paths.len(),
|
|
res.conflicted_paths.len(),
|
|
res.stdout,
|
|
res.stderr
|
|
);
|
|
}
|
|
println!("Successfully applied diff");
|
|
Ok(())
|
|
}
|