Skip to content

Commit d7b6dfd

Browse files
CSResselclaude
andcommitted
feat(tui): Complete feature flags implementation with Nori update system
- Add Nori-specific update modules (update_action.rs, updates.rs, update_prompt.rs) - Gate slash command visibility based on features (login, feedback) - Add CLI feature propagation to TUI crate - Update documentation for feature flags architecture - Remove unnecessary dead code workaround in updates.rs - Add documentation for get_update_action() design decision 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 9ee7eb1 commit d7b6dfd

10 files changed

Lines changed: 463 additions & 22 deletions

File tree

codex-rs/cli/Cargo.toml

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ full = [
2323
"app-server",
2424
"cloud-tasks",
2525
"login",
26+
"feedback",
27+
"backend-client",
28+
"upstream-updates",
2629
"mcp-server",
2730
"chatgpt",
2831
"responses-api-proxy",
@@ -34,8 +37,17 @@ app-server = ["dep:codex-app-server"]
3437
# Cloud tasks command
3538
cloud-tasks = ["dep:codex-cloud-tasks"]
3639

37-
# Login/logout commands
38-
login = ["dep:codex-login"]
40+
# Login/logout commands - propagate to TUI
41+
login = ["dep:codex-login", "codex-tui/login"]
42+
43+
# Feedback feature - propagate to TUI
44+
feedback = ["codex-tui/feedback"]
45+
46+
# Backend client feature - propagate to TUI
47+
backend-client = ["codex-tui/backend-client"]
48+
49+
# Upstream updates feature - propagate to TUI
50+
upstream-updates = ["codex-tui/upstream-updates"]
3951

4052
# MCP server functionality
4153
mcp-server = ["dep:codex-mcp-server", "dep:codex-rmcp-client"]

codex-rs/cli/docs.md

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,15 +75,28 @@ The CLI uses Cargo features to enable optional functionality. By default (`defau
7575
| `full` | All features | Complete legacy binary |
7676
| `app-server` | `codex-app-server` | `app-server` subcommand |
7777
| `cloud-tasks` | `codex-cloud-tasks` | `cloud` subcommand |
78-
| `login` | `codex-login` | `login`/`logout` subcommands |
78+
| `login` | `codex-login`, `codex-tui/login` | `login`/`logout` subcommands + TUI login |
79+
| `feedback` | `codex-tui/feedback` | Sentry feedback in TUI |
80+
| `backend-client` | `codex-tui/backend-client` | Cloud tasks backend client |
81+
| `upstream-updates` | `codex-tui/upstream-updates` | OpenAI update mechanism (vs Nori's) |
7982
| `mcp-server` | `codex-mcp-server`, `codex-rmcp-client` | `mcp`, `mcp-server` subcommands |
8083
| `chatgpt` | `codex-chatgpt` | `apply` subcommand |
8184
| `responses-api-proxy` | `codex-responses-api-proxy` | `responses-api-proxy` subcommand |
8285

86+
**Feature Propagation to TUI:**
87+
88+
Several CLI features propagate to the TUI crate for coordinated behavior:
89+
- `login` -> `codex-tui/login`: Enables login screens and `/login` command in TUI
90+
- `feedback` -> `codex-tui/feedback`: Enables Sentry feedback and `/feedback` command
91+
- `backend-client` -> `codex-tui/backend-client`: Enables cloud tasks backend
92+
- `upstream-updates` -> `codex-tui/upstream-updates`: Uses OpenAI update system instead of Nori's
93+
94+
Without these features, the TUI uses Nori-specific alternatives (e.g., GitHub Discussions for feedback, GitHub releases for updates).
95+
8396
Build examples:
8497
```bash
85-
cargo build -p codex-cli # Minimal (TUI + exec + ACP only)
86-
cargo build -p codex-cli --features full # All functionality
98+
cargo build -p codex-cli # Minimal (TUI + exec + ACP only, Nori updates)
99+
cargo build -p codex-cli --features full # All functionality (OpenAI-compatible)
87100
cargo build -p codex-cli --features login,mcp-server # Selective
88101
```
89102

codex-rs/tui/docs.md

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ TUI is one of the primary entry points, invoked when running `codex` without a s
1414
- **Depends on** `codex-acp` for ACP agent backend (alternative to HTTP-based LLM providers)
1515
- **Depends on** `codex-common` for CLI argument parsing and shared utilities
1616
- **Uses** `codex-protocol` types for events and messages
17-
- **Integrates** `codex-feedback` for tracing/feedback collection
17+
- **Optionally integrates** `codex-feedback`, `codex-login`, `codex-backend-client` via feature flags
1818

19-
The `cli/` crate's `main.rs` dispatches to `codex_tui::run_main()` for interactive mode.
19+
The `cli/` crate's `main.rs` dispatches to `codex_tui::run_main()` for interactive mode. Feature flags propagate from CLI to TUI for coordinated modular builds.
2020

2121
### Core Implementation
2222

@@ -84,7 +84,7 @@ In `spawn_acp_agent()`, the main task must drop its `Arc<AcpBackend>` reference
8484
**Onboarding:**
8585

8686
The `onboarding/` module handles first-run experience:
87-
- Login screen (ChatGPT OAuth or API key)
87+
- Login screen (ChatGPT OAuth or API key) - requires `login` feature
8888
- Trust screen (directory permission settings)
8989
- Windows WSL setup instructions
9090

@@ -95,6 +95,33 @@ The `onboarding/` module handles first-run experience:
9595

9696
### Things to Know
9797

98+
**Feature Flags Architecture:**
99+
100+
The TUI crate uses Cargo feature flags to enable modular builds with two primary modes:
101+
102+
| Feature | Optional Dep | Description |
103+
|---------|-------------|-------------|
104+
| `full` | - | Meta-feature enabling all optional features |
105+
| `login` | `codex-login` | ChatGPT/API login functionality |
106+
| `feedback` | `codex-feedback` | Sentry feedback integration |
107+
| `backend-client` | `codex-backend-client` | Cloud tasks backend client |
108+
| `upstream-updates` | - | OpenAI/Codex update checking mechanism |
109+
110+
Feature gating patterns:
111+
- Import gating: `#[cfg(feature = "backend-client")] use codex_backend_client::Client`
112+
- Struct field gating: `#[cfg(feature = "feedback")] feedback: CodexFeedback`
113+
- Function parameter gating: `#[cfg(feature = "feedback")] feedback: CodexFeedback` in `App::run()`
114+
- Enum variant gating: `AppEvent::Feedback` only exists with `feedback` feature
115+
116+
**Update System Selection:**
117+
118+
The update checking system is selected at compile time via `upstream-updates`:
119+
- With `upstream-updates`: Uses `update_action.rs`, `updates.rs`, `update_prompt.rs` from `@/codex-rs/tui/src/`
120+
- Without `upstream-updates`: Uses Nori-specific versions from `@/codex-rs/tui/src/nori/`
121+
- Re-exports in `lib.rs` provide unified access: `pub mod update_action` re-exports from either location
122+
123+
Update modules are only compiled in release builds (`#[cfg(not(debug_assertions))]`) to avoid unnecessary checks during development.
124+
98125
**Rendering Patterns:**
99126

100127
The crate uses Ratatui's `Stylize` trait for concise styling:

codex-rs/tui/src/lib.rs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,31 @@ mod terminal_palette;
7777
mod text_formatting;
7878
mod tui;
7979
mod ui_consts;
80+
81+
// Upstream OpenAI/Codex update modules (only included with upstream-updates feature)
82+
// The update_action module is available in all builds for the UpdateAction type
83+
// The update_prompt and updates modules are only for release builds
84+
#[cfg(feature = "upstream-updates")]
8085
pub mod update_action;
86+
#[cfg(all(not(debug_assertions), feature = "upstream-updates"))]
8187
mod update_prompt;
88+
#[cfg(all(not(debug_assertions), feature = "upstream-updates"))]
8289
mod updates;
90+
91+
// Nori-specific update modules (only when NOT using upstream-updates)
92+
// Re-export as pub mod for external access to UpdateAction type
93+
#[cfg(not(feature = "upstream-updates"))]
94+
pub mod update_action {
95+
pub use super::nori::update_action::*;
96+
}
97+
98+
// Re-export the appropriate update prompt functions based on feature (release builds only)
99+
#[cfg(all(not(debug_assertions), feature = "upstream-updates"))]
100+
pub(crate) use update_prompt::{UpdatePromptOutcome, run_update_prompt_if_needed};
101+
102+
#[cfg(all(not(debug_assertions), not(feature = "upstream-updates")))]
103+
pub(crate) use nori::update_prompt::{UpdatePromptOutcome, run_update_prompt_if_needed};
104+
83105
mod version;
84106

85107
mod wrapping;
@@ -388,11 +410,9 @@ async fn run_ratatui_app(
388410

389411
#[cfg(not(debug_assertions))]
390412
{
391-
use crate::update_prompt::UpdatePromptOutcome;
392-
393413
let skip_update_prompt = cli.prompt.as_ref().is_some_and(|prompt| !prompt.is_empty());
394414
if !skip_update_prompt {
395-
match update_prompt::run_update_prompt_if_needed(&mut tui, &initial_config).await? {
415+
match run_update_prompt_if_needed(&mut tui, &initial_config).await? {
396416
UpdatePromptOutcome::Continue => {}
397417
UpdatePromptOutcome::RunUpdate(action) => {
398418
crate::tui::restore()?;

codex-rs/tui/src/nori/docs.md

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@ Path: @/codex-rs/tui/src/nori
44

55
### Overview
66

7-
The `nori` module contains Nori-specific TUI customizations that replace or extend the default Codex UI behavior. Currently, the primary component is a branded session header that displays at the start of each TUI session.
7+
The `nori` module contains Nori-specific TUI customizations that replace or extend the default Codex UI behavior. It provides branded session headers, agent picking, feedback redirection, and a Nori-specific update checking mechanism that queries GitHub releases instead of OpenAI's update system.
88

99
### How it fits into the larger codebase
1010

1111
- **Called by** `history_cell.rs` via `new_session_info()` which delegates to `new_nori_session_info()`
1212
- **Replaces** the original `SessionHeaderHistoryCell` (preserved as dead code for potential future feature flag selection)
1313
- **Uses** `HistoryCell` trait from `@/codex-rs/tui/src/history_cell.rs` for consistent rendering
1414
- **Reads** `~/.nori-config.json` for Nori profile information
15+
- **Conditionally compiled** based on feature flags - modules like `feedback.rs`, `updates.rs` are only included when their corresponding upstream features are disabled
16+
- **Re-exported** by `@/codex-rs/tui/src/lib.rs` to provide unified access to update types regardless of which update system is active
1517

1618
### Core Implementation
1719

@@ -54,6 +56,33 @@ The banner uses green+bold for alphabetic characters and dark gray for structura
5456
- `acp_model_picker_params()` renders the `/model` fallback page that disables selection when ACP mode is active and points the user back to `/agent`.
5557
- `PendingAgentSelection` holds the selected model/display name pair so the App and `ChatWidget` can store it until the next prompt triggers `AppEvent::SubmitWithAgentSwitch`, at which point the conversation is rebuilt with the new model and the picker view is dismissed.
5658

59+
**Feedback Redirect (`feedback.rs`):**
60+
61+
Compiled only when `feedback` feature is disabled (`#[cfg(not(feature = "feedback"))]`). Redirects `/feedback` command to GitHub Discussions instead of OpenAI's feedback system:
62+
- `NORI_FEEDBACK_URL`: Points to `https://github.com/tilework-tech/nori-cli/discussions`
63+
- `feedback_message()`: Returns user-facing message with the discussions URL
64+
65+
**Update System (`update_action.rs`, `updates.rs`, `update_prompt.rs`):**
66+
67+
Compiled only when `upstream-updates` feature is disabled. Provides Nori-specific update checking:
68+
69+
`update_action.rs`:
70+
- `UpdateAction` enum with `NpmGlobalLatest` and `Manual` variants
71+
- `command_args()` returns the shell command to execute the update
72+
- `get_update_action()` (release builds only) checks `NORI_MANAGED_BY_NPM` env var to determine update method
73+
74+
`updates.rs` (release builds only):
75+
- Queries `https://api.github.com/repos/tilework-tech/nori-cli/releases/latest` for version info
76+
- Caches version data in `~/.codex/nori-version.json` with 20-hour refresh interval
77+
- `get_upgrade_version()`: Background-refreshes cache and returns newer version if available
78+
- `get_upgrade_version_for_popup()`: Returns version only if not previously dismissed
79+
- `dismiss_version()`: Persists user's dismissal to avoid repeated prompts
80+
- Tag format: expects `nori-v<semver>` (e.g., `nori-v1.2.3`)
81+
82+
`update_prompt.rs` (release builds only):
83+
- `run_update_prompt_if_needed()`: Displays update prompt UI when new version available
84+
- Returns `UpdatePromptOutcome::Continue` or `UpdatePromptOutcome::RunUpdate(action)`
85+
5786
### Things to Know
5887

5988
**Profile Display:**
@@ -70,4 +99,17 @@ The original Codex session header (`SessionHeaderHistoryCell`) is preserved with
7099

71100
The session header uses a max inner width of 60 characters. Directory paths are center-truncated when they exceed available space (e.g., `~/a/b/…/y/z`).
72101

102+
**Conditional Compilation:**
103+
104+
Module availability in `mod.rs` follows this pattern:
105+
106+
```
107+
session_header.rs, agent_picker.rs -> Always included
108+
feedback.rs -> #[cfg(not(feature = "feedback"))]
109+
update_action.rs -> #[cfg(not(feature = "upstream-updates"))]
110+
update_prompt.rs, updates.rs -> #[cfg(all(not(feature = "upstream-updates"), not(debug_assertions)))]
111+
```
112+
113+
The `lib.rs` re-export logic ensures `UpdateAction` type is always available via `codex_tui::update_action::UpdateAction` regardless of which update system is compiled.
114+
73115
Created and maintained by Nori.

codex-rs/tui/src/nori/mod.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,12 @@ pub(crate) mod session_header;
88

99
#[cfg(not(feature = "feedback"))]
1010
pub(crate) mod feedback;
11+
12+
// update_action is available in all builds for the UpdateAction type
13+
// update_prompt and updates are only for release builds
14+
#[cfg(not(feature = "upstream-updates"))]
15+
pub(crate) mod update_action;
16+
#[cfg(all(not(feature = "upstream-updates"), not(debug_assertions)))]
17+
pub(crate) mod update_prompt;
18+
#[cfg(all(not(feature = "upstream-updates"), not(debug_assertions)))]
19+
pub(crate) mod updates;

codex-rs/tui/src/nori/update_action.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ impl UpdateAction {
2929
}
3030
}
3131

32+
/// Returns the update action for the current installation.
33+
///
34+
/// Unlike the upstream version which returns `None` for unknown installations,
35+
/// this always returns `Some()` because Nori supports a manual update fallback
36+
/// that directs users to GitHub releases.
3237
#[cfg(not(debug_assertions))]
3338
pub(crate) fn get_update_action() -> Option<UpdateAction> {
3439
let managed_by_npm = std::env::var_os("NORI_MANAGED_BY_NPM").is_some();

0 commit comments

Comments
 (0)