name: ci
on:
push:
branches: [main]
tags: ['v*']
pull_request:
branches: [main]
permissions:
contents: read
env:
CARGO_TERM_COLOR: always
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
RUSTFLAGS: -D warnings
CARGO_PROFILE_DEV_DEBUG: 0
jobs:
fmt:
name: cargo fmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 with:
components: rustfmt
- run: cargo fmt --all -- --check
check:
name: cargo check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 - uses: Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae - run: sudo apt-get update && sudo apt-get install -y libdbus-1-dev pkg-config
- run: cargo check --locked --all-targets
clippy:
name: cargo clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 with:
components: clippy
- uses: Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae - run: sudo apt-get update && sudo apt-get install -y libdbus-1-dev pkg-config
- run: cargo clippy --locked --all-targets -- -D warnings
test:
name: cargo test (${{ matrix.target }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
target:
- x86_64-unknown-linux-gnu
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 with:
targets: ${{ matrix.target }}
- uses: Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae - run: sudo apt-get update && sudo apt-get install -y libdbus-1-dev pkg-config python3-gi dbus-daemon
- run: XDG_CACHE_HOME="$RUNNER_TEMP/cul-test-cache" cargo test --locked --target ${{ matrix.target }} --no-fail-fast
installer-shell:
name: install.sh regressions
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: bash -n install.sh scripts/install_sh_test.sh
- run: XDG_CACHE_HOME="$RUNNER_TEMP/cul-installer-cache" scripts/install_sh_test.sh
rustdoc-and-package:
name: rustdoc and cargo package
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 - uses: Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae - run: sudo apt-get update && sudo apt-get install -y libdbus-1-dev pkg-config
- run: RUSTDOCFLAGS="-D warnings" cargo doc --locked --no-deps --document-private-items
- run: cargo publish --dry-run --locked
supply-chain:
name: cargo audit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 - uses: Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae - run: cargo install cargo-audit --locked
- run: cargo audit --deny warnings
mcp-safety:
name: MCP safety contract
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 - uses: Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae with:
shared-key: host-dev
- name: Install MCP fixture dependencies
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
libdbus-1-dev pkg-config dbus-daemon python3-gi gir1.2-gtk-3.0 \
xvfb xdotool bubblewrap strace
- run: cargo build --locked
- run: scripts/mcp_safety_check.py --binary target/debug/computer-use-linux
- run: scripts/check_plugins.sh --binary target/debug/computer-use-linux
- name: Allow private test namespaces on the hosted runner
run: |
# Ubuntu's AppArmor policy can deny unprivileged Bubblewrap namespaces.
if [ -e /proc/sys/kernel/apparmor_restrict_unprivileged_userns ]; then
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
fi
- name: Test native X11 pointer input on private displays
timeout-minutes: 2
run: /usr/bin/python3 scripts/x11_pointer_test.py target/debug/computer-use-linux
agent-config:
name: agnix
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24
- run: npm install -g agnix
- run: agnix .
npm-wrapper:
name: npm wrapper
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 - uses: Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae with:
shared-key: host-dev
- uses: actions/setup-node@v4
with:
node-version: 24
- run: sudo apt-get update && sudo apt-get install -y libdbus-1-dev pkg-config
- name: Check Cargo and npm versions match
run: |
cargo_version="$(grep -m1 '^version = ' Cargo.toml | sed 's/.*"\(.*\)".*/\1/')"
npm_version="$(node -p "require('./package.json').version")"
test "$cargo_version" = "$npm_version"
- run: node --check npm/install.js
- run: node --check npm/bin/computer-use-linux.js
- run: python3 scripts/indicator_packaging_test.py
- run: cargo build --locked
- name: Test npm wrapper against local binary
run: |
prefix="$RUNNER_TEMP/npm-prefix"
COMPUTER_USE_LINUX_LOCAL_BINARY="$PWD/target/debug/computer-use-linux" \
COMPUTER_USE_LINUX_LOCAL_COSMIC_HELPER="$PWD/target/debug/computer-use-linux-cosmic" \
COMPUTER_USE_LINUX_LOCAL_INDICATOR="$PWD/target/debug/computer-use-linux-indicator" \
npm install --prefix "$prefix" -g .
"$prefix/bin/computer-use-linux" --help
- run: npm run pack:check
zod-schema:
name: zod MCP schema
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 - uses: Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae with:
shared-key: host-dev
- uses: actions/setup-node@v4
with:
node-version: 24
- run: sudo apt-get update && sudo apt-get install -y libdbus-1-dev pkg-config
- run: cargo build --locked
- name: Install MCP SDK for the zod check
run: npm ci --prefix scripts/zod-check --ignore-scripts --no-audit --no-fund
- name: Validate tools/list against the MCP SDK zod schema
run: node scripts/zod-check/check.mjs --command target/debug/computer-use-linux
pi-extension:
name: native Pi extension
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 - uses: Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae with:
shared-key: host-dev
- uses: actions/setup-node@v4
with:
node-version: 24
- run: sudo apt-get update && sudo apt-get install -y libdbus-1-dev pkg-config
- run: cargo build --locked
- run: npm ci --prefix pi --ignore-scripts --no-audit --no-fund
- run: npm run build:check --prefix pi
- run: npm run typecheck --prefix pi
- run: COMPUTER_USE_LINUX_TEST_BINARY="$PWD/target/debug/computer-use-linux" npm test --prefix pi
- run: python3 scripts/generate_pi_tool_catalog.py --binary target/debug/computer-use-linux --check
- name: Smoke the installed Pi package
run: |
prefix="$RUNNER_TEMP/pi-package"
package="$(npm pack --pack-destination "$RUNNER_TEMP" --silent)"
COMPUTER_USE_LINUX_LOCAL_BINARY="$PWD/target/debug/computer-use-linux" \
COMPUTER_USE_LINUX_LOCAL_COSMIC_HELPER="$PWD/target/debug/computer-use-linux-cosmic" \
COMPUTER_USE_LINUX_LOCAL_INDICATOR="$PWD/target/debug/computer-use-linux-indicator" \
npm install --prefix "$prefix" "$RUNNER_TEMP/$package" --no-audit --no-fund
test -x "$prefix/node_modules/@agent-sh/computer-use-linux/npm/bin/computer-use-linux-linux-x64"
COMPUTER_USE_LINUX_EXTENSION_PATH="$prefix/node_modules/@agent-sh/computer-use-linux/pi/extension/index.ts" \
COMPUTER_USE_LINUX_USE_PACKAGED_BINARY=1 \
npm test --prefix pi -- pi-integration.test.ts
release-binary:
name: release artifact (${{ matrix.target }})
needs:
- fmt
- check
- clippy
- test
- installer-shell
- rustdoc-and-package
- supply-chain
- mcp-safety
- agent-config
- npm-wrapper
- pi-extension
- zod-schema
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
permissions:
contents: write
strategy:
fail-fast: false
matrix:
include:
- target: x86_64-unknown-linux-gnu
asset: computer-use-linux-x86_64-unknown-linux-gnu
- target: aarch64-unknown-linux-gnu
asset: computer-use-linux-aarch64-unknown-linux-gnu
env:
GLIBC_FLOOR: "2.28"
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 with:
targets: ${{ matrix.target }}
- uses: Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae - name: Install host deps
run: sudo apt-get update && sudo apt-get install -y libdbus-1-dev pkg-config
- name: Install zig and cargo-zigbuild
run: |
python3 -m venv "$RUNNER_TEMP/zig"
"$RUNNER_TEMP/zig/bin/pip" install ziglang==0.17.0 cargo-zigbuild==0.24.0
echo "$RUNNER_TEMP/zig/bin" >> "$GITHUB_PATH"
- name: Build release binary
run: cargo zigbuild --locked --release --target ${{ matrix.target }}.${GLIBC_FLOOR}
- name: Check glibc floor
run: |
for bin in computer-use-linux computer-use-linux-cosmic computer-use-linux-indicator; do
path="target/${{ matrix.target }}/release/$bin"
max="$(objdump -T "$path" | grep -o 'GLIBC_[0-9.]*' | sed 's/GLIBC_//' | sort -uV | tail -n 1)"
echo "$bin needs glibc $max"
if [ -z "$max" ] || [ "$(printf '%s\n%s\n' "$max" "$GLIBC_FLOOR" | sort -V | tail -n 1)" != "$GLIBC_FLOOR" ]; then
echo "::error::$bin needs glibc $max, above the $GLIBC_FLOOR floor"
exit 1
fi
done
- name: Stage artifact
run: |
mkdir -p dist
cp target/${{ matrix.target }}/release/computer-use-linux dist/${{ matrix.asset }}
cp target/${{ matrix.target }}/release/computer-use-linux-cosmic dist/computer-use-linux-cosmic-${{ matrix.target }}
cp target/${{ matrix.target }}/release/computer-use-linux-indicator dist/computer-use-linux-indicator-${{ matrix.target }}
(
cd dist
sha256sum ${{ matrix.asset }} > ${{ matrix.asset }}.sha256
sha256sum computer-use-linux-cosmic-${{ matrix.target }} > computer-use-linux-cosmic-${{ matrix.target }}.sha256
sha256sum computer-use-linux-indicator-${{ matrix.target }} > computer-use-linux-indicator-${{ matrix.target }}.sha256
)
- uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 with:
files: |
dist/${{ matrix.asset }}
dist/${{ matrix.asset }}.sha256
dist/computer-use-linux-cosmic-${{ matrix.target }}
dist/computer-use-linux-cosmic-${{ matrix.target }}.sha256
dist/computer-use-linux-indicator-${{ matrix.target }}
dist/computer-use-linux-indicator-${{ matrix.target }}.sha256
generate_release_notes: true
publish-crate:
name: publish crate
needs: [release-binary]
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 - run: sudo apt-get update && sudo apt-get install -y libdbus-1-dev pkg-config
- name: Publish to crates.io
run: |
test -n "$CARGO_REGISTRY_TOKEN"
crate="$(grep -m1 '^name = ' Cargo.toml | sed 's/.*"\(.*\)".*/\1/')"
version="$(grep -m1 '^version = ' Cargo.toml | sed 's/.*"\(.*\)".*/\1/')"
test "v$version" = "$GITHUB_REF_NAME"
if curl -fsS "https://crates.io/api/v1/crates/${crate}/${version}" >/dev/null; then
echo "${crate} ${version} is already on crates.io; skipping"
else
cargo publish --locked
fi
publish-npm:
name: publish npm
needs: [release-binary]
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24
registry-url: https://registry.npmjs.org
- name: Publish to npm
run: |
test -n "$NODE_AUTH_TOKEN"
name="$(node -p "require('./package.json').name")"
version="$(node -p "require('./package.json').version")"
test "v$version" = "$GITHUB_REF_NAME"
base="https://github.com/agent-sh/computer-use-linux/releases/download/v${version}"
for asset in \
computer-use-linux-x86_64-unknown-linux-gnu \
computer-use-linux-aarch64-unknown-linux-gnu \
computer-use-linux-cosmic-x86_64-unknown-linux-gnu \
computer-use-linux-cosmic-aarch64-unknown-linux-gnu \
computer-use-linux-indicator-x86_64-unknown-linux-gnu \
computer-use-linux-indicator-aarch64-unknown-linux-gnu
do
for suffix in "" ".sha256"; do
for attempt in {1..30}; do
if curl -fsSIL "${base}/${asset}${suffix}" >/dev/null; then
break
fi
if [[ "$attempt" -eq 30 ]]; then
echo "release asset not available: ${asset}${suffix}" >&2
exit 1
fi
sleep 5
done
done
done
if npm view "${name}@${version}" version >/dev/null 2>&1; then
echo "${name} ${version} is already on npm; skipping"
else
npm publish --access public
fi
validate-published:
name: zod schema (published npm)
needs: [publish-npm]
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24
- run: sudo apt-get update && sudo apt-get install -y libdbus-1-dev pkg-config
- name: Install the published package
run: |
name="$(node -p "require('./package.json').name")"
version="$(node -p "require('./package.json').version")"
test "v$version" = "$GITHUB_REF_NAME"
# The registry can lag the publish job. Wait for the version to be
# visible, at most 10 tries 15s apart, then install once.
for attempt in 1 2 3 4 5 6 7 8 9 10; do
if npm view "${name}@${version}" version >/dev/null 2>&1; then
break
fi
if [ "$attempt" = 10 ]; then
echo "${name}@${version} is not visible on npm after 10 tries" >&2
exit 1
fi
echo "waiting for ${name}@${version} on npm (try ${attempt}/10)"
sleep 15
done
npm install --prefix "$RUNNER_TEMP/published" -g "${name}@${version}"
- name: Install MCP SDK for the zod check
run: npm ci --prefix scripts/zod-check --ignore-scripts --no-audit --no-fund
- name: Validate the published tools/list against the MCP SDK zod schema
run: node scripts/zod-check/check.mjs --command "$RUNNER_TEMP/published/bin/computer-use-linux"
notify-codex-desktop:
name: open codex-desktop update reminder
needs: [publish-crate, publish-npm]
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
steps:
- uses: actions/checkout@v4
- name: Open reminder issue to sync codex-desktop-linux
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ github.ref_name }}
run: |
title="Sync codex-desktop-linux with ${VERSION} release content"
existing="$(gh issue list --state all --search "in:title $title" --json title,number \
--jq ".[] | select(.title == \"$title\") | .number" | head -n1)"
if [ -n "$existing" ]; then
echo "reminder issue #$existing already exists; skipping"
exit 0
fi
notes="$(awk -v ver="${VERSION#v}" '
$0 ~ "^## \\[" ver "\\]" { capture = 1; next }
capture && /^## \[/ { exit }
capture { print }
' CHANGELOG.md)"
[ -z "$notes" ] && notes="(no CHANGELOG section found for ${VERSION})"
body="$(printf '%s released. Update [codex-desktop-linux](https://github.com/avifenesh/codex-desktop-linux) to pin/ship this version and mirror the relevant changes.\n\n## %s changelog\n%s\n\n---\n_Opened automatically by the release workflow._' "${VERSION}" "${VERSION}" "${notes}")"
gh issue create --title "$title" --body "$body"