commonizing cli logic

This commit is contained in:
kevin zhao
2025-11-19 20:30:20 +00:00
parent 745e2a6790
commit 9ec2873084
4 changed files with 73 additions and 94 deletions

View File

@@ -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<PathBuf>,
/// 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<String>,
}
fn format_exit_messages(exit_info: AppExitInfo, color_enabled: bool) -> Vec<String> {
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<codex_execpolicy2::Policy> {
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.<name>=true`.

View File

@@ -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<PathBuf>,
/// 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<String>,
}
impl ExecPolicyCheckCommand {
/// Load the policies for this command, evaluate the command, and render JSON output.
pub fn to_json(&self) -> Result<String> {
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<String> {
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<Policy> {
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())
}

View File

@@ -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;

View File

@@ -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<PathBuf>,
/// 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<String>,
},
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<PathBuf>, args: Vec<String>, 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<codex_execpolicy2::Policy> {
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())
}