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
62 changes: 54 additions & 8 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ jobs:
- name: Build wheel and sdist
run: python -m build

# upload-artifact is still on v6; download-artifact moved to v7 for Node 24.
- name: Upload release artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: python-package-distributions
path: dist/
Expand All @@ -58,18 +59,57 @@ jobs:

steps:
- name: Download release artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
with:
name: python-package-distributions
path: dist/

- name: Check whether version already exists on PyPI
id: check-pypi-version
env:
# Keep this in sync with [project.name] in pyproject.toml.
PACKAGE_NAME: oacp-cli
PACKAGE_VERSION: ${{ github.ref_name }}
run: |
python - <<'PY'
import json
import os
import urllib.error
import urllib.request

package = os.environ["PACKAGE_NAME"]
version = os.environ["PACKAGE_VERSION"].removeprefix("v")
url = f"https://pypi.org/pypi/{package}/json"

try:
with urllib.request.urlopen(url, timeout=30) as response:
releases = json.load(response).get("releases", {})
except urllib.error.HTTPError as exc:
if exc.code == 404:
releases = {}
else:
raise

version_exists = version in releases
with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as fh:
fh.write(f"version_exists={'true' if version_exists else 'false'}\n")

if version_exists:
print(f"PyPI already has {package} {version}; skipping publish.")
else:
print(f"PyPI does not have {package} {version}; proceeding with publish.")
PY

- name: Publish to PyPI
if: steps.check-pypi-version.outputs.version_exists != 'true'
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0

- name: Skip duplicate PyPI publish
if: steps.check-pypi-version.outputs.version_exists == 'true'
run: echo "PyPI already has ${GITHUB_REF_NAME#v}; skipping upload."

github-release:
needs:
- build
- publish-pypi
needs: build
runs-on: ubuntu-latest
permissions:
contents: write
Expand All @@ -78,13 +118,19 @@ jobs:
- name: Check out repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

# download-artifact v7 is the current Node 24-compatible major.
- name: Download release artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
with:
name: python-package-distributions
path: dist/

- name: Create GitHub Release
- name: Create or update GitHub Release
env:
GH_TOKEN: ${{ github.token }}
run: gh release create "${GITHUB_REF_NAME}" dist/* --generate-notes --verify-tag
run: |
if gh release view "${GITHUB_REF_NAME}" >/dev/null 2>&1; then
gh release upload "${GITHUB_REF_NAME}" dist/* --clobber
else
gh release create "${GITHUB_REF_NAME}" dist/* --generate-notes --verify-tag
fi
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
.oacp
.agent-hub
.agents/
AGENTS.md
CLAUDE.md
GEMINI.md
.worktrees/
__pycache__/
*.pyc
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# OACP — Open Agent Coordination Protocol

[![PyPI](https://img.shields.io/pypi/v/oacp-cli)](https://pypi.org/project/oacp-cli/)
[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)
[![Claude Code](https://img.shields.io/badge/Runtime-Claude_Code-6B4FBB.svg)](https://claude.ai/code)
[![Codex](https://img.shields.io/badge/Runtime-Codex-74AA9C.svg)](https://openai.com/index/codex/)
[![PRs Welcome](https://img.shields.io/badge/PRs-Welcome-brightgreen)](https://github.com/kiloloop/oacp/pulls)

**Empowering solo founders to coordinate AI agents, with human-in-the-loop for control.**

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "oacp-cli"
version = "0.1.0rc1"
version = "0.1.0"
description = "Open Agent Coordination Protocol CLI for file-based multi-agent workflows"
readme = "README.md"
license = "Apache-2.0"
Expand Down
24 changes: 20 additions & 4 deletions tests/test_github_workflows.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,29 @@ def test_release_workflow_publishes_with_trusted_publishing() -> None:
publish_job = workflow["jobs"]["publish-pypi"]
assert publish_job["environment"]["name"] == "pypi"
assert publish_job["permissions"]["id-token"] == "write"
assert publish_job["steps"][0]["uses"].startswith("actions/download-artifact@")
assert publish_job["steps"][-1]["uses"].startswith("pypa/gh-action-pypi-publish@")
publish_step_names = {step["name"]: step for step in publish_job["steps"]}
assert publish_step_names["Download release artifacts"]["uses"].startswith(
"actions/download-artifact@"
)
assert publish_step_names["Check whether version already exists on PyPI"]["id"] == (
"check-pypi-version"
)
assert publish_step_names["Publish to PyPI"]["if"] == (
"steps.check-pypi-version.outputs.version_exists != 'true'"
)
assert publish_step_names["Publish to PyPI"]["uses"].startswith(
"pypa/gh-action-pypi-publish@"
)
assert publish_step_names["Skip duplicate PyPI publish"]["if"] == (
"steps.check-pypi-version.outputs.version_exists == 'true'"
)

release_job = workflow["jobs"]["github-release"]
assert set(release_job["needs"]) == {"build", "publish-pypi"}
assert release_job["needs"] == "build"
assert release_job["permissions"]["contents"] == "write"
release_step_names = {step["name"]: step for step in release_job["steps"]}
assert release_step_names["Check out repository"]["uses"].startswith("actions/checkout@")
assert release_step_names["Download release artifacts"]["uses"].startswith("actions/download-artifact@")
assert "gh release create" in release_step_names["Create GitHub Release"]["run"]
assert "gh release view" in release_step_names["Create or update GitHub Release"]["run"]
assert "gh release upload" in release_step_names["Create or update GitHub Release"]["run"]
assert "gh release create" in release_step_names["Create or update GitHub Release"]["run"]