From d96961d567ba84e80efbeccb3be7186f7c837c0b Mon Sep 17 00:00:00 2001
From: Paul Lewis
Date: Sun, 21 Dec 2025 11:53:06 +0000
Subject: [PATCH 1/4] Add Homebrew cask workflow and macOS artifacts
---
.github/workflows/homebrew-cask-codexel.yml | 166 ++++++++++++++++++++
.github/workflows/npm-publish-codexel.yml | 98 ++++++++++--
README.md | 6 +-
codex-cli/scripts/verify-vendor.mjs | 2 +
codex-rs/README.md | 2 +-
codex-rs/tui/src/updates.rs | 6 +-
codex-rs/tui2/src/updates.rs | 6 +-
docs/faq.md | 7 +-
docs/releasing.md | 13 +-
9 files changed, 280 insertions(+), 26 deletions(-)
create mode 100644 .github/workflows/homebrew-cask-codexel.yml
diff --git a/.github/workflows/homebrew-cask-codexel.yml b/.github/workflows/homebrew-cask-codexel.yml
new file mode 100644
index 00000000000..dcd0eb60a9e
--- /dev/null
+++ b/.github/workflows/homebrew-cask-codexel.yml
@@ -0,0 +1,166 @@
+name: homebrew-cask-codexel
+
+on:
+ workflow_dispatch:
+ inputs:
+ tag:
+ description: "Existing Codexel tag (e.g. codexel-v0.1.3)"
+ required: true
+ type: string
+
+jobs:
+ build-macos:
+ name: Build macOS assets - ${{ matrix.target }}
+ runs-on: ${{ matrix.runner }}
+ timeout-minutes: 45
+ defaults:
+ run:
+ working-directory: codex-rs
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - runner: macos-14
+ target: aarch64-apple-darwin
+ - runner: macos-13
+ target: x86_64-apple-darwin
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ ref: ${{ inputs.tag }}
+
+ - uses: dtolnay/rust-toolchain@1.90
+ with:
+ targets: ${{ matrix.target }}
+
+ - uses: actions/cache@v5
+ with:
+ path: |
+ ~/.cargo/bin/
+ ~/.cargo/registry/index/
+ ~/.cargo/registry/cache/
+ ~/.cargo/git/db/
+ ${{ github.workspace }}/codex-rs/target/
+ key: cargo-${{ matrix.runner }}-${{ matrix.target }}-release-${{ hashFiles('**/Cargo.lock') }}
+
+ - name: Cargo build
+ shell: bash
+ run: cargo build --target ${{ matrix.target }} --release --bin codexel
+
+ - name: Package tarball
+ shell: bash
+ run: |
+ set -euo pipefail
+ out_dir="${GITHUB_WORKSPACE}/dist"
+ mkdir -p "$out_dir"
+ bin_path="target/${{ matrix.target }}/release/codexel"
+ chmod +x "$bin_path"
+ tar -C "$(dirname "$bin_path")" -czf "$out_dir/codexel-${{ matrix.target }}.tar.gz" codexel
+
+ - uses: actions/upload-artifact@v6
+ with:
+ name: codexel-${{ matrix.target }}-tarball
+ path: dist/codexel-${{ matrix.target }}.tar.gz
+ if-no-files-found: error
+
+ publish-homebrew:
+ name: Upload assets + update tap cask
+ needs: build-macos
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ steps:
+ - name: Validate tag
+ shell: bash
+ run: |
+ set -euo pipefail
+ [[ "${{ inputs.tag }}" =~ ^codexel-v[0-9]+\.[0-9]+\.[0-9]+(-((alpha|beta)\.[0-9]+))?$ ]] \
+ || { echo "Tag '${{ inputs.tag }}' doesn't match expected format"; exit 1; }
+
+ - name: Download tarballs
+ uses: actions/download-artifact@v7
+ with:
+ path: dist
+
+ - name: Move tarballs into dist/
+ shell: bash
+ run: |
+ set -euo pipefail
+ shopt -s nullglob
+ for tarball in dist/*/codexel-*-apple-darwin.tar.gz; do
+ mv "$tarball" dist/
+ done
+ ls -la dist
+
+ - name: Upload macOS assets to GitHub Release
+ env:
+ GH_TOKEN: ${{ github.token }}
+ shell: bash
+ run: |
+ set -euo pipefail
+ gh release upload "${{ inputs.tag }}" dist/codexel-*-apple-darwin.tar.gz \
+ --repo Ixe1/codexel \
+ --clobber
+
+ - name: Compute sha256
+ id: shas
+ shell: bash
+ run: |
+ set -euo pipefail
+ sha_arm=$(sha256sum dist/codexel-aarch64-apple-darwin.tar.gz | cut -d' ' -f1)
+ sha_intel=$(sha256sum dist/codexel-x86_64-apple-darwin.tar.gz | cut -d' ' -f1)
+ echo "arm=$sha_arm" >> "$GITHUB_OUTPUT"
+ echo "intel=$sha_intel" >> "$GITHUB_OUTPUT"
+
+ - name: Checkout Homebrew tap
+ if: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN != '' }}
+ uses: actions/checkout@v6
+ with:
+ repository: Ixe1/homebrew-tap
+ token: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }}
+ path: homebrew-tap
+
+ - name: Update cask
+ if: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN != '' }}
+ shell: bash
+ run: |
+ set -euo pipefail
+ version="${{ inputs.tag }}"
+ version="${version#codexel-v}"
+ sha_arm="${{ steps.shas.outputs.arm }}"
+ sha_intel="${{ steps.shas.outputs.intel }}"
+
+ mkdir -p homebrew-tap/Casks
+ cat > homebrew-tap/Casks/codexel.rb <&2
exit 1
@@ -302,7 +330,8 @@ jobs:
shell: bash
run: |
set -euo pipefail
- version="${GITHUB_REF_NAME#codexel-v}"
+ version="${{ inputs.tag }}"
+ version="${version#codexel-v}"
prerelease="false"
if [[ "${version}" == *-* ]]; then
prerelease="true"
@@ -314,18 +343,63 @@ jobs:
uses: softprops/action-gh-release@v2
with:
name: ${{ steps.release_meta.outputs.version }}
- tag_name: ${{ github.ref_name }}
+ tag_name: ${{ inputs.tag }}
files: dist/**
prerelease: ${{ steps.release_meta.outputs.prerelease }}
generate_release_notes: true
+ - name: Checkout Homebrew tap
+ if: ${{ inputs.update_homebrew && secrets.HOMEBREW_TAP_GITHUB_TOKEN != '' }}
+ uses: actions/checkout@v6
+ with:
+ repository: ${{ env.HOMEBREW_TAP_REPO }}
+ token: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }}
+ path: homebrew-tap
+
+ - name: Update Homebrew cask
+ if: ${{ inputs.update_homebrew && secrets.HOMEBREW_TAP_GITHUB_TOKEN != '' }}
+ shell: bash
+ run: |
+ set -euo pipefail
+ version="${{ steps.release_meta.outputs.version }}"
+ sha_arm=$(sha256sum "dist/codexel-aarch64-apple-darwin.tar.gz" | cut -d' ' -f1)
+ sha_intel=$(sha256sum "dist/codexel-x86_64-apple-darwin.tar.gz" | cut -d' ' -f1)
+
+ mkdir -p homebrew-tap/Casks
+ cat > homebrew-tap/Casks/codexel.rb <
npm i -g @ixe1/codexel
- brew install --cask codexel
+ brew install --cask Ixe1/tap/codexel
or download from GitHub Releases
@@ -48,7 +48,7 @@ npm install -g @ixe1/codexel
Alternatively, if you use Homebrew:
```shell
-brew install --cask codexel
+brew install --cask Ixe1/tap/codexel
```
Then run `codexel`:
@@ -71,7 +71,7 @@ Each GitHub Release contains many executables, but in practice, you likely want
- x86_64: `codexel-x86_64-unknown-linux-musl.tar.gz`
- arm64: `codexel-aarch64-unknown-linux-musl.tar.gz`
-Each archive contains a single entry with the platform baked into the name (e.g., `codexel-x86_64-unknown-linux-musl`), so you likely want to rename it to `codexel` after extracting it.
+Each archive contains a single `codexel` binary.
diff --git a/codex-cli/scripts/verify-vendor.mjs b/codex-cli/scripts/verify-vendor.mjs
index 78c1d6e8601..05582ea39cc 100644
--- a/codex-cli/scripts/verify-vendor.mjs
+++ b/codex-cli/scripts/verify-vendor.mjs
@@ -10,6 +10,8 @@ const vendorRoot = path.join(packageRoot, "vendor");
const targets = [
"aarch64-unknown-linux-musl",
"x86_64-unknown-linux-musl",
+ "aarch64-apple-darwin",
+ "x86_64-apple-darwin",
"aarch64-pc-windows-msvc",
"x86_64-pc-windows-msvc",
];
diff --git a/codex-rs/README.md b/codex-rs/README.md
index c945b12b62a..44c5118c5c4 100644
--- a/codex-rs/README.md
+++ b/codex-rs/README.md
@@ -11,7 +11,7 @@ npm i -g @ixe1/codexel
codexel
```
-You can also install via Homebrew (`brew install --cask codexel`) or download a platform-specific release directly from [GitHub Releases](../../releases).
+You can also install via Homebrew (`brew install --cask Ixe1/tap/codexel`) or download a platform-specific release directly from [GitHub Releases](../../releases).
## Documentation quickstart
diff --git a/codex-rs/tui/src/updates.rs b/codex-rs/tui/src/updates.rs
index bfd9ae9edbe..b6137654fb8 100644
--- a/codex-rs/tui/src/updates.rs
+++ b/codex-rs/tui/src/updates.rs
@@ -64,7 +64,7 @@ const NPM_LATEST_URL: &str = "https://registry.npmjs.org/@ixe1%2Fcodexel/latest"
const NPM_SOURCE_KEY: &str = "npm:@ixe1/codexel";
// We use the latest version from the cask if installation is via homebrew - homebrew does not immediately pick up the latest release and can lag behind.
const HOMEBREW_CASK_URL: &str =
- "https://raw.githubusercontent.com/Homebrew/homebrew-cask/HEAD/Casks/c/codexel.rb";
+ "https://raw.githubusercontent.com/Ixe1/homebrew-tap/HEAD/Casks/codexel.rb";
const HOMEBREW_SOURCE_KEY: &str = "brew:codexel";
const LATEST_RELEASE_URL: &str = "https://api.github.com/repos/Ixe1/codexel/releases/latest";
const GITHUB_SOURCE_KEY: &str = "github:Ixe1/codexel";
@@ -221,7 +221,7 @@ fn extract_version_from_cask(cask_contents: &str) -> anyhow::Result {
fn extract_version_from_latest_tag(latest_tag_name: &str) -> anyhow::Result {
latest_tag_name
- .strip_prefix("rust-v")
+ .strip_prefix("codexel-v")
.map(str::to_owned)
.ok_or_else(|| anyhow::anyhow!("Failed to parse latest tag name '{latest_tag_name}'"))
}
@@ -295,7 +295,7 @@ mod tests {
#[test]
fn extracts_version_from_latest_tag() {
assert_eq!(
- extract_version_from_latest_tag("rust-v1.5.0").expect("failed to parse version"),
+ extract_version_from_latest_tag("codexel-v1.5.0").expect("failed to parse version"),
"1.5.0"
);
}
diff --git a/codex-rs/tui2/src/updates.rs b/codex-rs/tui2/src/updates.rs
index bfd9ae9edbe..b6137654fb8 100644
--- a/codex-rs/tui2/src/updates.rs
+++ b/codex-rs/tui2/src/updates.rs
@@ -64,7 +64,7 @@ const NPM_LATEST_URL: &str = "https://registry.npmjs.org/@ixe1%2Fcodexel/latest"
const NPM_SOURCE_KEY: &str = "npm:@ixe1/codexel";
// We use the latest version from the cask if installation is via homebrew - homebrew does not immediately pick up the latest release and can lag behind.
const HOMEBREW_CASK_URL: &str =
- "https://raw.githubusercontent.com/Homebrew/homebrew-cask/HEAD/Casks/c/codexel.rb";
+ "https://raw.githubusercontent.com/Ixe1/homebrew-tap/HEAD/Casks/codexel.rb";
const HOMEBREW_SOURCE_KEY: &str = "brew:codexel";
const LATEST_RELEASE_URL: &str = "https://api.github.com/repos/Ixe1/codexel/releases/latest";
const GITHUB_SOURCE_KEY: &str = "github:Ixe1/codexel";
@@ -221,7 +221,7 @@ fn extract_version_from_cask(cask_contents: &str) -> anyhow::Result {
fn extract_version_from_latest_tag(latest_tag_name: &str) -> anyhow::Result {
latest_tag_name
- .strip_prefix("rust-v")
+ .strip_prefix("codexel-v")
.map(str::to_owned)
.ok_or_else(|| anyhow::anyhow!("Failed to parse latest tag name '{latest_tag_name}'"))
}
@@ -295,7 +295,7 @@ mod tests {
#[test]
fn extracts_version_from_latest_tag() {
assert_eq!(
- extract_version_from_latest_tag("rust-v1.5.0").expect("failed to parse version"),
+ extract_version_from_latest_tag("codexel-v1.5.0").expect("failed to parse version"),
"1.5.0"
);
}
diff --git a/docs/faq.md b/docs/faq.md
index 6384e0cd3c5..c9d616649bb 100644
--- a/docs/faq.md
+++ b/docs/faq.md
@@ -45,11 +45,12 @@ Follow the quick setup in [Install & build](./install.md) and then jump into [Ge
### `brew upgrade codexel` isn't upgrading me
-If you're running Codexel v0.46.0 or older, `brew upgrade codexel` will not move you to the latest version because we migrated from a Homebrew formula to a cask. To upgrade, uninstall the existing oudated formula and then install the new cask:
+If Homebrew isn't picking up the latest version, ensure you're using the Codexel tap and reinstall the cask:
```bash
-brew uninstall --formula codexel
-brew install --cask codexel
+brew update
+brew uninstall --cask codexel
+brew install --cask Ixe1/tap/codexel
```
After reinstalling, `brew upgrade --cask codexel` will keep future releases up to date.
diff --git a/docs/releasing.md b/docs/releasing.md
index 5387052e9e5..df65eb6a9eb 100644
--- a/docs/releasing.md
+++ b/docs/releasing.md
@@ -10,6 +10,7 @@ Codexel is published as `@ixe1/codexel` with prebuilt native binaries bundled in
- Create and push a tag:
- Stable: `codexel-vX.Y.Z`
- Pre-release: `codexel-vX.Y.Z-alpha.N` or `codexel-vX.Y.Z-beta.N`
+- In GitHub Actions, run the `npm-publish-codexel` workflow and pass the tag you just pushed.
### What the workflow does
@@ -19,9 +20,19 @@ The `npm-publish-codexel` workflow:
- Assembles `codex-cli/vendor//codex/codexel(.exe)`.
- Packs an npm tarball and runs a smoke test (`codexel --help`).
- Creates a GitHub Release containing the per-target binaries and npm tarball.
-- Publishes `@ixe1/codexel` using npm Trusted Publishing (OIDC).
+- Publishes `@ixe1/codexel` using npm Trusted Publishing (OIDC) when `publish_npm` is enabled.
+- If `HOMEBREW_TAP_GITHUB_TOKEN` is set, updates the `codexel` cask in `Ixe1/homebrew-tap`.
+
+If you only want to publish/update Homebrew artifacts (no npm), run the `homebrew-cask-codexel` workflow manually with an existing `codexel-v*` tag.
### One-time setup
Before the first publish, configure npm Trusted Publishing for `@ixe1/codexel`
to trust this repository and the `npm-publish-codexel` workflow in the npm UI.
+
+If you want Homebrew installs (`brew install --cask Ixe1/tap/codexel`) to track releases:
+
+- Create the tap repository `Ixe1/homebrew-tap`.
+- Add a repository secret `HOMEBREW_TAP_GITHUB_TOKEN` (a GitHub token with permission to push to `Ixe1/homebrew-tap`).
+ - Recommended: a fine-grained Personal Access Token scoped to `Ixe1/homebrew-tap` with `Contents: Read and write`.
+ - Create it in GitHub: `Settings -> Developer settings -> Personal access tokens -> Fine-grained tokens`.
From 26b12d4bc7da1ebf3546ceb28152b41597abb589 Mon Sep 17 00:00:00 2001
From: Paul Lewis
Date: Sun, 21 Dec 2025 11:55:01 +0000
Subject: [PATCH 2/4] Fix YAML heredoc indentation
---
.github/workflows/homebrew-cask-codexel.yml | 36 ++++++++++-----------
1 file changed, 18 insertions(+), 18 deletions(-)
diff --git a/.github/workflows/homebrew-cask-codexel.yml b/.github/workflows/homebrew-cask-codexel.yml
index dcd0eb60a9e..6b23dfa188e 100644
--- a/.github/workflows/homebrew-cask-codexel.yml
+++ b/.github/workflows/homebrew-cask-codexel.yml
@@ -132,24 +132,24 @@ jobs:
mkdir -p homebrew-tap/Casks
cat > homebrew-tap/Casks/codexel.rb <
Date: Sun, 21 Dec 2025 11:57:26 +0000
Subject: [PATCH 3/4] Fix rust-ci job if expressions
---
.github/workflows/rust-ci.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml
index 1da23b5938f..1af0bf2f4ae 100644
--- a/.github/workflows/rust-ci.yml
+++ b/.github/workflows/rust-ci.yml
@@ -53,7 +53,7 @@ jobs:
name: Format / etc
runs-on: ubuntu-24.04
needs: changed
- if: ${{ (needs.changed.outputs.codex == 'true' || needs.changed.outputs.workflows == 'true' || github.event_name == 'push') && !(github.event_name == 'pull_request' && startsWith(matrix.runner, 'macos')) }}
+ if: ${{ needs.changed.outputs.codex == 'true' || needs.changed.outputs.workflows == 'true' || github.event_name == 'push' }}
defaults:
run:
working-directory: codex-rs
@@ -71,7 +71,7 @@ jobs:
name: cargo shear
runs-on: ubuntu-24.04
needs: changed
- if: ${{ (needs.changed.outputs.codex == 'true' || needs.changed.outputs.workflows == 'true' || github.event_name == 'push') && !(github.event_name == 'pull_request' && startsWith(matrix.runner, 'macos')) }}
+ if: ${{ needs.changed.outputs.codex == 'true' || needs.changed.outputs.workflows == 'true' || github.event_name == 'push' }}
defaults:
run:
working-directory: codex-rs
From 4819c91da4b2dc5da2a86a845de225b627e929ad Mon Sep 17 00:00:00 2001
From: Paul Lewis
Date: Sun, 21 Dec 2025 12:00:24 +0000
Subject: [PATCH 4/4] Fix workflows: avoid secrets in if
---
.github/workflows/homebrew-cask-codexel.yml | 19 +++++++++++++++----
.github/workflows/npm-publish-codexel.yml | 7 ++++---
2 files changed, 19 insertions(+), 7 deletions(-)
diff --git a/.github/workflows/homebrew-cask-codexel.yml b/.github/workflows/homebrew-cask-codexel.yml
index 6b23dfa188e..eb26bfc2456 100644
--- a/.github/workflows/homebrew-cask-codexel.yml
+++ b/.github/workflows/homebrew-cask-codexel.yml
@@ -69,6 +69,8 @@ jobs:
runs-on: ubuntu-latest
permissions:
contents: write
+ env:
+ TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }}
steps:
- name: Validate tag
shell: bash
@@ -112,16 +114,25 @@ jobs:
echo "arm=$sha_arm" >> "$GITHUB_OUTPUT"
echo "intel=$sha_intel" >> "$GITHUB_OUTPUT"
+ - name: Verify Homebrew tap token configured
+ shell: bash
+ run: |
+ set -euo pipefail
+ if [[ -z "${TAP_TOKEN}" ]]; then
+ echo "Missing HOMEBREW_TAP_GITHUB_TOKEN secret; cannot update Ixe1/homebrew-tap." >&2
+ exit 1
+ fi
+
- name: Checkout Homebrew tap
- if: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN != '' }}
+ if: ${{ env.TAP_TOKEN != '' }}
uses: actions/checkout@v6
with:
repository: Ixe1/homebrew-tap
- token: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }}
+ token: ${{ env.TAP_TOKEN }}
path: homebrew-tap
- name: Update cask
- if: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN != '' }}
+ if: ${{ env.TAP_TOKEN != '' }}
shell: bash
run: |
set -euo pipefail
@@ -152,7 +163,7 @@ jobs:
EOF
- name: Commit and push
- if: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN != '' }}
+ if: ${{ env.TAP_TOKEN != '' }}
working-directory: homebrew-tap
shell: bash
run: |
diff --git a/.github/workflows/npm-publish-codexel.yml b/.github/workflows/npm-publish-codexel.yml
index c04b348b0ff..cf4f84e4c5b 100644
--- a/.github/workflows/npm-publish-codexel.yml
+++ b/.github/workflows/npm-publish-codexel.yml
@@ -294,6 +294,7 @@ jobs:
contents: write
env:
HOMEBREW_TAP_REPO: Ixe1/homebrew-tap
+ TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }}
steps:
- name: Download build artifacts
uses: actions/download-artifact@v7
@@ -349,15 +350,15 @@ jobs:
generate_release_notes: true
- name: Checkout Homebrew tap
- if: ${{ inputs.update_homebrew && secrets.HOMEBREW_TAP_GITHUB_TOKEN != '' }}
+ if: ${{ inputs.update_homebrew && env.TAP_TOKEN != '' }}
uses: actions/checkout@v6
with:
repository: ${{ env.HOMEBREW_TAP_REPO }}
- token: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }}
+ token: ${{ env.TAP_TOKEN }}
path: homebrew-tap
- name: Update Homebrew cask
- if: ${{ inputs.update_homebrew && secrets.HOMEBREW_TAP_GITHUB_TOKEN != '' }}
+ if: ${{ inputs.update_homebrew && env.TAP_TOKEN != '' }}
shell: bash
run: |
set -euo pipefail