Skip to content

Commit 4cf82d9

Browse files
CSResselnori-agent
andcommitted
feat(cli): Add feature flags for modular CLI builds
Implement Cargo feature flags to make the CLI binary modular: - default = [] (minimal: TUI + exec + ACP only) - full = all features for complete functionality - Individual features: app-server, cloud-tasks, login, mcp-server, chatgpt, responses-api-proxy This reduces release binary size from 46MB to 36MB (22% reduction) for minimal builds by excluding optional HTTP-based functionality. Key changes: - Made 6 dependencies optional with dep: prefix - Added #[cfg(feature = "...")] guards to main.rs imports, enum variants, and match arms - Added required-features to MCP integration tests - Updated docs.md with feature flag documentation 🤖 Generated with [Nori](https://nori.ai) Co-Authored-By: Nori <contact@tilework.tech>
1 parent c6c608e commit 4cf82d9

6 files changed

Lines changed: 128 additions & 32 deletions

File tree

codex-rs/cli/Cargo.toml

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,57 @@ path = "src/lib.rs"
1515
[lints]
1616
workspace = true
1717

18+
[features]
19+
default = []
20+
21+
# Full feature set - enables all legacy functionality
22+
full = [
23+
"app-server",
24+
"cloud-tasks",
25+
"login",
26+
"mcp-server",
27+
"chatgpt",
28+
"responses-api-proxy",
29+
]
30+
31+
# App server functionality
32+
app-server = ["dep:codex-app-server"]
33+
34+
# Cloud tasks command
35+
cloud-tasks = ["dep:codex-cloud-tasks"]
36+
37+
# Login/logout commands
38+
login = ["dep:codex-login"]
39+
40+
# MCP server functionality
41+
mcp-server = ["dep:codex-mcp-server", "dep:codex-rmcp-client"]
42+
43+
# ChatGPT/apply command
44+
chatgpt = ["dep:codex-chatgpt"]
45+
46+
# Responses API proxy
47+
responses-api-proxy = ["dep:codex-responses-api-proxy"]
48+
1849
[dependencies]
1950
anyhow = { workspace = true }
2051
clap = { workspace = true, features = ["derive"] }
2152
clap_complete = { workspace = true }
2253
codex-acp = { workspace = true }
23-
codex-app-server = { workspace = true }
54+
codex-app-server = { workspace = true, optional = true }
2455
codex-app-server-protocol = { workspace = true }
2556
codex-arg0 = { workspace = true }
26-
codex-chatgpt = { workspace = true }
27-
codex-cloud-tasks = { path = "../cloud-tasks" }
57+
codex-chatgpt = { workspace = true, optional = true }
58+
codex-cloud-tasks = { path = "../cloud-tasks", optional = true }
2859
codex-common = { workspace = true, features = ["cli"] }
2960
codex-core = { workspace = true }
3061
codex-exec = { workspace = true }
3162
codex-execpolicy = { workspace = true }
32-
codex-login = { workspace = true }
33-
codex-mcp-server = { workspace = true }
63+
codex-login = { workspace = true, optional = true }
64+
codex-mcp-server = { workspace = true, optional = true }
3465
codex-process-hardening = { workspace = true }
3566
codex-protocol = { workspace = true }
36-
codex-responses-api-proxy = { workspace = true }
37-
codex-rmcp-client = { workspace = true }
67+
codex-responses-api-proxy = { workspace = true, optional = true }
68+
codex-rmcp-client = { workspace = true, optional = true }
3869
codex-stdio-to-uds = { workspace = true }
3970
codex-tui = { workspace = true }
4071
ctor = { workspace = true }
@@ -62,3 +93,12 @@ assert_matches = { workspace = true }
6293
predicates = { workspace = true }
6394
pretty_assertions = { workspace = true }
6495
tempfile = { workspace = true }
96+
97+
# Integration tests that require the mcp-server feature
98+
[[test]]
99+
name = "mcp_add_remove"
100+
required-features = ["mcp-server"]
101+
102+
[[test]]
103+
name = "mcp_list"
104+
required-features = ["mcp-server"]

codex-rs/cli/docs.md

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,8 @@ The `codex-cli` crate is the main multitool binary that provides the `codex` com
1010

1111
This crate is the primary entry point that ties together all other crates:
1212

13-
- **Dispatches to** `codex-tui` for interactive mode (default, no subcommand)
14-
- **Dispatches to** `codex-exec` for `codex exec` non-interactive execution
15-
- **Dispatches to** `codex-mcp-server` for `codex mcp-server`
16-
- **Dispatches to** `codex-app-server` for `codex app-server`
17-
- **Dispatches to** `codex-cloud-tasks` for `codex cloud` browsing
18-
- **Uses** `codex-login` for authentication flows
19-
- **Uses** `codex-chatgpt` for the `codex apply` command
13+
- **Always included:** `codex-tui`, `codex-exec`, `codex-acp`, `codex-core` (minimal build)
14+
- **Optional via features:** `codex-mcp-server`, `codex-app-server`, `codex-cloud-tasks`, `codex-login`, `codex-chatgpt`, `codex-responses-api-proxy`
2015
- **Uses** `codex-arg0` for arg0-based dispatch (Linux sandbox embedding)
2116

2217
### Core Implementation
@@ -38,20 +33,20 @@ match subcommand {
3833

3934
**Subcommands:**
4035

41-
| Subcommand | Alias | Description |
42-
|------------|-------|-------------|
43-
| `exec` | `e` | Run Codex non-interactively |
44-
| `login` | | Manage authentication |
45-
| `logout` | | Remove stored credentials |
46-
| `mcp` | | Manage MCP server configurations |
47-
| `mcp-server` | | Run as MCP server (stdio) |
48-
| `app-server` | | Run app server (JSON-RPC stdio) |
49-
| `resume` | | Resume previous session |
50-
| `apply` | `a` | Apply latest Codex diff to working tree |
51-
| `sandbox` | `debug` | Test sandbox enforcement |
52-
| `cloud` | | Browse Codex Cloud tasks |
53-
| `completion` | | Generate shell completions |
54-
| `features` | | List feature flags |
36+
| Subcommand | Alias | Description | Required Feature |
37+
|------------|-------|-------------|------------------|
38+
| `exec` | `e` | Run Codex non-interactively | (always) |
39+
| `login` | | Manage authentication | `login` |
40+
| `logout` | | Remove stored credentials | `login` |
41+
| `mcp` | | Manage MCP server configurations | `mcp-server` |
42+
| `mcp-server` | | Run as MCP server (stdio) | `mcp-server` |
43+
| `app-server` | | Run app server (JSON-RPC stdio) | `app-server` |
44+
| `resume` | | Resume previous session | (always) |
45+
| `apply` | `a` | Apply latest Codex diff to working tree | `chatgpt` |
46+
| `sandbox` | `debug` | Test sandbox enforcement | (always) |
47+
| `cloud` | | Browse Codex Cloud tasks | `cloud-tasks` |
48+
| `completion` | | Generate shell completions | (always) |
49+
| `features` | | List feature flags | (always) |
5550

5651
**Feature Toggles:**
5752

@@ -71,6 +66,29 @@ These translate to `-c features.<name>=true/false` config overrides.
7166

7267
### Things to Know
7368

69+
**Cargo Feature Flags (Compile-time):**
70+
71+
The CLI uses Cargo features to enable optional functionality. By default (`default = []`), only core functionality is included (TUI, exec, ACP). Optional features can be enabled individually or via the `full` meta-feature:
72+
73+
| Feature | Dependencies | Enables |
74+
|---------|--------------|---------|
75+
| `full` | All features | Complete legacy binary |
76+
| `app-server` | `codex-app-server` | `app-server` subcommand |
77+
| `cloud-tasks` | `codex-cloud-tasks` | `cloud` subcommand |
78+
| `login` | `codex-login` | `login`/`logout` subcommands |
79+
| `mcp-server` | `codex-mcp-server`, `codex-rmcp-client` | `mcp`, `mcp-server` subcommands |
80+
| `chatgpt` | `codex-chatgpt` | `apply` subcommand |
81+
| `responses-api-proxy` | `codex-responses-api-proxy` | `responses-api-proxy` subcommand |
82+
83+
Build examples:
84+
```bash
85+
cargo build -p codex-cli # Minimal (TUI + exec + ACP only)
86+
cargo build -p codex-cli --features full # All functionality
87+
cargo build -p codex-cli --features login,mcp-server # Selective
88+
```
89+
90+
Feature-gated code uses `#[cfg(feature = "...")]` on imports, enum variants, match arms, and struct definitions in `main.rs`. Integration tests that require specific features use `required-features` in `Cargo.toml` (e.g., MCP tests require `mcp-server`).
91+
7492
**Sandbox Debugging:**
7593

7694
The `debug_sandbox` module (in `debug_sandbox/`) provides:

codex-rs/cli/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
pub mod debug_sandbox;
22
mod exit_status;
3+
#[cfg(feature = "login")]
34
pub mod login;
45

56
use clap::Parser;

codex-rs/cli/src/main.rs

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,34 @@
1-
use clap::Args;
21
use clap::CommandFactory;
32
use clap::Parser;
43
use clap_complete::Shell;
54
use clap_complete::generate;
65
use codex_acp::init_file_tracing;
76
use codex_arg0::arg0_dispatch_or_else;
7+
#[cfg(feature = "chatgpt")]
88
use codex_chatgpt::apply_command::ApplyCommand;
9+
#[cfg(feature = "chatgpt")]
910
use codex_chatgpt::apply_command::run_apply_command;
1011
use codex_cli::LandlockCommand;
1112
use codex_cli::SeatbeltCommand;
1213
use codex_cli::WindowsCommand;
14+
#[cfg(feature = "login")]
1315
use codex_cli::login::read_api_key_from_stdin;
16+
#[cfg(feature = "login")]
1417
use codex_cli::login::run_login_status;
18+
#[cfg(feature = "login")]
1519
use codex_cli::login::run_login_with_api_key;
20+
#[cfg(feature = "login")]
1621
use codex_cli::login::run_login_with_chatgpt;
22+
#[cfg(feature = "login")]
1723
use codex_cli::login::run_login_with_device_code;
24+
#[cfg(feature = "login")]
1825
use codex_cli::login::run_logout;
26+
#[cfg(feature = "cloud-tasks")]
1927
use codex_cloud_tasks::Cli as CloudTasksCli;
2028
use codex_common::CliConfigOverrides;
2129
use codex_exec::Cli as ExecCli;
2230
use codex_execpolicy::ExecPolicyCheckCommand;
31+
#[cfg(feature = "responses-api-proxy")]
2332
use codex_responses_api_proxy::Args as ResponsesApiProxyArgs;
2433
use codex_tui::AppExitInfo;
2534
use codex_tui::Cli as TuiCli;
@@ -28,10 +37,12 @@ use owo_colors::OwoColorize;
2837
use std::path::PathBuf;
2938
use supports_color::Stream;
3039

40+
#[cfg(feature = "mcp-server")]
3141
mod mcp_cmd;
3242
#[cfg(not(windows))]
3343
mod wsl_paths;
3444

45+
#[cfg(feature = "mcp-server")]
3546
use crate::mcp_cmd::McpCli;
3647

3748
use codex_core::config::Config;
@@ -74,18 +85,23 @@ enum Subcommand {
7485
Exec(ExecCli),
7586

7687
/// Manage login.
88+
#[cfg(feature = "login")]
7789
Login(LoginCommand),
7890

7991
/// Remove stored authentication credentials.
92+
#[cfg(feature = "login")]
8093
Logout(LogoutCommand),
8194

8295
/// [experimental] Run Codex as an MCP server and manage MCP servers.
96+
#[cfg(feature = "mcp-server")]
8397
Mcp(McpCli),
8498

8599
/// [experimental] Run the Codex MCP server (stdio transport).
100+
#[cfg(feature = "mcp-server")]
86101
McpServer,
87102

88103
/// [experimental] Run the app server or related tooling.
104+
#[cfg(feature = "app-server")]
89105
AppServer(AppServerCommand),
90106

91107
/// Generate shell completion scripts.
@@ -100,17 +116,20 @@ enum Subcommand {
100116
Execpolicy(ExecpolicyCommand),
101117

102118
/// Apply the latest diff produced by Codex agent as a `git apply` to your local working tree.
119+
#[cfg(feature = "chatgpt")]
103120
#[clap(visible_alias = "a")]
104121
Apply(ApplyCommand),
105122

106123
/// Resume a previous interactive session (picker by default; use --last to continue the most recent).
107124
Resume(ResumeCommand),
108125

109126
/// [EXPERIMENTAL] Browse tasks from Codex Cloud and apply changes locally.
127+
#[cfg(feature = "cloud-tasks")]
110128
#[clap(name = "cloud", alias = "cloud-tasks")]
111129
Cloud(CloudTasksCli),
112130

113131
/// Internal: run the responses API proxy.
132+
#[cfg(feature = "responses-api-proxy")]
114133
#[clap(hide = true)]
115134
ResponsesApiProxy(ResponsesApiProxyArgs),
116135

@@ -181,6 +200,7 @@ enum ExecpolicySubcommand {
181200
Check(ExecPolicyCheckCommand),
182201
}
183202

203+
#[cfg(feature = "login")]
184204
#[derive(Debug, Parser)]
185205
struct LoginCommand {
186206
#[clap(skip)]
@@ -216,25 +236,29 @@ struct LoginCommand {
216236
action: Option<LoginSubcommand>,
217237
}
218238

239+
#[cfg(feature = "login")]
219240
#[derive(Debug, clap::Subcommand)]
220241
enum LoginSubcommand {
221242
/// Show login status.
222243
Status,
223244
}
224245

246+
#[cfg(feature = "login")]
225247
#[derive(Debug, Parser)]
226248
struct LogoutCommand {
227249
#[clap(skip)]
228250
config_overrides: CliConfigOverrides,
229251
}
230252

253+
#[cfg(feature = "app-server")]
231254
#[derive(Debug, Parser)]
232255
struct AppServerCommand {
233256
/// Omit to run the app server; specify a subcommand for tooling.
234257
#[command(subcommand)]
235258
subcommand: Option<AppServerSubcommand>,
236259
}
237260

261+
#[cfg(feature = "app-server")]
238262
#[derive(Debug, clap::Subcommand)]
239263
enum AppServerSubcommand {
240264
/// [experimental] Generate TypeScript bindings for the app server protocol.
@@ -244,7 +268,8 @@ enum AppServerSubcommand {
244268
GenerateJsonSchema(GenerateJsonSchemaCommand),
245269
}
246270

247-
#[derive(Debug, Args)]
271+
#[cfg(feature = "app-server")]
272+
#[derive(Debug, clap::Args)]
248273
struct GenerateTsCommand {
249274
/// Output directory where .ts files will be written
250275
#[arg(short = 'o', long = "out", value_name = "DIR")]
@@ -255,7 +280,8 @@ struct GenerateTsCommand {
255280
prettier: Option<PathBuf>,
256281
}
257282

258-
#[derive(Debug, Args)]
283+
#[cfg(feature = "app-server")]
284+
#[derive(Debug, clap::Args)]
259285
struct GenerateJsonSchemaCommand {
260286
/// Output directory where the schema bundle will be written
261287
#[arg(short = 'o', long = "out", value_name = "DIR")]
@@ -458,14 +484,17 @@ async fn cli_main(codex_linux_sandbox_exe: Option<PathBuf>) -> anyhow::Result<()
458484
);
459485
codex_exec::run_main(exec_cli, codex_linux_sandbox_exe).await?;
460486
}
487+
#[cfg(feature = "mcp-server")]
461488
Some(Subcommand::McpServer) => {
462489
codex_mcp_server::run_main(codex_linux_sandbox_exe, root_config_overrides).await?;
463490
}
491+
#[cfg(feature = "mcp-server")]
464492
Some(Subcommand::Mcp(mut mcp_cli)) => {
465493
// Propagate any root-level config overrides (e.g. `-c key=value`).
466494
prepend_config_flags(&mut mcp_cli.config_overrides, root_config_overrides.clone());
467495
mcp_cli.run().await?;
468496
}
497+
#[cfg(feature = "app-server")]
469498
Some(Subcommand::AppServer(app_server_cli)) => match app_server_cli.subcommand {
470499
None => {
471500
codex_app_server::run_main(codex_linux_sandbox_exe, root_config_overrides).await?;
@@ -497,6 +526,7 @@ async fn cli_main(codex_linux_sandbox_exe: Option<PathBuf>) -> anyhow::Result<()
497526
let exit_info = codex_tui::run_main(interactive, codex_linux_sandbox_exe).await?;
498527
handle_app_exit(exit_info)?;
499528
}
529+
#[cfg(feature = "login")]
500530
Some(Subcommand::Login(mut login_cli)) => {
501531
prepend_config_flags(
502532
&mut login_cli.config_overrides,
@@ -528,6 +558,7 @@ async fn cli_main(codex_linux_sandbox_exe: Option<PathBuf>) -> anyhow::Result<()
528558
}
529559
}
530560
}
561+
#[cfg(feature = "login")]
531562
Some(Subcommand::Logout(mut logout_cli)) => {
532563
prepend_config_flags(
533564
&mut logout_cli.config_overrides,
@@ -538,6 +569,7 @@ async fn cli_main(codex_linux_sandbox_exe: Option<PathBuf>) -> anyhow::Result<()
538569
Some(Subcommand::Completion(completion_cli)) => {
539570
print_completion(completion_cli);
540571
}
572+
#[cfg(feature = "cloud-tasks")]
541573
Some(Subcommand::Cloud(mut cloud_cli)) => {
542574
prepend_config_flags(
543575
&mut cloud_cli.config_overrides,
@@ -583,13 +615,15 @@ async fn cli_main(codex_linux_sandbox_exe: Option<PathBuf>) -> anyhow::Result<()
583615
Some(Subcommand::Execpolicy(ExecpolicyCommand { sub })) => match sub {
584616
ExecpolicySubcommand::Check(cmd) => run_execpolicycheck(cmd)?,
585617
},
618+
#[cfg(feature = "chatgpt")]
586619
Some(Subcommand::Apply(mut apply_cli)) => {
587620
prepend_config_flags(
588621
&mut apply_cli.config_overrides,
589622
root_config_overrides.clone(),
590623
);
591624
run_apply_command(apply_cli, None).await?;
592625
}
626+
#[cfg(feature = "responses-api-proxy")]
593627
Some(Subcommand::ResponsesApiProxy(args)) => {
594628
tokio::task::spawn_blocking(move || codex_responses_api_proxy::run_main(args))
595629
.await??;

codex-rs/tui-pty-e2e/tests/snapshots/startup__startup_welcome_dimensions_40x120.snap

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
source: tui-pty-e2e/tests/startup.rs
33
expression: normalize_for_input_snapshot(contents)
44
---
5+
Operation 'ListCustomPrompts' is not supported in ACP mode
6+
7+
58
› [DEFAULT_PROMPT]
69

710
100% context left · ? for shortcuts

codex-rs/tui-pty-e2e/tests/snapshots/streaming__cancelled_stream.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,4 @@ went wrong? Hit `/feedback` to report the issue.
1818

1919
› [DEFAULT_PROMPT]
2020

21-
100% context left · ? for shortcuts
21+
ctrl + c again to quit

0 commit comments

Comments
 (0)