diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index b7388a5a8..549c2817a 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -608,6 +608,10 @@ "arrayvec_0.7.6": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bencher\",\"req\":\"^0.1.4\"},{\"default_features\":false,\"name\":\"borsh\",\"optional\":true,\"req\":\"^1.2.0\"},{\"kind\":\"dev\",\"name\":\"matches\",\"req\":\"^0.1\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"zeroize\",\"optional\":true,\"req\":\"^1.4\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", "ascii-canvas_3.0.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"diff\",\"req\":\"^0.1\"},{\"name\":\"term\",\"req\":\"^0.7\"}],\"features\":{}}", "ascii_1.1.0": "{\"dependencies\":[{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.25\"},{\"name\":\"serde_test\",\"optional\":true,\"req\":\"^1.0\"}],\"features\":{\"alloc\":[],\"default\":[\"std\"],\"std\":[\"alloc\"]}}", + "askama_0.15.4": "{\"dependencies\":[{\"default_features\":false,\"name\":\"askama_macros\",\"optional\":true,\"req\":\"=0.15.4\"},{\"kind\":\"dev\",\"name\":\"assert_matches\",\"req\":\"^1.5.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.8\"},{\"name\":\"itoa\",\"req\":\"^1.0.11\"},{\"default_features\":false,\"name\":\"percent-encoding\",\"optional\":true,\"req\":\"^2.1.0\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"serde_json\",\"optional\":true,\"req\":\"^1.0\"}],\"features\":{\"alloc\":[\"askama_macros?/alloc\",\"serde?/alloc\",\"serde_json?/alloc\",\"percent-encoding?/alloc\"],\"code-in-doc\":[\"askama_macros?/code-in-doc\"],\"config\":[\"askama_macros?/config\"],\"default\":[\"config\",\"derive\",\"std\",\"urlencode\"],\"derive\":[\"dep:askama_macros\",\"dep:askama_macros\"],\"full\":[\"default\",\"code-in-doc\",\"serde_json\"],\"nightly-spans\":[\"askama_macros/nightly-spans\"],\"serde_json\":[\"std\",\"askama_macros?/serde_json\",\"dep:serde\",\"dep:serde_json\"],\"std\":[\"alloc\",\"askama_macros?/std\",\"serde?/std\",\"serde_json?/std\",\"percent-encoding?/std\"],\"urlencode\":[\"askama_macros?/urlencode\",\"dep:percent-encoding\"]}}", + "askama_derive_0.15.4": "{\"dependencies\":[{\"name\":\"basic-toml\",\"optional\":true,\"req\":\"^0.1.1\"},{\"kind\":\"dev\",\"name\":\"console\",\"req\":\"^0.16.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.8\"},{\"name\":\"memchr\",\"req\":\"^2\"},{\"name\":\"parser\",\"package\":\"askama_parser\",\"req\":\"=0.15.4\"},{\"kind\":\"dev\",\"name\":\"prettyplease\",\"req\":\"^0.2.20\"},{\"default_features\":false,\"name\":\"proc-macro2\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"pulldown-cmark\",\"optional\":true,\"req\":\"^0.13.0\"},{\"default_features\":false,\"name\":\"quote\",\"req\":\"^1\"},{\"name\":\"rustc-hash\",\"req\":\"^2.0.0\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"serde_derive\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"similar\",\"req\":\"^2.6.0\"},{\"default_features\":false,\"features\":[\"clone-impls\",\"derive\",\"full\",\"parsing\",\"printing\"],\"name\":\"syn\",\"req\":\"^2.0.3\"}],\"features\":{\"alloc\":[],\"code-in-doc\":[\"dep:pulldown-cmark\"],\"config\":[\"external-sources\",\"dep:basic-toml\",\"dep:serde\",\"dep:serde_derive\",\"parser/config\"],\"default\":[\"alloc\",\"code-in-doc\",\"config\",\"external-sources\",\"proc-macro\",\"serde_json\",\"std\",\"urlencode\"],\"external-sources\":[],\"nightly-spans\":[],\"proc-macro\":[\"proc-macro2/proc-macro\"],\"serde_json\":[],\"std\":[\"alloc\"],\"urlencode\":[]}}", + "askama_macros_0.15.4": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"external-sources\",\"proc-macro\"],\"name\":\"askama_derive\",\"package\":\"askama_derive\",\"req\":\"=0.15.4\"}],\"features\":{\"alloc\":[\"askama_derive/alloc\"],\"code-in-doc\":[\"askama_derive/code-in-doc\"],\"config\":[\"askama_derive/config\"],\"default\":[\"config\",\"derive\",\"std\",\"urlencode\"],\"derive\":[],\"full\":[\"default\",\"code-in-doc\",\"serde_json\"],\"nightly-spans\":[\"askama_derive/nightly-spans\"],\"serde_json\":[\"askama_derive/serde_json\"],\"std\":[\"askama_derive/std\"],\"urlencode\":[\"askama_derive/urlencode\"]}}", + "askama_parser_0.15.4": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.8\"},{\"name\":\"rustc-hash\",\"req\":\"^2.0.0\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"serde_derive\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"unicode-ident\",\"req\":\"^1.0.12\"},{\"features\":[\"simd\"],\"name\":\"winnow\",\"req\":\"^0.7.0\"}],\"features\":{\"config\":[\"dep:serde\",\"dep:serde_derive\"]}}", "asn1-rs-derive_0.6.0": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0\"},{\"features\":[\"full\"],\"name\":\"syn\",\"req\":\"^2.0\"},{\"name\":\"synstructure\",\"req\":\"^0.13\"}],\"features\":{}}", "asn1-rs-impl_0.2.0": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1\"},{\"name\":\"quote\",\"req\":\"^1\"},{\"name\":\"syn\",\"req\":\"^2.0\"}],\"features\":{}}", "asn1-rs_0.7.1": "{\"dependencies\":[{\"name\":\"asn1-rs-derive\",\"req\":\"^0.6\"},{\"name\":\"asn1-rs-impl\",\"req\":\"^0.2\"},{\"name\":\"bitvec\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"colored\",\"optional\":true,\"req\":\"^3.0\"},{\"kind\":\"dev\",\"name\":\"colored\",\"req\":\"^3.0\"},{\"name\":\"cookie-factory\",\"optional\":true,\"req\":\"^0.3.0\"},{\"name\":\"displaydoc\",\"req\":\"^0.2.2\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^0.4\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"nom\",\"req\":\"^7.0\"},{\"name\":\"num-bigint\",\"optional\":true,\"req\":\"^0.4\"},{\"name\":\"num-traits\",\"req\":\"^0.2.14\"},{\"kind\":\"dev\",\"name\":\"pem\",\"req\":\"^3.0\"},{\"name\":\"rusticata-macros\",\"req\":\"^4.0\"},{\"name\":\"thiserror\",\"req\":\"^2.0.0\"},{\"features\":[\"macros\",\"parsing\",\"formatting\"],\"name\":\"time\",\"optional\":true,\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0\"}],\"features\":{\"bigint\":[\"num-bigint\"],\"bits\":[\"bitvec\"],\"datetime\":[\"time\"],\"debug\":[\"std\",\"colored\"],\"default\":[\"std\"],\"serialize\":[\"cookie-factory\"],\"std\":[],\"trace\":[\"debug\"]}}", diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 92716cc41..a6a5e7710 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -1623,6 +1623,26 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "codex-command" +version = "0.0.0" +dependencies = [ + "anyhow", + "base64 0.22.1", + "codex-protocol", + "codex-utils-absolute-path", + "once_cell", + "pretty_assertions", + "regex", + "serde", + "serde_json", + "shlex", + "tree-sitter", + "tree-sitter-bash", + "url", + "which", +] + [[package]] name = "codex-common" version = "0.0.0" @@ -1660,6 +1680,7 @@ dependencies = [ "codex-arg0", "codex-async-utils", "codex-client", + "codex-command", "codex-core", "codex-execpolicy", "codex-file-search", @@ -1733,8 +1754,6 @@ dependencies = [ "tracing", "tracing-subscriber", "tracing-test", - "tree-sitter", - "tree-sitter-bash", "url", "uuid", "walkdir", diff --git a/codex-rs/Cargo.toml b/codex-rs/Cargo.toml index eebc652d4..101023f4a 100644 --- a/codex-rs/Cargo.toml +++ b/codex-rs/Cargo.toml @@ -16,6 +16,7 @@ members = [ "cloud-tasks-client", "cli", "common", + "command", "core", "hooks", "secrets", @@ -82,6 +83,7 @@ codex-chatgpt = { path = "chatgpt" } codex-cli = { path = "cli"} codex-client = { path = "codex-client" } codex-common = { path = "common" } +codex-command = { path = "command" } codex-core = { path = "core" } codex-hooks = { path = "hooks" } codex-secrets = { path = "secrets" } diff --git a/codex-rs/command/BUILD.bazel b/codex-rs/command/BUILD.bazel new file mode 100644 index 000000000..d1d0fd34a --- /dev/null +++ b/codex-rs/command/BUILD.bazel @@ -0,0 +1,7 @@ +load("//:defs.bzl", "codex_rust_crate") + +codex_rust_crate( + name = "command", + crate_name = "codex_command", + compile_data = ["src/command_safety/powershell_parser.ps1"], +) diff --git a/codex-rs/command/Cargo.toml b/codex-rs/command/Cargo.toml new file mode 100644 index 000000000..61e386869 --- /dev/null +++ b/codex-rs/command/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "codex-command" +version.workspace = true +edition.workspace = true +license.workspace = true + +[lints] +workspace = true + +[dependencies] +base64 = { workspace = true } +codex-protocol = { workspace = true } +codex-utils-absolute-path = { workspace = true } +once_cell = { workspace = true } +regex = { workspace = true } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +shlex = { workspace = true } +tree-sitter = { workspace = true } +tree-sitter-bash = { workspace = true } +url = { workspace = true } +which = { workspace = true } + +[dev-dependencies] +anyhow = { workspace = true } +pretty_assertions = { workspace = true } diff --git a/codex-rs/core/src/bash.rs b/codex-rs/command/src/bash.rs similarity index 99% rename from codex-rs/core/src/bash.rs rename to codex-rs/command/src/bash.rs index fcb8f9ae1..a8eb37a92 100644 --- a/codex-rs/core/src/bash.rs +++ b/codex-rs/command/src/bash.rs @@ -5,8 +5,8 @@ use tree_sitter::Parser; use tree_sitter::Tree; use tree_sitter_bash::LANGUAGE as BASH; -use crate::shell::ShellType; -use crate::shell::detect_shell_type; +use crate::shell_detect::ShellType; +use crate::shell_detect::detect_shell_type; /// Parse the provided bash source using tree-sitter-bash, returning a Tree on /// success or None if parsing failed. diff --git a/codex-rs/core/src/command_safety/is_dangerous_command.rs b/codex-rs/command/src/command_safety/is_dangerous_command.rs similarity index 100% rename from codex-rs/core/src/command_safety/is_dangerous_command.rs rename to codex-rs/command/src/command_safety/is_dangerous_command.rs diff --git a/codex-rs/core/src/command_safety/is_safe_command.rs b/codex-rs/command/src/command_safety/is_safe_command.rs similarity index 100% rename from codex-rs/core/src/command_safety/is_safe_command.rs rename to codex-rs/command/src/command_safety/is_safe_command.rs diff --git a/codex-rs/core/src/command_safety/mod.rs b/codex-rs/command/src/command_safety/mod.rs similarity index 100% rename from codex-rs/core/src/command_safety/mod.rs rename to codex-rs/command/src/command_safety/mod.rs diff --git a/codex-rs/core/src/command_safety/powershell_parser.ps1 b/codex-rs/command/src/command_safety/powershell_parser.ps1 similarity index 100% rename from codex-rs/core/src/command_safety/powershell_parser.ps1 rename to codex-rs/command/src/command_safety/powershell_parser.ps1 diff --git a/codex-rs/core/src/command_safety/windows_dangerous_commands.rs b/codex-rs/command/src/command_safety/windows_dangerous_commands.rs similarity index 100% rename from codex-rs/core/src/command_safety/windows_dangerous_commands.rs rename to codex-rs/command/src/command_safety/windows_dangerous_commands.rs diff --git a/codex-rs/core/src/command_safety/windows_safe_commands.rs b/codex-rs/command/src/command_safety/windows_safe_commands.rs similarity index 100% rename from codex-rs/core/src/command_safety/windows_safe_commands.rs rename to codex-rs/command/src/command_safety/windows_safe_commands.rs diff --git a/codex-rs/command/src/lib.rs b/codex-rs/command/src/lib.rs new file mode 100644 index 000000000..215c30fd7 --- /dev/null +++ b/codex-rs/command/src/lib.rs @@ -0,0 +1,11 @@ +//! Command parsing and safety utilities shared across Codex crates. + +mod shell_detect; + +pub mod bash; +pub mod command_safety; +pub mod parse_command; +pub mod powershell; + +pub use command_safety::is_dangerous_command; +pub use command_safety::is_safe_command; diff --git a/codex-rs/core/src/parse_command.rs b/codex-rs/command/src/parse_command.rs similarity index 100% rename from codex-rs/core/src/parse_command.rs rename to codex-rs/command/src/parse_command.rs diff --git a/codex-rs/core/src/powershell.rs b/codex-rs/command/src/powershell.rs similarity index 92% rename from codex-rs/core/src/powershell.rs rename to codex-rs/command/src/powershell.rs index 148605b4b..4d3b55333 100644 --- a/codex-rs/core/src/powershell.rs +++ b/codex-rs/command/src/powershell.rs @@ -1,18 +1,16 @@ use std::path::PathBuf; -#[cfg(any(windows, test))] use codex_utils_absolute_path::AbsolutePathBuf; -use crate::shell::ShellType; -use crate::shell::detect_shell_type; +use crate::shell_detect::ShellType; +use crate::shell_detect::detect_shell_type; const POWERSHELL_FLAGS: &[&str] = &["-nologo", "-noprofile", "-command", "-c"]; /// Prefixed command for powershell shell calls to force UTF-8 console output. -pub(crate) const UTF8_OUTPUT_PREFIX: &str = - "[Console]::OutputEncoding=[System.Text.Encoding]::UTF8;\n"; +pub const UTF8_OUTPUT_PREFIX: &str = "[Console]::OutputEncoding=[System.Text.Encoding]::UTF8;\n"; -pub(crate) fn prefix_powershell_script_with_utf8(command: &[String]) -> Vec { +pub fn prefix_powershell_script_with_utf8(command: &[String]) -> Vec { let Some((_, script)) = extract_powershell_command(command) else { return command.to_vec(); }; @@ -84,8 +82,7 @@ pub(crate) fn try_find_powershellish_executable_blocking() -> Option Option { +pub fn try_find_powershell_executable_blocking() -> Option { try_find_powershellish_executable_in_path(&["powershell.exe"]) } @@ -99,8 +96,7 @@ pub(crate) fn try_find_powershell_executable_blocking() -> Option Option { +pub fn try_find_pwsh_executable_blocking() -> Option { if let Some(ps_home) = std::process::Command::new("cmd") .args(["/C", "pwsh", "-NoProfile", "-Command", "$PSHOME"]) .output() @@ -126,7 +122,6 @@ pub(crate) fn try_find_pwsh_executable_blocking() -> Option { try_find_powershellish_executable_in_path(&["pwsh.exe"]) } -#[cfg(any(windows, test))] fn try_find_powershellish_executable_in_path(candidates: &[&str]) -> Option { for candidate in candidates { let Ok(resolved_path) = which::which(candidate) else { @@ -147,7 +142,6 @@ fn try_find_powershellish_executable_in_path(candidates: &[&str]) -> Option bool { // This test works for both powershell.exe and pwsh.exe. std::process::Command::new(powershell_or_pwsh_exe) diff --git a/codex-rs/command/src/shell_detect.rs b/codex-rs/command/src/shell_detect.rs new file mode 100644 index 000000000..34322a06d --- /dev/null +++ b/codex-rs/command/src/shell_detect.rs @@ -0,0 +1,32 @@ +use std::path::Path; +use std::path::PathBuf; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub(crate) enum ShellType { + Zsh, + Bash, + PowerShell, + Sh, + Cmd, +} + +pub(crate) fn detect_shell_type(shell_path: &PathBuf) -> Option { + match shell_path.as_os_str().to_str() { + Some("zsh") => Some(ShellType::Zsh), + Some("sh") => Some(ShellType::Sh), + Some("cmd") => Some(ShellType::Cmd), + Some("bash") => Some(ShellType::Bash), + Some("pwsh") => Some(ShellType::PowerShell), + Some("powershell") => Some(ShellType::PowerShell), + _ => { + let shell_name = shell_path.file_stem(); + if let Some(shell_name) = shell_name { + let shell_name_path = Path::new(shell_name); + if shell_name_path != Path::new(shell_path) { + return detect_shell_type(&shell_name_path.to_path_buf()); + } + } + None + } + } +} diff --git a/codex-rs/core/Cargo.toml b/codex-rs/core/Cargo.toml index 4142a72d7..2a86148c4 100644 --- a/codex-rs/core/Cargo.toml +++ b/codex-rs/core/Cargo.toml @@ -33,6 +33,7 @@ codex-app-server-protocol = { workspace = true } codex-apply-patch = { workspace = true } codex-async-utils = { workspace = true } codex-client = { workspace = true } +codex-command = { workspace = true } codex-execpolicy = { workspace = true } codex-file-search = { workspace = true } codex-git = { workspace = true } @@ -106,8 +107,6 @@ tokio-tungstenite = { workspace = true } toml = { workspace = true } toml_edit = { workspace = true } tracing = { workspace = true, features = ["log"] } -tree-sitter = { workspace = true } -tree-sitter-bash = { workspace = true } url = { workspace = true } uuid = { workspace = true, features = ["serde", "v4", "v5"] } which = { workspace = true } diff --git a/codex-rs/core/src/lib.rs b/codex-rs/core/src/lib.rs index 1500a7615..8ee229c02 100644 --- a/codex-rs/core/src/lib.rs +++ b/codex-rs/core/src/lib.rs @@ -10,7 +10,6 @@ pub mod api_bridge; mod apply_patch; mod apps; pub mod auth; -pub mod bash; mod client; mod client_common; pub mod codex; @@ -22,7 +21,6 @@ pub use codex_thread::ThreadConfigSnapshot; mod agent; mod codex_delegate; mod command_canonicalization; -mod command_safety; pub mod config; pub mod config_loader; pub mod connectors; @@ -52,10 +50,8 @@ mod memories; mod mentions; mod message_history; mod model_provider_info; -pub mod parse_command; pub mod path_utils; pub mod personality_migration; -pub mod powershell; mod proposed_plan_parser; mod sandbox_tags; pub mod sandboxing; @@ -133,11 +129,14 @@ mod state; mod tasks; mod user_shell_command; pub mod util; +pub use codex_command::bash; +pub use codex_command::is_dangerous_command; +pub use codex_command::is_safe_command; +pub use codex_command::parse_command; +pub use codex_command::powershell; pub use apply_patch::CODEX_APPLY_PATCH_ARG1; pub use client::X_CODEX_TURN_METADATA_HEADER; -pub use command_safety::is_dangerous_command; -pub use command_safety::is_safe_command; pub use exec_policy::ExecPolicyError; pub use exec_policy::check_execpolicy_for_warnings; pub use exec_policy::load_exec_policy;