Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,56 @@ jobs:
path: aether-agents.cdx.json
if-no-files-found: error
retention-days: 90

pypi-launcher:
name: PyPI launcher
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Set up Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '24'

- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
# The floor requires-python declares, so the published wheel is proved on the
# oldest interpreter it claims to support.
python-version: '3.10'

- name: Launcher version tracks package.json
run: node packages/sync-version.mjs --check

- name: Refuse runtime dependencies
working-directory: packages/pypi-cli
run: grep -qx 'dependencies = \[\]' pyproject.toml

- name: Unit tests
working-directory: packages/pypi-cli
env:
PYTHONPATH: src
run: python -m unittest discover -s tests -v

- name: Type check
working-directory: packages/pypi-cli
run: |
python -m pip install --disable-pip-version-check mypy==2.3.1
python -m mypy src

- name: Build and smoke-test the wheel
working-directory: packages/pypi-cli
run: |
set -Eeuo pipefail
python -m pip install --disable-pip-version-check build==1.4.0 twine==6.2.0
python -m build
python -m twine check --strict dist/*
python -m venv "$RUNNER_TEMP/launcher"
"$RUNNER_TEMP/launcher/bin/pip" install --quiet dist/*.whl
"$RUNNER_TEMP/launcher/bin/aether-agent" self --version
"$RUNNER_TEMP/launcher/bin/aether-agent" self --help > /dev/null
150 changes: 150 additions & 0 deletions .github/workflows/publish-pypi.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
name: Publish PyPI launcher

# Ships packages/pypi-cli to PyPI as `aether-agent`: the pip/pipx front door that installs
# and runs the npm CLI. Separate from release.yml (which publishes the npm package itself)
# -- own ecosystem, own artifact, own job -- but cut from the same immutable release tag, so
# `pipx install aether-agent` and `npm install -g aether-agents` are the same release.
#
# Authentication is PyPI Trusted Publishing (OIDC): no token, nothing to rotate or leak. The
# `pypi-production` environment scopes which GitHub identity PyPI will accept; its Trusted
# Publisher must be registered with owner AetherAI3, repo aether-agent, workflow
# publish-pypi.yml, and environment name "pypi-production" (Manage project -> Publishing on
# pypi.org).
#
# workflow_dispatch is there to rehearse and to repair: it defaults to a dry run, and it
# publishes only when dry_run is explicitly false.

on:
release:
types:
- published
workflow_dispatch:
inputs:
ref:
description: Release tag to build (for example v0.3.0)
required: true
type: string
dry_run:
description: Build and verify without publishing
required: false
default: true
type: boolean

permissions:
contents: read

concurrency:
group: pypi-production-${{ github.event.release.tag_name || inputs.ref }}
cancel-in-progress: false

jobs:
publish:
runs-on: ubuntu-latest
timeout-minutes: 20
environment: pypi-production
permissions:
contents: read
id-token: write # PyPI Trusted Publishing (OIDC)
env:
RELEASE_TAG: ${{ github.event.release.tag_name || inputs.ref }}
steps:
- name: Checkout immutable release tag
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ env.RELEASE_TAG }}
fetch-depth: 0
persist-credentials: false

- name: Verify release commit belongs to main
shell: bash
run: |
git fetch --no-tags origin main
git merge-base --is-ancestor HEAD origin/main

- name: Set up Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '24'

- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
# The floor declared by requires-python, so the published wheel is proved on the
# oldest interpreter it claims to support.
python-version: '3.10'

- name: Verify the launcher, the npm package, and the tag agree
shell: bash
run: |
set -Eeuo pipefail
node packages/sync-version.mjs --check
version="$(node -p "require('./package.json').version")"
test "$version" = "${RELEASE_TAG#v}"

- name: Refuse runtime dependencies
working-directory: packages/pypi-cli
run: grep -qx 'dependencies = \[\]' pyproject.toml

- name: Unit tests
working-directory: packages/pypi-cli
env:
PYTHONPATH: src
run: python -m unittest discover -s tests -v

- name: Type check
working-directory: packages/pypi-cli
run: |
python -m pip install --disable-pip-version-check mypy==2.3.1
python -m mypy src

- name: Build sdist and wheel
id: build
shell: bash
working-directory: packages/pypi-cli
run: |
set -Eeuo pipefail
python -m pip install --upgrade pip build==1.4.0 twine==6.2.0
python -m build
python -m twine check --strict dist/*
version="${RELEASE_TAG#v}"
printf 'version=%s\n' "$version" >> "$GITHUB_OUTPUT"
test -f "dist/aether_agent-${version}.tar.gz"
test -f "dist/aether_agent-${version}-py3-none-any.whl"
sha256sum dist/*

- name: Smoke-test the exact wheel
shell: bash
working-directory: packages/pypi-cli
run: |
set -Eeuo pipefail
python -m venv "$RUNNER_TEMP/launcher"
"$RUNNER_TEMP/launcher/bin/pip" install --quiet \
"dist/aether_agent-${{ steps.build.outputs.version }}-py3-none-any.whl"
test "$("$RUNNER_TEMP/launcher/bin/aether-agent" self --version)" \
= "${{ steps.build.outputs.version }}"
# doctor exits 1 until the npm CLI is present, which is the correct answer on a
# clean runner; what is being proved here is that the console script runs at all.
"$RUNNER_TEMP/launcher/bin/aether-agent" self doctor || true
"$RUNNER_TEMP/launcher/bin/aether-agent" self --help > /dev/null

- name: Upload release evidence
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: pypi-release-${{ env.RELEASE_TAG }}
path: packages/pypi-cli/dist/*
if-no-files-found: error
retention-days: 90

- name: Publish to PyPI
if: ${{ github.event_name == 'release' || !inputs.dry_run }}
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
with:
packages-dir: packages/pypi-cli/dist

- name: Dry run summary
if: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run }}
shell: bash
run: |
set -Eeuo pipefail
echo "Dry run only. Built aether-agent ${{ steps.build.outputs.version }}."
echo "Re-run with dry_run=false to publish."
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,9 @@ _tmp/
# packed npm tarballs (npm pack output) — the registry is the distribution
# channel, not the repo
*.tgz

# Python launcher (packages/pypi-cli)
__pycache__/
*.py[cod]
.mypy_cache/
.ruff_cache/
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

[![CI](https://github.com/AetherAI3/aether-agent/actions/workflows/ci.yml/badge.svg)](https://github.com/AetherAI3/aether-agent/actions/workflows/ci.yml)
[![npm](https://img.shields.io/npm/v/aether-agents?label=npm)](https://www.npmjs.com/package/aether-agents)
[![PyPI](https://img.shields.io/pypi/v/aether-agent?label=PyPI&color=3775a9)](https://pypi.org/project/aether-agent/)
[![Node 24+](https://img.shields.io/badge/node-24%2B-14b8a6)](https://nodejs.org/)
[![License](https://img.shields.io/badge/license-Apache--2.0-06b6d4)](LICENSE)

Expand All @@ -29,6 +30,10 @@ aether auth login
aether agent --test-cmd "npm test" "fix the failing test"
```

Prefer Python tooling? `pipx install aether-agent` installs the same CLI and forwards
every command to it, so `aether-agent code "..."` and `aether code "..."` do the same
work. See [`packages/pypi-cli`](packages/pypi-cli/README.md).

The third command gives Aether one task and one verification command. The local
host runs `npm test`; its real exit code determines whether the result is verified.

Expand Down Expand Up @@ -165,6 +170,7 @@ badge or `npm view aether-agents version` for the npm `latest` dist-tag, and
| Install | Version | What it represents |
|---|---:|---|
| npm `latest` | [![npm latest](https://img.shields.io/npm/v/aether-agents?label=&color=14b8a6)](https://www.npmjs.com/package/aether-agents) | Published package; the badge resolves the live dist-tag. |
| PyPI `aether-agent` | [![PyPI latest](https://img.shields.io/pypi/v/aether-agent?label=&color=3775a9)](https://pypi.org/project/aether-agent/) | Launcher that installs and runs the npm CLI; its version is the agent version it installs. |
| `main` source build | **0.3.0** | Current repository source and its 0.3 workflow. |

The [release record](docs/releases/2026-08-22.md),
Expand Down
Loading