From 9ec2873084a13f53fa4f6f6ec297b51efb507523 Mon Sep 17 00:00:00 2001 From: kevin zhao Date: Wed, 19 Nov 2025 20:30:20 +0000 Subject: [PATCH] commonizing cli logic --- codex-rs/cli/src/main.rs | 49 +----------------------- codex-rs/execpolicy2/src/cli.rs | 64 ++++++++++++++++++++++++++++++++ codex-rs/execpolicy2/src/lib.rs | 2 + codex-rs/execpolicy2/src/main.rs | 52 +++----------------------- 4 files changed, 73 insertions(+), 94 deletions(-) create mode 100644 codex-rs/execpolicy2/src/cli.rs diff --git a/codex-rs/cli/src/main.rs b/codex-rs/cli/src/main.rs index a234cdb8ec..7d701b9552 100644 --- a/codex-rs/cli/src/main.rs +++ b/codex-rs/cli/src/main.rs @@ -1,4 +1,3 @@ -use anyhow::Context; use clap::Args; use clap::CommandFactory; use clap::Parser; @@ -19,13 +18,12 @@ use codex_cli::login::run_logout; use codex_cloud_tasks::Cli as CloudTasksCli; use codex_common::CliConfigOverrides; use codex_exec::Cli as ExecCli; -use codex_execpolicy2::PolicyParser; +use codex_execpolicy2::ExecPolicyCheckCommand; use codex_responses_api_proxy::Args as ResponsesApiProxyArgs; use codex_tui::AppExitInfo; use codex_tui::Cli as TuiCli; use codex_tui::update_action::UpdateAction; use owo_colors::OwoColorize; -use std::fs; use std::path::PathBuf; use supports_color::Stream; @@ -253,26 +251,6 @@ struct StdioToUdsCommand { socket_path: PathBuf, } -#[derive(Debug, Parser)] -struct ExecPolicyCheckCommand { - /// Paths to execpolicy files to evaluate (repeatable). - #[arg(short, long = "policy", value_name = "PATH", required = true)] - policies: Vec, - - /// Pretty-print the JSON output. - #[arg(long)] - pretty: bool, - - /// Command tokens to check against the policy. - #[arg( - value_name = "COMMAND", - required = true, - trailing_var_arg = true, - allow_hyphen_values = true - )] - command: Vec, -} - fn format_exit_messages(exit_info: AppExitInfo, color_enabled: bool) -> Vec { let AppExitInfo { token_usage, @@ -351,34 +329,11 @@ fn run_update_action(action: UpdateAction) -> anyhow::Result<()> { } fn run_execpolicy_check(cli: ExecPolicyCheckCommand) -> anyhow::Result<()> { - let policy = load_policies(&cli.policies)?; - let evaluation = policy.check(&cli.command); - - let json = if cli.pretty { - serde_json::to_string_pretty(&evaluation)? - } else { - serde_json::to_string(&evaluation)? - }; - + let json = cli.to_json()?; println!("{json}"); Ok(()) } -fn load_policies(policy_paths: &[PathBuf]) -> anyhow::Result { - let mut parser = PolicyParser::new(); - - for policy_path in policy_paths { - let policy_file_contents = fs::read_to_string(policy_path) - .with_context(|| format!("failed to read policy at {}", policy_path.display()))?; - let policy_identifier = policy_path.to_string_lossy().to_string(); - parser - .parse(&policy_identifier, &policy_file_contents) - .with_context(|| format!("failed to parse policy at {}", policy_path.display()))?; - } - - Ok(parser.build()) -} - #[derive(Debug, Default, Parser, Clone)] struct FeatureToggles { /// Enable a feature (repeatable). Equivalent to `-c features.=true`. diff --git a/codex-rs/execpolicy2/src/cli.rs b/codex-rs/execpolicy2/src/cli.rs new file mode 100644 index 0000000000..d7f5edb5ab --- /dev/null +++ b/codex-rs/execpolicy2/src/cli.rs @@ -0,0 +1,64 @@ +use std::fs; +use std::path::PathBuf; + +use anyhow::Context; +use anyhow::Result; +use clap::Parser; + +use crate::Evaluation; +use crate::Policy; +use crate::PolicyParser; + +/// Arguments for evaluating a command against one or more execpolicy files. +#[derive(Debug, Parser, Clone)] +pub struct ExecPolicyCheckCommand { + /// Paths to execpolicy files to evaluate (repeatable). + #[arg(short, long = "policy", value_name = "PATH", required = true)] + pub policies: Vec, + + /// Pretty-print the JSON output. + #[arg(long)] + pub pretty: bool, + + /// Command tokens to check against the policy. + #[arg( + value_name = "COMMAND", + required = true, + trailing_var_arg = true, + allow_hyphen_values = true + )] + pub command: Vec, +} + +impl ExecPolicyCheckCommand { + /// Load the policies for this command, evaluate the command, and render JSON output. + pub fn to_json(&self) -> Result { + let policy = load_policies(&self.policies)?; + let evaluation = policy.check(&self.command); + + format_evaluation_json(&evaluation, self.pretty) + } +} + +pub fn format_evaluation_json(evaluation: &Evaluation, pretty: bool) -> Result { + if pretty { + serde_json::to_string_pretty(evaluation).map_err(Into::into) + } else { + serde_json::to_string(evaluation).map_err(Into::into) + } +} + +pub fn load_policies(policy_paths: &[PathBuf]) -> Result { + let mut parser = PolicyParser::new(); + + for policy_path in policy_paths { + let policy_file_contents = fs::read_to_string(policy_path) + .with_context(|| format!("failed to read policy at {}", policy_path.display()))?; + let policy_identifier = policy_path.to_string_lossy().to_string(); + parser + .parse(&policy_identifier, &policy_file_contents) + .with_context(|| format!("failed to parse policy at {}", policy_path.display()))?; + } + + Ok(parser.build()) +} diff --git a/codex-rs/execpolicy2/src/lib.rs b/codex-rs/execpolicy2/src/lib.rs index 1b789fd862..5710c8ed67 100644 --- a/codex-rs/execpolicy2/src/lib.rs +++ b/codex-rs/execpolicy2/src/lib.rs @@ -1,9 +1,11 @@ +pub mod cli; pub mod decision; pub mod error; pub mod parser; pub mod policy; pub mod rule; +pub use cli::ExecPolicyCheckCommand; pub use decision::Decision; pub use error::Error; pub use error::Result; diff --git a/codex-rs/execpolicy2/src/main.rs b/codex-rs/execpolicy2/src/main.rs index f654c5f84a..2e1ca2cad0 100644 --- a/codex-rs/execpolicy2/src/main.rs +++ b/codex-rs/execpolicy2/src/main.rs @@ -1,66 +1,24 @@ -use std::fs; -use std::path::PathBuf; - -use anyhow::Context; use anyhow::Result; use clap::Parser; -use codex_execpolicy2::PolicyParser; +use codex_execpolicy2::ExecPolicyCheckCommand; /// CLI for evaluating exec policies #[derive(Parser)] #[command(name = "codex-execpolicy2")] enum Cli { /// Evaluate a command against a policy. - Check { - #[arg(short, long = "policy", value_name = "PATH", required = true)] - policies: Vec, - - /// Pretty-print the JSON output. - #[arg(long)] - pretty: bool, - - /// Command tokens to check. - #[arg( - value_name = "COMMAND", - required = true, - trailing_var_arg = true, - allow_hyphen_values = true - )] - command: Vec, - }, + Check(ExecPolicyCheckCommand), } fn main() -> Result<()> { let cli = Cli::parse(); match cli { - Cli::Check { - policies, - command, - pretty, - } => cmd_check(policies, command, pretty), + Cli::Check(cmd) => cmd_check(cmd), } } -fn cmd_check(policy_paths: Vec, args: Vec, pretty: bool) -> Result<()> { - let policy = load_policies(&policy_paths)?; - - let eval = policy.check(&args); - let json = if pretty { - serde_json::to_string_pretty(&eval)? - } else { - serde_json::to_string(&eval)? - }; +fn cmd_check(cmd: ExecPolicyCheckCommand) -> Result<()> { + let json = cmd.to_json()?; println!("{json}"); Ok(()) } - -fn load_policies(policy_paths: &[PathBuf]) -> Result { - let mut parser = PolicyParser::new(); - for policy_path in policy_paths { - let policy_file_contents = fs::read_to_string(policy_path) - .with_context(|| format!("failed to read policy at {}", policy_path.display()))?; - let policy_identifier = policy_path.to_string_lossy().to_string(); - parser.parse(&policy_identifier, &policy_file_contents)?; - } - Ok(parser.build()) -}