feat: set up code fork with rebranding and npm publishing

- Rename binaries from codex to code across all Cargo.toml files
- Create @just-every/code npm package with generic build scripts
- Add CODE_HOME environment variable support (falls back to CODEX_HOME)
- Create GitHub Actions workflow for automated npm releases
- Update JavaScript wrapper from codex.js to code.js
- Add FORK_SETUP.md documentation
- Configure dual-branch strategy for fork management
This commit is contained in:
James Peter
2025-08-10 15:57:12 +10:00
parent a0faa3a9ea
commit ff8378b2af
12 changed files with 496 additions and 26 deletions
+205
View File
@@ -0,0 +1,205 @@
name: Release
on:
push:
branches: [ code-release ]
paths-ignore:
- '.github/workflows/test.yml'
- 'examples/**'
- '**/*.test.ts'
- 'test/**'
- '*.md'
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
build-binaries:
name: Build ${{ matrix.target }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-musl
artifact: code-x86_64-unknown-linux-musl
- os: ubuntu-latest
target: aarch64-unknown-linux-musl
artifact: code-aarch64-unknown-linux-musl
- os: macos-latest
target: x86_64-apple-darwin
artifact: code-x86_64-apple-darwin
- os: macos-latest
target: aarch64-apple-darwin
artifact: code-aarch64-apple-darwin
- os: windows-latest
target: x86_64-pc-windows-msvc
artifact: code-x86_64-pc-windows-msvc.exe
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Install musl tools (Linux)
if: contains(matrix.target, 'musl')
run: |
sudo apt-get update
sudo apt-get install -y musl-tools
if [[ "${{ matrix.target }}" == "aarch64-unknown-linux-musl" ]]; then
sudo apt-get install -y gcc-aarch64-linux-gnu
fi
- name: Build binaries
run: |
cd codex-rs
cargo build --release --target ${{ matrix.target }} --bin code --bin code-tui --bin code-exec
env:
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER: aarch64-linux-gnu-gcc
- name: Prepare artifacts
run: |
mkdir -p artifacts
if [[ "${{ matrix.os }}" == "windows-latest" ]]; then
cp codex-rs/target/${{ matrix.target }}/release/code.exe artifacts/${{ matrix.artifact }}
cp codex-rs/target/${{ matrix.target }}/release/code-tui.exe artifacts/code-tui-${{ matrix.target }}.exe
cp codex-rs/target/${{ matrix.target }}/release/code-exec.exe artifacts/code-exec-${{ matrix.target }}.exe
else
cp codex-rs/target/${{ matrix.target }}/release/code artifacts/${{ matrix.artifact }}
cp codex-rs/target/${{ matrix.target }}/release/code-tui artifacts/code-tui-${{ matrix.target }}
cp codex-rs/target/${{ matrix.target }}/release/code-exec artifacts/code-exec-${{ matrix.target }}
fi
shell: bash
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: binaries-${{ matrix.target }}
path: artifacts/
release:
name: Publish to npm
needs: build-binaries
runs-on: ubuntu-latest
timeout-minutes: 30
# Skip if commit message contains [skip ci]
if: "!contains(github.event.head_commit.message, '[skip ci]')"
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GH_PAT || secrets.GITHUB_TOKEN }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts/
- name: Prepare binaries for npm
run: |
# Create bin directory in codex-cli
mkdir -p codex-cli/bin
# Copy all binaries to the bin directory
for dir in artifacts/binaries-*; do
cp $dir/* codex-cli/bin/
done
# Make binaries executable
chmod +x codex-cli/bin/*
# List binaries for verification
ls -la codex-cli/bin/
- name: Bump version
id: version
working-directory: codex-cli
run: |
# Configure git
git config --local user.email "[email protected]"
git config --local user.name "GitHub Action"
# Get current version
CURRENT_VERSION=$(node -p "require('./package.json').version")
echo "Current version: $CURRENT_VERSION"
# Bump version
npm version patch --no-git-tag-version
NEW_VERSION=$(node -p "require('./package.json').version")
echo "New version: $NEW_VERSION"
echo "version=${NEW_VERSION}" >> $GITHUB_OUTPUT
# Commit changes
git add package.json
if git diff --staged --quiet; then
echo "No changes to commit"
echo "skip_push=true" >> $GITHUB_OUTPUT
else
git commit -m "chore(release): ${NEW_VERSION} [skip ci]"
git tag "v${NEW_VERSION}"
echo "skip_push=false" >> $GITHUB_OUTPUT
fi
- name: Check if we should publish
id: should_publish
run: |
if [ -z "${{ secrets.NPM_TOKEN }}" ]; then
echo "NPM_TOKEN not set, skipping publish"
echo "publish=false" >> $GITHUB_OUTPUT
else
echo "NPM_TOKEN is set, will publish"
echo "publish=true" >> $GITHUB_OUTPUT
fi
- name: Publish to npm
working-directory: codex-cli
run: npm publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
if: steps.should_publish.outputs.publish == 'true'
- name: Push changes
run: |
git push origin code-release
git push origin --tags
if: steps.version.outputs.skip_push != 'true'
- name: Create GitHub Release
uses: softprops/action-gh-release@v1
if: steps.version.outputs.skip_push != 'true'
with:
tag_name: v${{ steps.version.outputs.version }}
name: Release v${{ steps.version.outputs.version }}
body: |
## @just-every/code v${{ steps.version.outputs.version }}
Fork of OpenAI Codex with additional features:
- Enhanced image handling (drag-and-drop, paste, file path detection)
- /reasoning slash command for dynamic reasoning effort
### Installation
```bash
npm install -g @just-every/code
```
### Usage
```bash
code
```
files: |
artifacts/binaries-*/*
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+115
View File
@@ -0,0 +1,115 @@
# Setting up "code" - A Fork of OpenAI Codex
## Overview
This document outlines the changes needed to rebrand and publish this fork of OpenAI Codex as "code" under the @just-every npm scope.
## Required Changes
### 1. Binary Renaming
- [ ] Rename binary from `codex` to `code` in:
- `codex-rs/cli/Cargo.toml` - Change `[[bin]] name = "codex"` to `name = "code"`
- `codex-rs/tui/Cargo.toml` - Change binary name to `code-tui`
- `codex-rs/exec/Cargo.toml` - Change binary name to `code-exec`
### 2. Package Configuration
- [ ] Update `codex-cli/package.json`:
- Change name from `@openai/codex` to `@just-every/code`
- Update repository URL to `https://github.com/just-every/code.git`
- Update bin field from `"codex": "bin/codex.js"` to `"code": "bin/code.js"`
- Set initial version (e.g., "0.1.0")
- [ ] Rename `codex-cli/bin/codex.js` to `codex-cli/bin/code.js`
- [ ] Update the shebang and binary detection logic in `code.js`
### 3. GitHub Actions Updates
- [ ] Modify `.github/workflows/rust-release.yml`:
- Update artifact names from `codex-*` to `code-*`
- Change npm package staging to use new name
- Add npm publish step after GitHub release
- [ ] Create `.github/workflows/npm-publish.yml` for automated npm publishing:
```yaml
name: Publish to npm
on:
release:
types: [published]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'
- name: Download release assets
# Download the npm package from release
- name: Publish to npm
run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
```
### 4. Rust Code Updates
- [ ] Update all references to "codex" in:
- Error messages
- Help text
- Documentation strings
- Environment variables (e.g., `CODEX_HOME` → `CODE_HOME`)
### 5. Configuration Updates
- [ ] Update config paths from `~/.codex/` to `~/.code/`
- [ ] Update environment variable names
- [ ] Update default config values
### 6. Build Scripts
- [ ] Update build scripts to produce `code` binaries
- [ ] Update install scripts to use new binary names
- [ ] Update the staging script at `codex-cli/scripts/stage_rust_release.py`
### 7. Documentation
- [ ] Update README.md with new project name and installation instructions
- [ ] Update all documentation to reference `code` instead of `codex`
- [ ] Add attribution to original OpenAI Codex project
## GitHub Secrets Required
Add these secrets to your GitHub repository:
- `NPM_TOKEN`: Your npm authentication token for publishing
## Release Process
1. Update version in `codex-rs/Cargo.toml`
2. Update version in `codex-cli/package.json`
3. Commit changes
4. Create and push a tag: `git tag -a v0.1.0 -m "Release 0.1.0"`
5. Push tag: `git push origin v0.1.0`
6. GitHub Actions will automatically:
- Build binaries for all platforms
- Create GitHub release
- Publish to npm
## Testing Locally
```bash
# Build the Rust binaries
cd codex-rs
cargo build --release --bin code
# Test the CLI
./target/release/code --version
# Test npm package locally
cd ../codex-cli
npm link
code --version
```
## npm Publishing Setup
1. Create npm account if needed
2. Create organization @just-every on npm
3. Generate npm access token (automation type)
4. Add token as GitHub secret NPM_TOKEN
## Maintaining the Fork
- Regularly sync with upstream OpenAI Codex for bug fixes
- Keep a clear changelog of fork-specific features
- Maintain compatibility where possible
+123
View File
@@ -0,0 +1,123 @@
#!/usr/bin/env node
// Unified entry point for the Code CLI (fork of OpenAI Codex).
import path from "path";
import { fileURLToPath } from "url";
// __dirname equivalent in ESM
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const { platform, arch } = process;
let targetTriple = null;
switch (platform) {
case "linux":
case "android":
switch (arch) {
case "x64":
targetTriple = "x86_64-unknown-linux-musl";
break;
case "arm64":
targetTriple = "aarch64-unknown-linux-musl";
break;
default:
break;
}
break;
case "darwin":
switch (arch) {
case "x64":
targetTriple = "x86_64-apple-darwin";
break;
case "arm64":
targetTriple = "aarch64-apple-darwin";
break;
default:
break;
}
break;
case "win32":
switch (arch) {
case "x64":
targetTriple = "x86_64-pc-windows-msvc.exe";
break;
case "arm64":
// We do not build this today, fall through...
default:
break;
}
break;
default:
break;
}
if (!targetTriple) {
throw new Error(`Unsupported platform: ${platform} (${arch})`);
}
const binaryPath = path.join(__dirname, "..", "bin", `code-${targetTriple}`);
// Use an asynchronous spawn instead of spawnSync so that Node is able to
// respond to signals (e.g. Ctrl-C / SIGINT) while the native binary is
// executing. This allows us to forward those signals to the child process
// and guarantees that when either the child terminates or the parent
// receives a fatal signal, both processes exit in a predictable manner.
const { spawn } = await import("child_process");
const child = spawn(binaryPath, process.argv.slice(2), {
stdio: "inherit",
env: { ...process.env, CODE_MANAGED_BY_NPM: "1", CODEX_MANAGED_BY_NPM: "1" },
});
child.on("error", (err) => {
// Typically triggered when the binary is missing or not executable.
// Re-throwing here will terminate the parent with a non-zero exit code
// while still printing a helpful stack trace.
// eslint-disable-next-line no-console
console.error(err);
process.exit(1);
});
// Forward common termination signals to the child so that it shuts down
// gracefully. In the handler we temporarily disable the default behavior of
// exiting immediately; once the child has been signaled we simply wait for
// its exit event which will in turn terminate the parent (see below).
const forwardSignal = (signal) => {
if (child.killed) {
return;
}
try {
child.kill(signal);
} catch {
/* ignore */
}
};
["SIGINT", "SIGTERM", "SIGHUP"].forEach((sig) => {
process.on(sig, () => forwardSignal(sig));
});
// When the child exits, mirror its termination reason in the parent so that
// shell scripts and other tooling observe the correct exit status.
// Wrap the lifetime of the child process in a Promise so that we can await
// its termination in a structured way. The Promise resolves with an object
// describing how the child exited: either via exit code or due to a signal.
const childResult = await new Promise((resolve) => {
child.on("exit", (code, signal) => {
if (signal) {
resolve({ type: "signal", signal });
} else {
resolve({ type: "code", exitCode: code ?? 1 });
}
});
});
if (childResult.type === "signal") {
// Re-emit the same signal so that the parent terminates with the expected
// semantics (this also sets the correct exit code of 128 + n).
process.kill(process.pid, childResult.signal);
} else {
process.exit(childResult.exitCode);
}
+8 -4
View File
@@ -1,9 +1,10 @@
{
"name": "@openai/codex",
"version": "0.0.0-dev",
"name": "@just-every/code",
"version": "0.1.0",
"license": "Apache-2.0",
"description": "Lightweight coding agent that runs in your terminal - fork of OpenAI Codex",
"bin": {
"codex": "bin/codex.js"
"code": "bin/code.js"
},
"type": "module",
"engines": {
@@ -15,6 +16,9 @@
],
"repository": {
"type": "git",
"url": "git+https://github.com/openai/codex.git"
"url": "git+https://github.com/just-every/code.git"
},
"publishConfig": {
"access": "public"
}
}
+1 -1
View File
@@ -4,7 +4,7 @@ name = "codex-cli"
version = { workspace = true }
[[bin]]
name = "codex"
name = "code"
path = "src/main.rs"
[lib]
+25 -10
View File
@@ -728,30 +728,45 @@ fn default_model() -> String {
OPENAI_DEFAULT_MODEL.to_string()
}
/// Returns the path to the Codex configuration directory, which can be
/// specified by the `CODEX_HOME` environment variable. If not set, defaults to
/// `~/.codex`.
/// Returns the path to the Code/Codex configuration directory, which can be
/// specified by the `CODE_HOME` or `CODEX_HOME` environment variables. If not set,
/// defaults to `~/.code` (falling back to `~/.codex` if it exists for compatibility).
///
/// - If `CODEX_HOME` is set, the value will be canonicalized and this
/// - If `CODE_HOME` or `CODEX_HOME` is set, the value will be canonicalized and this
/// function will Err if the path does not exist.
/// - If `CODEX_HOME` is not set, this function does not verify that the
/// directory exists.
/// - If neither is set, this function does not verify that the directory exists.
pub fn find_codex_home() -> std::io::Result<PathBuf> {
// Honor the `CODEX_HOME` environment variable when it is set to allow users
// (and tests) to override the default location.
// First check CODE_HOME for the fork
if let Ok(val) = std::env::var("CODE_HOME") {
if !val.is_empty() {
return PathBuf::from(val).canonicalize();
}
}
// Fall back to CODEX_HOME for compatibility
if let Ok(val) = std::env::var("CODEX_HOME") {
if !val.is_empty() {
return PathBuf::from(val).canonicalize();
}
}
let mut p = home_dir().ok_or_else(|| {
let home = home_dir().ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::NotFound,
"Could not find home directory",
)
})?;
p.push(".codex");
// Check if ~/.codex exists for backward compatibility
let mut codex_path = home.clone();
codex_path.push(".codex");
if codex_path.exists() {
return Ok(codex_path);
}
// Otherwise use ~/.code for the fork
let mut p = home;
p.push(".code");
Ok(p)
}
+1 -1
View File
@@ -4,7 +4,7 @@ name = "codex-exec"
version = { workspace = true }
[[bin]]
name = "codex-exec"
name = "code-exec"
path = "src/main.rs"
[lib]
+4 -2
View File
@@ -312,7 +312,8 @@ pub fn spawn_login_with_chatgpt(codex_home: &Path) -> std::io::Result<SpawnedLog
let script_path = write_login_script_to_disk()?;
let mut cmd = std::process::Command::new("python3");
cmd.arg(&script_path)
.env("CODEX_HOME", codex_home)
.env("CODE_HOME", codex_home)
.env("CODEX_HOME", codex_home) // Keep for compatibility
.env("CODEX_CLIENT_ID", CLIENT_ID)
.stdin(Stdio::null())
.stdout(Stdio::piped())
@@ -350,7 +351,8 @@ pub async fn login_with_chatgpt(codex_home: &Path, capture_output: bool) -> std:
let script_path = write_login_script_to_disk()?;
let child = Command::new("python3")
.arg(&script_path)
.env("CODEX_HOME", codex_home)
.env("CODE_HOME", codex_home)
.env("CODEX_HOME", codex_home) // Keep for compatibility
.env("CODEX_CLIENT_ID", CLIENT_ID)
.stdin(Stdio::null())
.stdout(if capture_output {
+6 -5
View File
@@ -3,7 +3,7 @@
- Listens on 127.0.0.1:1455
- Opens http://localhost:1455/auth/callback in the browser
- If the user successfully navigates the auth flow,
$CODEX_HOME/auth.json will be written with the API key.
$CODE_HOME/auth.json (or $CODEX_HOME/auth.json) will be written with the API key.
- User will be redirected to http://localhost:1455/success upon success.
The script should exit with a non-zero code if the user fails to navigate the
@@ -12,8 +12,8 @@ auth flow.
To test this script locally without overwriting your existing auth.json file:
```
rm -rf /tmp/codex_home && mkdir /tmp/codex_home
CODEX_HOME=/tmp/codex_home python3 codex-rs/login/src/login_with_chatgpt.py
rm -rf /tmp/code_home && mkdir /tmp/code_home
CODE_HOME=/tmp/code_home python3 codex-rs/login/src/login_with_chatgpt.py
```
"""
@@ -81,9 +81,10 @@ def main() -> None:
parser.add_argument("--verbose", action="store_true", help="Enable request logging")
args = parser.parse_args()
codex_home = os.environ.get("CODEX_HOME")
# Check CODE_HOME first, fall back to CODEX_HOME for compatibility
codex_home = os.environ.get("CODE_HOME") or os.environ.get("CODEX_HOME")
if not codex_home:
eprint("ERROR: CODEX_HOME environment variable is not set")
eprint("ERROR: CODE_HOME or CODEX_HOME environment variable is not set")
sys.exit(1)
client_id = os.getenv("CODEX_CLIENT_ID")
+1 -1
View File
@@ -4,7 +4,7 @@ name = "codex-tui"
version = { workspace = true }
[[bin]]
name = "codex-tui"
name = "code-tui"
path = "src/main.rs"
[lib]
+1
View File
@@ -11,6 +11,7 @@ use crate::slash_command::SlashCommand;
use crate::tui;
use codex_core::config::Config;
use codex_core::protocol::Event;
use codex_core::protocol::EventMsg;
use codex_core::protocol::Op;
use color_eyre::eyre::Result;
use crossterm::SynchronizedUpdate;
+6 -2
View File
@@ -1,10 +1,14 @@
{
"name": "codex-monorepo",
"name": "code-monorepo",
"private": true,
"description": "Tools for repo-wide maintenance.",
"scripts": {
"format": "prettier --check *.json *.md .github/workflows/*.yml",
"format:fix": "prettier --write *.json *.md .github/workflows/*.yml"
"format:fix": "prettier --write *.json *.md .github/workflows/*.yml",
"build": "cd codex-rs && cargo build --release --bin code --bin code-tui --bin code-exec",
"build:dev": "cd codex-rs && cargo build --bin code --bin code-tui --bin code-exec",
"build:quick": "cd codex-rs && cargo build --release --bin code",
"test:local": "cd codex-cli && npm link && code --version"
},
"devDependencies": {
"prettier": "^3.5.3"