Skip to content

Commit ea26716

Browse files
authored
Merge pull request #47 from jsburckhardt/feat/codex-cli
feat(codex): add Codex CLI feature and related tests
2 parents d20b336 + 99c8baa commit ea26716

7 files changed

Lines changed: 244 additions & 1 deletion

File tree

.github/workflows/test.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ jobs:
2525
- ruff
2626
- skopeo
2727
- uv
28+
- codex
2829
baseImage:
2930
- debian:latest
3031
- ubuntu:latest

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ This repository contains a _collection_ of Features.
2121
| jnv | https://github.com/ynqa/jnv | jnv is designed for navigating JSON, offering an interactive JSON viewer and jq filter editor. |
2222
| UV/UVX | https://docs.astral.sh/uv/ | An extremely fast Python package and project manager, written in Rust. A single tool to replace pip, pip-tools, pipx, poetry, pyenv, virtualenv, and more. |
2323
| Ruff | https://docs.astral.sh/ruff/ | An extremely fast Python linter and code formatter, written in Rust. |
24+
| Codex-cli | https://github.com/openai/codex | Codex CLI is an experimental project under active development. |
2425

2526

2627

@@ -244,3 +245,20 @@ Running `ruff` inside the built container will print the help menu of ruff.
244245
```bash
245246
ruff --version
246247
```
248+
249+
### `Codex-CLI`
250+
251+
Running `codex` inside the built container will print the help menu of codex.
252+
253+
```jsonc
254+
{
255+
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
256+
"features": {
257+
"ghcr.io/jsburckhardt/devcontainer-features/codex:1": {}
258+
}
259+
}
260+
```
261+
262+
```bash
263+
codex --version
264+
```
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"name": "codex",
3+
"id": "codex",
4+
"version": "1.0.0",
5+
"description": "A CLI for AI-assisted coding, using OpenAI models to help with programming tasks - chat-driven development with code execution capability. Always installs the latest version."
6+
}

src/codex/install.sh

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
#!/bin/bash
2+
3+
# Variables
4+
REPO_OWNER="openai"
5+
REPO_NAME="codex"
6+
BINARY_NAME="codex"
7+
8+
set -e
9+
10+
if [ "$(id -u)" -ne 0 ]; then
11+
echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
12+
exit 1
13+
fi
14+
15+
# Clean up
16+
rm -rf /var/lib/apt/lists/*
17+
18+
check_packages() {
19+
if ! dpkg -s "$@" >/dev/null 2>&1; then
20+
if [ "$(find /var/lib/apt/lists/* | wc -l)" = "0" ]; then
21+
echo "Running apt-get update..."
22+
apt-get update -y
23+
fi
24+
apt-get -y install --no-install-recommends "$@"
25+
fi
26+
}
27+
28+
# make sure we have packages
29+
check_packages curl jq ca-certificates zstd unzip file
30+
31+
# Function to get the most recent release (including pre-releases) from GitHub API
32+
get_latest_version() {
33+
RELEASES_URL="https://api.github.com/repos/$REPO_OWNER/$REPO_NAME/releases"
34+
echo "Fetching list of releases from $RELEASES_URL to determine the latest version..." >&2
35+
# Fetches all releases, takes the first one (most recent, including pre-releases),
36+
# and extracts its tag_name using jq.
37+
# Returns an empty string if no releases are found or tag_name is missing, leading to an error.
38+
VERSION_TAG=$(curl -s "$RELEASES_URL" | jq -r '.[0].tag_name // ""')
39+
40+
if [ -z "$VERSION_TAG" ]; then
41+
echo "Error: Could not automatically determine the latest version tag from $RELEASES_URL." >&2
42+
echo "Please check the releases page on GitHub and try again later." >&2
43+
exit 1
44+
fi
45+
echo "$VERSION_TAG"
46+
}
47+
48+
# Always install the latest version
49+
VERSION=$(get_latest_version)
50+
echo "Installing latest version: $VERSION"
51+
52+
# Determine the OS and architecture
53+
OS=$(uname | tr '[:upper:]' '[:lower:]')
54+
ARCH=$(uname -m)
55+
56+
if [ "$ARCH" == "x86_64" ]; then
57+
ARCH="x86_64"
58+
TARGET="x86_64-unknown-linux-musl"
59+
elif [ "$ARCH" == "i686" ]; then
60+
ARCH="i386"
61+
TARGET="i686-unknown-linux-musl"
62+
elif [ "$ARCH" == "armv7l" ]; then
63+
ARCH="armv7"
64+
TARGET="armv7-unknown-linux-gnueabihf"
65+
elif [ "$ARCH" == "aarch64" ]; then
66+
ARCH="arm64"
67+
TARGET="aarch64-unknown-linux-gnu"
68+
fi
69+
70+
# Create a temporary directory for the download
71+
TMP_DIR=$(mktemp -d)
72+
cd "$TMP_DIR" || exit
73+
74+
# First check what assets are available in the release
75+
RELEASE_URL="https://api.github.com/repos/$REPO_OWNER/$REPO_NAME/releases/tags/$VERSION"
76+
echo "Checking available assets for release: $VERSION"
77+
78+
ASSETS=$(curl -s "$RELEASE_URL" | jq -r '.assets[].name' 2>/dev/null || echo "")
79+
80+
# If we couldn't get the assets list or it's empty, try a common pattern
81+
if [ -z "$ASSETS" ]; then
82+
echo "Could not retrieve assets list, using common release patterns"
83+
84+
# Try common naming patterns for binary releases
85+
DOWNLOAD_URL="https://github.com/$REPO_OWNER/$REPO_NAME/releases/download/$VERSION/${BINARY_NAME}-${TARGET}.zst"
86+
87+
echo "Attempting to download from: $DOWNLOAD_URL"
88+
if ! curl -L --output "${BINARY_NAME}.zst" --fail "$DOWNLOAD_URL"; then
89+
# Try alternative formats if zst fails
90+
echo "Failed to download zst format, trying tar.gz..."
91+
DOWNLOAD_URL="https://github.com/$REPO_OWNER/$REPO_NAME/releases/download/$VERSION/${BINARY_NAME}-${OS}-${ARCH}.tar.gz"
92+
echo "Attempting to download from: $DOWNLOAD_URL"
93+
94+
if ! curl -L --output "${BINARY_NAME}.tar.gz" --fail "$DOWNLOAD_URL"; then
95+
echo "Failed to download tar.gz format, trying zip..."
96+
DOWNLOAD_URL="https://github.com/$REPO_OWNER/$REPO_NAME/releases/download/$VERSION/${BINARY_NAME}-${OS}-${ARCH}.zip"
97+
echo "Attempting to download from: $DOWNLOAD_URL"
98+
99+
if ! curl -L --output "${BINARY_NAME}.zip" --fail "$DOWNLOAD_URL"; then
100+
echo "Failed to download any known release format. Please check if the release exists and try again."
101+
exit 1
102+
else
103+
echo "Downloaded zip file successfully"
104+
unzip -q "${BINARY_NAME}.zip"
105+
fi
106+
else
107+
echo "Downloaded tar.gz file successfully"
108+
tar -xzf "${BINARY_NAME}.tar.gz"
109+
fi
110+
else
111+
echo "Downloaded zst file successfully"
112+
zstd -d "${BINARY_NAME}.zst" -o "$BINARY_NAME"
113+
fi
114+
else
115+
echo "Available assets:"
116+
echo "$ASSETS"
117+
118+
# Look for matching assets based on architecture and naming pattern
119+
# Try to match the most likely asset name for the binary
120+
ASSET_PATTERN="${BINARY_NAME}-*${TARGET}.zst"
121+
ZST_ASSET=$(echo "$ASSETS" | grep -E "^$ASSET_PATTERN$" | head -n 1 || echo "")
122+
123+
# Fallback: try to match any zst for the target
124+
if [ -z "$ZST_ASSET" ]; then
125+
ZST_ASSET=$(echo "$ASSETS" | grep -i "${TARGET}.zst" | head -n 1 || echo "")
126+
fi
127+
128+
TARGZ_ASSET=$(echo "$ASSETS" | grep -i "${OS}.*${ARCH}.*tar.gz" | head -n 1 || echo "")
129+
ZIP_ASSET=$(echo "$ASSETS" | grep -i "${OS}.*${ARCH}.*zip" | head -n 1 || echo "")
130+
131+
# Try to download in order of preference: zst, tar.gz, zip
132+
if [ -n "$ZST_ASSET" ]; then
133+
echo "Found zst asset: $ZST_ASSET"
134+
DOWNLOAD_URL="https://github.com/$REPO_OWNER/$REPO_NAME/releases/download/$VERSION/$ZST_ASSET"
135+
echo "Downloading from: $DOWNLOAD_URL"
136+
curl -L --output "$ZST_ASSET" --fail "$DOWNLOAD_URL"
137+
zstd -d "$ZST_ASSET" -o "$BINARY_NAME"
138+
elif [ -n "$TARGZ_ASSET" ]; then
139+
echo "Found tar.gz asset: $TARGZ_ASSET"
140+
DOWNLOAD_URL="https://github.com/$REPO_OWNER/$REPO_NAME/releases/download/$VERSION/$TARGZ_ASSET"
141+
echo "Downloading from: $DOWNLOAD_URL"
142+
curl -L --output "$TARGZ_ASSET" --fail "$DOWNLOAD_URL"
143+
tar -xzf "$TARGZ_ASSET"
144+
elif [ -n "$ZIP_ASSET" ]; then
145+
echo "Found zip asset: $ZIP_ASSET"
146+
DOWNLOAD_URL="https://github.com/$REPO_OWNER/$REPO_NAME/releases/download/$VERSION/$ZIP_ASSET"
147+
echo "Downloading from: $DOWNLOAD_URL"
148+
curl -L --output "$ZIP_ASSET" --fail "$DOWNLOAD_URL"
149+
unzip -q "$ZIP_ASSET"
150+
else
151+
echo "No suitable assets found for $OS/$ARCH"
152+
exit 1
153+
fi
154+
fi
155+
156+
# Find the binary in the extracted files if it's not in the current directory
157+
if [ ! -f "$BINARY_NAME" ]; then
158+
BINARY_PATH=$(find . -type f -executable -name "$BINARY_NAME" | head -n 1)
159+
if [ -z "$BINARY_PATH" ]; then
160+
echo "Could not find $BINARY_NAME binary in the extracted files"
161+
exit 1
162+
fi
163+
BINARY_NAME="$BINARY_PATH"
164+
fi
165+
166+
# Make the binary executable
167+
chmod +x "$BINARY_NAME"
168+
169+
# Move the binary to /usr/local/bin
170+
echo "Installing $BINARY_NAME"
171+
mv "$BINARY_NAME" /usr/local/bin/
172+
173+
# Cleanup
174+
cd - || exit
175+
rm -rf "$TMP_DIR"
176+
177+
# Verify installation
178+
echo "Verifying installation"
179+
if $BINARY_NAME --version; then
180+
echo "Installation verified successfully"
181+
else
182+
echo "Installation completed, but $BINARY_NAME --version failed. This may be expected for some versions."
183+
fi
184+
185+
# Create a wrapper script to set required environment variables
186+
echo "Creating wrapper script"
187+
cat > "/usr/local/bin/${BINARY_NAME}-wrapper" << EOF
188+
#!/bin/bash
189+
# Set required environment variables for codex
190+
export OPENAI_API_KEY=\${OPENAI_API_KEY:-}
191+
192+
# Execute the real binary with all arguments
193+
echo "Starting codex - remember to set your OPENAI_API_KEY environment variable"
194+
exec $BINARY_NAME "\$@"
195+
EOF
196+
197+
# Make the wrapper script executable
198+
chmod +x "/usr/local/bin/${BINARY_NAME}-wrapper"
199+
200+
# Create a symlink for convenience
201+
ln -sf "/usr/local/bin/${BINARY_NAME}-wrapper" "/usr/local/bin/${BINARY_NAME}-cli"
202+
203+
echo "$BINARY_NAME has been installed successfully."

test/_global/all-tools.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,6 @@ check "uv" uv --version
1414
check "ruff" ruff --version
1515
check "jnv" jnv -V
1616
check "zarf" zarf version
17+
check "codex" codex --version
1718

1819
reportResults

test/_global/scenarios.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
"ruff": {},
1515
"skopeo": {},
1616
"uv": {},
17-
"zarf": {}
17+
"zarf": {},
18+
"codex": {}
1819
}
1920
},
2021
"flux-specific-version": {

test/codex/test.sh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/bin/bash
2+
3+
set -e
4+
5+
# Optional: Import test library bundled with the devcontainer CLI
6+
# See https://github.com/devcontainers/cli/blob/HEAD/docs/features/test.md
7+
source dev-container-features-test-lib
8+
9+
# Feature-specific tests
10+
check "codex version" codex --version
11+
12+
# Report result
13+
reportResults

0 commit comments

Comments
 (0)