Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ jobs:
- name: Test Codex Lab app package builder
run: python3 -m unittest discover -s scripts/codex_lab_package -p 'test_*.py'

- name: Test local developer scripts
run: python3 -m unittest discover -s scripts/local -p 'test_*.py'

- name: Smoke test Codex Lab app package layout
shell: bash
run: |
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/codex-lab-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
- name: Build Codex Lab CLI
working-directory: codex-rs
shell: bash
run: cargo build --release -p codex-cli --bin codex
run: cargo build --release -p codex-cli --bin codex-lab

- name: Build Codex Lab app bundle
id: package
Expand All @@ -52,7 +52,7 @@ jobs:
lab_version="$(PYTHONPATH=scripts python3 -c 'from codex_package.version import read_workspace_version; print(read_workspace_version())')"
mkdir -p "$output_root"
python3 scripts/build_codex_lab_app.py \
--codex-bin codex-rs/target/release/codex \
--codex-bin codex-rs/target/release/codex-lab \
--app-dir "$app_dir" \
--shim-dir "$shim_dir" \
--short-version "$lab_version" \
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/codex-lab-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ jobs:
- name: Build Codex Lab CLI
working-directory: codex-rs
shell: bash
run: cargo build --release -p codex-cli --bin codex
run: cargo build --release -p codex-cli --bin codex-lab

- name: Build Codex Lab app bundle
id: package
Expand All @@ -81,7 +81,7 @@ jobs:
lab_version="$(PYTHONPATH=scripts python3 -c 'from codex_package.version import read_workspace_version; print(read_workspace_version())')"
mkdir -p "$output_root"
python3 scripts/build_codex_lab_app.py \
--codex-bin codex-rs/target/release/codex \
--codex-bin codex-rs/target/release/codex-lab \
--app-dir "$app_dir" \
--shim-dir "$shim_dir" \
--short-version "$lab_version" \
Expand Down
4 changes: 4 additions & 0 deletions codex-rs/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ build = "build.rs"
name = "codex"
path = "src/main.rs"

[[bin]]
name = "codex-lab"
path = "src/bin/codex-lab.rs"

[lib]
name = "codex_cli"
path = "src/lib.rs"
Expand Down
1 change: 1 addition & 0 deletions codex-rs/cli/src/bin/codex-lab.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include!("../main.rs");
85 changes: 70 additions & 15 deletions codex-rs/cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use clap::Args;
use clap::Command;
use clap::CommandFactory;
use clap::FromArgMatches;
use clap::Parser;
use clap_complete::Shell;
use clap_complete::generate;
Expand Down Expand Up @@ -92,12 +94,7 @@ use codex_terminal_detection::TerminalName;
author,
version,
// If a sub‑command is given, ignore requirements of the default args.
subcommand_negates_reqs = true,
// The executable is sometimes invoked via a platform‑specific name like
// `codex-x86_64-unknown-linux-musl`, but the help output should always use
// the generic `codex` command name that users run.
bin_name = "codex",
override_usage = "codex [OPTIONS] [PROMPT]\n codex [OPTIONS] <COMMAND> [ARGS]"
subcommand_negates_reqs = true
)]
struct MultitoolCli {
#[clap(flatten)]
Expand Down Expand Up @@ -894,19 +891,20 @@ fn stage_str(stage: Stage) -> &'static str {

fn main() -> anyhow::Result<()> {
arg0_dispatch_or_else(|arg0_paths: Arg0DispatchPaths| async move {
cli_main(arg0_paths).await?;
cli_main(arg0_paths, cli_command_name()).await?;
Ok(())
})
}

async fn cli_main(arg0_paths: Arg0DispatchPaths) -> anyhow::Result<()> {
async fn cli_main(arg0_paths: Arg0DispatchPaths, command_name: &'static str) -> anyhow::Result<()> {
let cli = named_multitool_command(command_name);
let MultitoolCli {
config_overrides: mut root_config_overrides,
feature_toggles,
remote,
mut interactive,
subcommand,
} = MultitoolCli::parse();
} = MultitoolCli::from_arg_matches(&cli.get_matches())?;

// Fold --enable/--disable into config overrides so they flow to all subcommands.
let toggle_overrides = feature_toggles.to_overrides()?;
Expand Down Expand Up @@ -959,7 +957,7 @@ async fn cli_main(arg0_paths: Arg0DispatchPaths) -> anyhow::Result<()> {
root_remote_auth_token_env.as_deref(),
"review",
)?;
let mut exec_cli = ExecCli::try_parse_from(["codex", "exec"])?;
let mut exec_cli = ExecCli::try_parse_from([command_name, "exec"])?;
exec_cli
.shared
.inherit_exec_root_options(&interactive.shared);
Expand Down Expand Up @@ -1306,7 +1304,7 @@ async fn cli_main(arg0_paths: Arg0DispatchPaths) -> anyhow::Result<()> {
root_remote_auth_token_env.as_deref(),
"completion",
)?;
print_completion(completion_cli);
print_completion(completion_cli, command_name);
}
Some(Subcommand::Update) => {
reject_remote_mode_for_subcommand(
Expand Down Expand Up @@ -2356,12 +2354,35 @@ fn merge_interactive_cli_flags(interactive: &mut TuiCli, subcommand_cli: TuiCli)
.extend(config_overrides.raw_overrides);
}

fn print_completion(cmd: CompletionCommand) {
let mut app = MultitoolCli::command();
let name = "codex";
fn print_completion(cmd: CompletionCommand, command_name: &'static str) {
let mut app = named_multitool_command(command_name);
let name = command_name;
generate(cmd.shell, &mut app, name, &mut std::io::stdout());
}

fn named_multitool_command(command_name: &'static str) -> Command {
MultitoolCli::command()
.bin_name(command_name)
.override_usage(format!(
"{command_name} [OPTIONS] [PROMPT]\n {command_name} [OPTIONS] <COMMAND> [ARGS]"
))
}

fn cli_command_name() -> &'static str {
let Some(arg0) = std::env::args_os().next() else {
return "codex";
};
if std::path::Path::new(&arg0)
.file_name()
.and_then(|name| name.to_str())
== Some("codex-lab")
{
"codex-lab"
} else {
"codex"
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -2682,6 +2703,33 @@ mod tests {
assert!(cmd.bundled);
}

#[test]
fn codex_lab_command_name_updates_help_usage() {
let help = named_multitool_command("codex-lab")
.render_help()
.to_string();

assert!(help.contains("codex-lab [OPTIONS] [PROMPT]"));
assert!(help.contains("codex-lab [OPTIONS] <COMMAND> [ARGS]"));
}

#[test]
fn codex_command_name_keeps_upstream_usage() {
let help = named_multitool_command("codex").render_help().to_string();

assert!(help.contains("codex [OPTIONS] [PROMPT]"));
assert!(help.contains("codex [OPTIONS] <COMMAND> [ARGS]"));
}

#[test]
fn codex_lab_exec_help_uses_lab_command_and_home() {
let help = help_from_args(&["codex-lab", "exec", "--help"]);

assert!(help.contains("Usage: codex-lab exec"));
assert!(help.contains("CODEX_LAB_HOME"));
assert!(!help.contains("CODEX_HOME"));
}

#[test]
fn responses_subcommand_is_not_registered() {
let command = MultitoolCli::command();
Expand All @@ -2693,7 +2741,14 @@ mod tests {
}

fn help_from_args(args: &[&str]) -> String {
let err = MultitoolCli::try_parse_from(args).expect_err("help should short-circuit");
let command_name = if args.first().copied() == Some("codex-lab") {
"codex-lab"
} else {
"codex"
};
let err = named_multitool_command(command_name)
.try_get_matches_from(args)
.expect_err("help should short-circuit");
assert_eq!(err.kind(), clap::error::ErrorKind::DisplayHelp);
err.to_string()
}
Expand Down
7 changes: 2 additions & 5 deletions codex-rs/exec/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@ use codex_utils_cli::SharedCliOptions;
use std::path::PathBuf;

#[derive(Parser, Debug)]
#[command(
version,
override_usage = "codex exec [OPTIONS] [PROMPT]\n codex exec [OPTIONS] <COMMAND> [ARGS]"
)]
#[command(version)]
pub struct Cli {
/// Action to perform. If omitted, runs a new non-interactive session.
#[command(subcommand)]
Expand All @@ -31,7 +28,7 @@ pub struct Cli {
#[arg(long = "ephemeral", global = true, default_value_t = false)]
pub ephemeral: bool,

/// Do not load `$CODEX_HOME/config.toml`; auth still uses `CODEX_HOME`.
/// Do not load `$CODEX_LAB_HOME/config.toml`; auth still uses `CODEX_LAB_HOME`.
#[arg(long = "ignore-user-config", global = true, default_value_t = false)]
pub ignore_user_config: bool,

Expand Down
21 changes: 15 additions & 6 deletions docs/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ The GitHub Release also contains a [DotSlash](https://dotslash-cli.com/) file fo
### Build from source

```bash
# Clone the repository and navigate to the root of the Cargo workspace.
git clone https://github.com/openai/codex.git
cd codex/codex-rs
# Clone the repository and navigate to the root of the Codex Lab checkout.
git clone https://github.com/cbusillo/codex-lab.git
cd codex-lab

# Install the Rust toolchain, if necessary.
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
Expand All @@ -29,11 +29,20 @@ cargo install --locked just
# Install nextest for the `just test` helper.
cargo install --locked cargo-nextest

# Build Codex.
# Build Codex Lab from the Rust workspace.
cd codex-rs
cargo build

# Launch the TUI with a sample prompt.
cargo run --bin codex -- "explain this codebase to me"
# Launch the TUI with a sample prompt from the workspace.
cargo run --bin codex-lab -- "explain this codebase to me"
cd ..

# Install the dogfood launcher into ~/.local/bin/codex-lab. The launcher keeps
# rebuilding this checkout incrementally, defaults CODEX_LAB_HOME to
# ~/.codex-lab when unset, and leaves upstream `codex` plus Every Code `code`
# untouched.
just install-codex-lab-dev
codex-lab "explain this codebase to me"

# After making changes, use the root justfile helpers (they default to codex-rs):
just fmt
Expand Down
10 changes: 10 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ alias c := codex
codex *args:
cargo run --bin codex -- {args}

# Install a local `codex-lab` launcher for dogfooding this checkout.
[no-cd]
[unix]
install-codex-lab-dev *args:
{{ justfile_directory() }}/scripts/local/install-codex-lab-dev.sh {args}

# `codex exec`
exec *args:
cargo run --bin codex -- exec {args}
Expand Down Expand Up @@ -100,6 +106,10 @@ test *args:
test-github-scripts:
{{ python }} -m unittest discover -s {{ justfile_directory() }}/.github/scripts -p 'test_*.py'

[no-cd]
test-local-scripts:
{{ python }} -m unittest discover -s {{ justfile_directory() }}/scripts/local -p 'test_*.py'

# Run explicit workspace benchmark targets.
bench *args:
cargo bench --workspace --bench '*' {args}
Expand Down
2 changes: 1 addition & 1 deletion scripts/codex_lab_package/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Example:

```shell
scripts/build_codex_lab_app.py \
--codex-bin codex-rs/target/release/codex \
--codex-bin codex-rs/target/release/codex-lab \
--app-dir /tmp/Codex\ Lab.app \
--shim-dir /tmp/codex-lab-bin \
--force
Expand Down
Loading
Loading