diff --git a/.codespellignore b/.codespellignore index d74f5ed86c9..835c0e538e7 100644 --- a/.codespellignore +++ b/.codespellignore @@ -1,2 +1,3 @@ iTerm +iTerm2 psuedo \ No newline at end of file diff --git a/.codespellrc b/.codespellrc index da831d8957e..84b4495e310 100644 --- a/.codespellrc +++ b/.codespellrc @@ -3,4 +3,4 @@ skip = .git*,vendor,*-lock.yaml,*.lock,.codespellrc,*test.ts,*.jsonl,frame*.txt check-hidden = true ignore-regex = ^\s*"image/\S+": ".*|\b(afterAll)\b -ignore-words-list = ratatui,ser +ignore-words-list = ratatui,ser,iTerm,iterm2,iterm diff --git a/.github/actions/macos-code-sign/action.yml b/.github/actions/macos-code-sign/action.yml index 5c11ac7728c..75b3a2ba260 100644 --- a/.github/actions/macos-code-sign/action.yml +++ b/.github/actions/macos-code-sign/action.yml @@ -4,6 +4,14 @@ inputs: target: description: Rust compilation target triple (e.g. aarch64-apple-darwin). required: true + sign-binaries: + description: Whether to sign and notarize the macOS binaries. + required: false + default: "true" + sign-dmg: + description: Whether to sign and notarize the macOS dmg. + required: false + default: "true" apple-certificate: description: Base64-encoded Apple signing certificate (P12). required: true @@ -107,6 +115,7 @@ runs: echo "::add-mask::$APPLE_CODESIGN_IDENTITY" - name: Sign macOS binaries + if: ${{ inputs.sign-binaries == 'true' }} shell: bash run: | set -euo pipefail @@ -127,6 +136,7 @@ runs: done - name: Notarize macOS binaries + if: ${{ inputs.sign-binaries == 'true' }} shell: bash env: APPLE_NOTARIZATION_KEY_P8: ${{ inputs.apple-notarization-key-p8 }} @@ -149,6 +159,8 @@ runs: } trap cleanup_notary EXIT + source "$GITHUB_ACTION_PATH/notary_helpers.sh" + notarize_binary() { local binary="$1" local source_path="codex-rs/target/${{ inputs.target }}/release/${binary}" @@ -162,31 +174,53 @@ runs: rm -f "$archive_path" ditto -c -k --keepParent "$source_path" "$archive_path" - submission_json=$(xcrun notarytool submit "$archive_path" \ - --key "$notary_key_path" \ - --key-id "$APPLE_NOTARIZATION_KEY_ID" \ - --issuer "$APPLE_NOTARIZATION_ISSUER_ID" \ - --output-format json \ - --wait) - - status=$(printf '%s\n' "$submission_json" | jq -r '.status // "Unknown"') - submission_id=$(printf '%s\n' "$submission_json" | jq -r '.id // ""') + notarize_submission "$binary" "$archive_path" "$notary_key_path" + } - if [[ -z "$submission_id" ]]; then - echo "Failed to retrieve submission ID for $binary" - exit 1 - fi + notarize_binary "codex" + notarize_binary "codex-responses-api-proxy" - echo "::notice title=Notarization::$binary submission ${submission_id} completed with status ${status}" + - name: Sign and notarize macOS dmg + if: ${{ inputs.sign-dmg == 'true' }} + shell: bash + env: + APPLE_NOTARIZATION_KEY_P8: ${{ inputs.apple-notarization-key-p8 }} + APPLE_NOTARIZATION_KEY_ID: ${{ inputs.apple-notarization-key-id }} + APPLE_NOTARIZATION_ISSUER_ID: ${{ inputs.apple-notarization-issuer-id }} + run: | + set -euo pipefail - if [[ "$status" != "Accepted" ]]; then - echo "Notarization failed for ${binary} (submission ${submission_id}, status ${status})" + for var in APPLE_CODESIGN_IDENTITY APPLE_NOTARIZATION_KEY_P8 APPLE_NOTARIZATION_KEY_ID APPLE_NOTARIZATION_ISSUER_ID; do + if [[ -z "${!var:-}" ]]; then + echo "$var is required" exit 1 fi + done + + notary_key_path="${RUNNER_TEMP}/notarytool.key.p8" + echo "$APPLE_NOTARIZATION_KEY_P8" | base64 -d > "$notary_key_path" + cleanup_notary() { + rm -f "$notary_key_path" } + trap cleanup_notary EXIT - notarize_binary "codex" - notarize_binary "codex-responses-api-proxy" + source "$GITHUB_ACTION_PATH/notary_helpers.sh" + + dmg_path="codex-rs/target/${{ inputs.target }}/release/codex-${{ inputs.target }}.dmg" + + if [[ ! -f "$dmg_path" ]]; then + echo "dmg $dmg_path not found" + exit 1 + fi + + keychain_args=() + if [[ -n "${APPLE_CODESIGN_KEYCHAIN:-}" && -f "${APPLE_CODESIGN_KEYCHAIN}" ]]; then + keychain_args+=(--keychain "${APPLE_CODESIGN_KEYCHAIN}") + fi + + codesign --force --timestamp --sign "$APPLE_CODESIGN_IDENTITY" "${keychain_args[@]}" "$dmg_path" + notarize_submission "codex-${{ inputs.target }}.dmg" "$dmg_path" "$notary_key_path" + xcrun stapler staple "$dmg_path" - name: Remove signing keychain if: ${{ always() }} diff --git a/.github/actions/macos-code-sign/notary_helpers.sh b/.github/actions/macos-code-sign/notary_helpers.sh new file mode 100644 index 00000000000..ad9757fe3cb --- /dev/null +++ b/.github/actions/macos-code-sign/notary_helpers.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash + +notarize_submission() { + local label="$1" + local path="$2" + local notary_key_path="$3" + + if [[ -z "${APPLE_NOTARIZATION_KEY_ID:-}" || -z "${APPLE_NOTARIZATION_ISSUER_ID:-}" ]]; then + echo "APPLE_NOTARIZATION_KEY_ID and APPLE_NOTARIZATION_ISSUER_ID are required for notarization" + exit 1 + fi + + if [[ -z "$notary_key_path" || ! -f "$notary_key_path" ]]; then + echo "Notary key file $notary_key_path not found" + exit 1 + fi + + if [[ ! -f "$path" ]]; then + echo "Notarization payload $path not found" + exit 1 + fi + + local submission_json + submission_json=$(xcrun notarytool submit "$path" \ + --key "$notary_key_path" \ + --key-id "$APPLE_NOTARIZATION_KEY_ID" \ + --issuer "$APPLE_NOTARIZATION_ISSUER_ID" \ + --output-format json \ + --wait) + + local status submission_id + status=$(printf '%s\n' "$submission_json" | jq -r '.status // "Unknown"') + submission_id=$(printf '%s\n' "$submission_json" | jq -r '.id // ""') + + if [[ -z "$submission_id" ]]; then + echo "Failed to retrieve submission ID for $label" + exit 1 + fi + + echo "::notice title=Notarization::$label submission ${submission_id} completed with status ${status}" + + if [[ "$status" != "Accepted" ]]; then + echo "Notarization failed for ${label} (submission ${submission_id}, status ${status})" + exit 1 + fi +} diff --git a/.github/workflows/cargo-deny.yml b/.github/workflows/cargo-deny.yml index e365420cca9..60adb38710f 100644 --- a/.github/workflows/cargo-deny.yml +++ b/.github/workflows/cargo-deny.yml @@ -20,7 +20,7 @@ jobs: uses: dtolnay/rust-toolchain@stable - name: Run cargo-deny - uses: EmbarkStudios/cargo-deny-action@v1 + uses: EmbarkStudios/cargo-deny-action@v2 with: rust-version: stable manifest-path: ./codex-rs/Cargo.toml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 475493b0bf7..288b79885d3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,7 +39,7 @@ jobs: run: | set -euo pipefail # Use a rust-release version that includes all native binaries. - CODEX_VERSION=0.74.0-alpha.3 + CODEX_VERSION=0.74.0 OUTPUT_DIR="${RUNNER_TEMP}" python3 ./scripts/stage_npm_packages.py \ --release-version "$CODEX_VERSION" \ diff --git a/.github/workflows/rust-release.yml b/.github/workflows/rust-release.yml index f41e6087257..11c769d95cb 100644 --- a/.github/workflows/rust-release.yml +++ b/.github/workflows/rust-release.yml @@ -128,11 +128,72 @@ jobs: account-name: ${{ secrets.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }} certificate-profile-name: ${{ secrets.AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE_NAME }} - - if: ${{ matrix.runner == 'macos-15-xlarge' }} - name: MacOS code signing + - if: ${{ runner.os == 'macOS' }} + name: MacOS code signing (binaries) uses: ./.github/actions/macos-code-sign with: target: ${{ matrix.target }} + sign-binaries: "true" + sign-dmg: "false" + apple-certificate: ${{ secrets.APPLE_CERTIFICATE_P12 }} + apple-certificate-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} + apple-notarization-key-p8: ${{ secrets.APPLE_NOTARIZATION_KEY_P8 }} + apple-notarization-key-id: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }} + apple-notarization-issuer-id: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }} + + - if: ${{ runner.os == 'macOS' }} + name: Build macOS dmg + shell: bash + run: | + set -euo pipefail + + target="${{ matrix.target }}" + release_dir="target/${target}/release" + dmg_root="${RUNNER_TEMP}/codex-dmg-root" + volname="Codex (${target})" + dmg_path="${release_dir}/codex-${target}.dmg" + + # The previous "MacOS code signing (binaries)" step signs + notarizes the + # built artifacts in `${release_dir}`. This step packages *those same* + # signed binaries into a dmg. + codex_binary_path="${release_dir}/codex" + proxy_binary_path="${release_dir}/codex-responses-api-proxy" + + rm -rf "$dmg_root" + mkdir -p "$dmg_root" + + if [[ ! -f "$codex_binary_path" ]]; then + echo "Binary $codex_binary_path not found" + exit 1 + fi + if [[ ! -f "$proxy_binary_path" ]]; then + echo "Binary $proxy_binary_path not found" + exit 1 + fi + + ditto "$codex_binary_path" "${dmg_root}/codex" + ditto "$proxy_binary_path" "${dmg_root}/codex-responses-api-proxy" + + rm -f "$dmg_path" + hdiutil create \ + -volname "$volname" \ + -srcfolder "$dmg_root" \ + -format UDZO \ + -ov \ + "$dmg_path" + + if [[ ! -f "$dmg_path" ]]; then + echo "dmg $dmg_path not found after build" + exit 1 + fi + + - if: ${{ runner.os == 'macOS' }} + name: MacOS code signing (dmg) + uses: ./.github/actions/macos-code-sign + with: + target: ${{ matrix.target }} + sign-binaries: "false" + sign-dmg: "true" apple-certificate: ${{ secrets.APPLE_CERTIFICATE_P12 }} apple-certificate-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} apple-notarization-key-p8: ${{ secrets.APPLE_NOTARIZATION_KEY_P8 }} @@ -160,6 +221,10 @@ jobs: cp target/${{ matrix.target }}/release/codex-responses-api-proxy.sigstore "$dest/codex-responses-api-proxy-${{ matrix.target }}.sigstore" fi + if [[ "${{ matrix.target }}" == *apple-darwin ]]; then + cp target/${{ matrix.target }}/release/codex-${{ matrix.target }}.dmg "$dest/codex-${{ matrix.target }}.dmg" + fi + - if: ${{ matrix.runner == 'windows-11-arm' }} name: Install zstd shell: powershell @@ -194,7 +259,7 @@ jobs: base="$(basename "$f")" # Skip files that are already archives (shouldn't happen, but be # safe). - if [[ "$base" == *.tar.gz || "$base" == *.zip ]]; then + if [[ "$base" == *.tar.gz || "$base" == *.zip || "$base" == *.dmg ]]; then continue fi diff --git a/.gitignore b/.gitignore index a58e9dfb7b9..efd2f78dcc1 100644 --- a/.gitignore +++ b/.gitignore @@ -66,6 +66,7 @@ coverage/ # personal files personal/ +.codexel/ # os .DS_Store @@ -85,3 +86,8 @@ CHANGELOG.ignore.md # nix related .direnv .envrc + +# Python bytecode files +__pycache__/ +*.pyc + diff --git a/CHANGELOG.md b/CHANGELOG.md index eee8432f3ac..cc8dcdf6caa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,13 +11,72 @@ edited between the markers. ### Highlights +- _No fork-only changes yet._ + +### Details + + + +_No fork-only changes yet._ + + + +## [0.1.3] - 2025-12-20 + +Upstream baseline: openai/codex@a6974087e5c04fc711af68f70fe93f7f5d2b0981 +Release commit: 44f8df17aa11051fcf3919a9c16fe3b9c3296d66 + +### Highlights + +- Merge latest `upstream/main` into `v0.1.3`. +- Fix subagent config constraint handling after upstream merge. +- Add a read-only spawn_subagent tool for parallel exploration and research. +- Show spawn_subagent tool calls in chat history, including live activity and token usage, and stop them on Esc. +- Fix: keep `spawn_subagent` history entries updating even when other messages are inserted. +- Plan Mode: persist approved plans under `.codexel/plan.md` (and hide `.codexel/` from built-in file tools). - Skip macOS rust-ci jobs on pull requests to avoid flaky PR runs. - Skip upstream npm package staging in CI for forks. - Fix sdk workflow to build the codexel binary. +- Fix Codexel update checks for npm/bun installs and keep the default state directory isolated to `~/.codexel`. ### Details - + + +#### Fixes + +- Fix sdk workflow codexel build +- Fix update checks and codex home isolation + +#### Documentation + +- docs: clarify Codexel fork positioning +- docs: move What's different up and mention ask_user_question + +#### TUI + +- tui: show subagent tool calls in history +- tui: keep subagent history updating +- tui: keep subagent cell live during inserts + +#### Core + +- core: fix subagent config constraints + +#### Plan Mode + +- subagent: stream activity and match plan-variants UI +- Persist approved plan and hide .codexel + +#### Branding & Packaging + +- chore: ignore .codexel +- tests: keep codexel CLI suites green + +#### Chores + +- chore: update login flow and tui snapshots +- chore: regenerate changelog #### Other @@ -26,6 +85,18 @@ edited between the markers. - Skip macOS rust-ci jobs on PRs - Skip upstream npm staging in CI for forks - Format markdown and workflow files +- Add spawn_subagent tool +- Show spawn_subagent tool calls in history +- subagent: stream token counts +- release: bump workspace version to 0.1.3 +- changelog: cut 0.1.3 +- changelog: update unreleased +- changelog: update +- Require spawn_subagent description and refresh snapshots +- changelog: fix 0.1.3 release ranges +- Merge upstream/main into v0.1.3 +- changelog: filter upstream commits in generator +- changelog: keep generators in sync ## [0.1.2] - 2025-12-19 @@ -42,13 +113,16 @@ Release commit: 79d019672838ccc532247588d31d2eda81fb42d8 +#### Fixes + +- Fix Codexel update actions + #### Plan Mode - Deduplicate plan updates in history #### Branding & Packaging -- Fix Codexel update actions - Add GitHub Release publishing for Codexel #### Other @@ -75,20 +149,23 @@ Release commit: d02343f99e3260308b2355f26e382ae04b14d7e7 +#### Fixes + +- Fix npm publish workflow yaml + #### Documentation -- Document changelog workflow in AGENTS -- Remove interactive questions from AGENTS +- docs: document changelog workflow in AGENTS +- docs: remove interactive questions from AGENTS #### Branding & Packaging -- Add Codexel changelog and generator +- changelog: add Codexel changelog and generator - Prepare Codexel npm 0.1.1 release #### Other - Update changelog for 0.1.1 -- Fix npm publish workflow yaml - Skip macOS in npm publish workflow @@ -110,49 +187,49 @@ Release commit: 3e57f558eff5b400292a6ad3c9df2721648aed6f #### Features -- Add /plan mode with plan approval +- feat: add /plan mode with plan approval #### Fixes -- Drop disabled_reason from ask_user_question rows +- fix(tui2): drop disabled_reason from ask_user_question rows #### Documentation -- Document AskUserQuestion -- Add Windows notes for just -- Fix plan mode note apostrophe +- docs: document AskUserQuestion +- docs: add Windows notes for just +- docs: fix plan mode note apostrophe #### TUI -- Show plan-variant progress -- Show plan subagent checklist -- Auto-execute approved plans -- Polish plan-variants progress -- Fix /plan cursor position -- Add review step for ask_user_question -- Taller plan approval overlay and wrapped summary -- Make Plan Mode placeholder generic +- tui: show plan-variant progress +- tui: show plan subagent checklist +- tui: auto-execute approved plans +- tui: polish plan-variants progress +- tui: fix /plan cursor position +- tui: add review step for ask_user_question +- tui: taller plan approval overlay and wrapped summary +- tui: make Plan Mode placeholder generic #### Core -- Keep plan subagents aligned with session model -- Make Plan Mode outputs junior-executable -- Pin approved plan into developer instructions -- Emit immediate plan progress on approval +- core: keep plan subagents aligned with session model +- core: make Plan Mode outputs junior-executable +- core: pin approved plan into developer instructions +- core: emit immediate plan progress on approval #### Plan Mode -- Run variants in parallel with status -- Show subagent thinking/writing status -- Show per-variant token usage -- Prevent nested plan variants and shrink prompts -- Tighten prompts to avoid retry loops +- plan: run variants in parallel with status +- plan: show subagent thinking/writing status +- plan: show per-variant token usage +- plan: prevent nested plan variants and shrink prompts +- plan: tighten prompts to avoid retry loops - Improve /plan detail and plan variants - Use ASCII ranges in plan prompts - Tidy plan mode prompt bullets - Improve plan approval UI and auto-execute after /plan - Add configurable plan model setting -- Humanize exec activity + multiline goal +- plan: humanize exec activity + multiline goal #### Branding & Packaging @@ -162,8 +239,8 @@ Release commit: 3e57f558eff5b400292a6ad3c9df2721648aed6f #### Chores -- Fix build after rebasing onto upstream/main -- Sync built-in prompts with upstream +- chore: fix build after rebasing onto upstream/main +- chore(core): sync built-in prompts with upstream #### Other diff --git a/README.md b/README.md index 4954060687a..1fbfb75c906 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,18 @@ -
npm i -g @ixe1/codexel
or brew install --cask codexel
+ npm i -g @ixe1/codexel
+ brew install --cask codexel
+ or download from GitHub Releases
+
Codexel is a coding agent from OpenAI that runs locally on your computer. - -If you want Codex in your code editor (VS Code, Cursor, Windsurf), install in your IDE -If you are looking for the cloud-based agent from OpenAI, Codex Web, go to chatgpt.com/codex
+
+ Codexel is an unofficial community fork of
+ OpenAI Codex CLI (a local coding agent).
+
+ This repository is community-maintained and is not an official OpenAI project.
+
+ IDE extension: developers.openai.com/codex/ide
+ Hosted agent: chatgpt.com/codex
+
@@ -11,9 +20,24 @@
---
+## What's different in Codexel?
+
+Codexel is a fork of upstream Codex CLI with extra UX and workflow improvements. Recent highlights include:
+
+- Plan Mode: `/plan` with plan approval, plan variants, and automatic execution after approval.
+- `ask_user_question`: a tool to ask structured multiple-choice clarifying questions.
+- `spawn_subagent`: a read-only parallel research tool surfaced in the TUI (with live activity and token usage).
+- TUI improvements for streaming status, tool visibility, and long-running work.
+- Isolated state by default in `~/.codexel` (separate from the legacy `~/.codex`).
+- Packaging and update-check fixes for Codexel's release channels.
+
+For the full list of Codexel-only changes, see [CHANGELOG.md](./CHANGELOG.md).
+
+---
+
## Quickstart
-### Installing and running Codexel
+### Install
Install globally with your preferred package manager. If you use npm:
@@ -27,7 +51,7 @@ Alternatively, if you use Homebrew:
brew install --cask codexel
```
-Then simply run `codexel` to get started:
+Then run `codexel`:
```shell
codexel
@@ -51,59 +75,53 @@ Each archive contains a single entry with the platform baked into the name (e.g.
-### Using Codexel with your ChatGPT plan
+### Authenticate
(
sandbox_cwd: P,
codex_linux_sandbox_exe: Option (
writable_folder: P,
codex_linux_sandbox_exe: Option(
+async fn send_sandbox_state_update(
sandbox_state: SandboxState,
service: &RunningService