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
7 changes: 6 additions & 1 deletion .agents/skills/build-go-cobra-clis/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ cmd/
agent-tools/
main.go
github-issue-create/
.env.example
README.md
main.go
internal/
cli/
Expand All @@ -60,6 +62,8 @@ Rules:
- Let standalone binaries wrap the same command package used by the suite command.
- Keep reusable non-CLI behavior outside Cobra packages so it can be tested without CLI wiring.
- Use `pkg/` only when external import is intentional.
- Add `cmd/<binary>/README.md` for every exported binary with usage, required inputs, output formats, build, and package examples.
- Add `cmd/<binary>/.env.example` next to that README for every environment variable the binary reads. Leave secret values blank.

## Agent Contract

Expand All @@ -81,7 +85,8 @@ Design every command so an AI agent can call it predictably:
4. Use `RunE`, `Args`, `cmd.Context()`, and returned errors. Reserve `os.Exit` for `main.go`.
5. Add local flags by default. Use persistent flags only for concerns inherited by every child command.
6. Inject I/O with `cmd.SetOut`, `cmd.SetErr`, and constructor options. Avoid `fmt.Println` in command logic.
7. Verify with tests, a binary build, `--help`, and at least one realistic command invocation.
7. Update `cmd/<binary>/README.md` and `cmd/<binary>/.env.example` for the tool contract.
8. Verify with tests, a binary build, `--help`, and at least one realistic command invocation.

## Command Pattern

Expand Down
135 changes: 135 additions & 0 deletions .agents/skills/release-agent-binaries/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
---
name: release-agent-binaries
description: Version, package, tag, push, and verify standalone Go CLI binaries in the agent-cli-tools repo. Use when the user asks to release, deploy, publish, version, bump, package, or install one of the binaries under cmd/*, especially with per-binary SemVer tags such as slack-post-v0.1.0 and Makefile targets.
---

# Release Agent Binaries

## Overview

Release each binary independently from this repo. Use per-binary SemVer tags, not repo-wide `v*` tags:

```text
<tool>-v<major>.<minor>.<patch>
slack-post-v0.1.0
```

Use the Makefile as the source of truth for validation, version stamping, archive naming, tag creation, and tag push commands.

## Workflow

1. Identify the binary:

```bash
make list
test -d "cmd/<tool>"
```

If the requested tool is not listed, stop and tell the user which binaries are available.

2. Determine the version:

- If the user gives an exact version, require a leading `v`, for example `v0.1.0`.
- If the user asks for `major`, `minor`, or `patch`, inspect existing tags for that binary and calculate the next SemVer.
- If there is no existing tag for that binary, default the first release to `v0.1.0` unless the user requested a different version.

Useful commands:

```bash
git fetch --tags
git tag -l "<tool>-v*" --sort=-version:refname
make print-tag TOOL=<tool> VERSION=<version>
```

3. Confirm the release points at the intended code:

```bash
git status --short --branch
git log --oneline -5
```

Do not create a release tag on stale or unintended code. If there are uncommitted changes that should be part of the release, finish and verify them first. If unrelated local changes exist, leave them alone and explain the release boundary.

4. Verify before tagging:

```bash
make test
make build-tool TOOL=<tool> VERSION=<version>
dist/<tool> --version
make package TOOL=<tool> VERSION=<version> GOOS=linux GOARCH=amd64
tar -tzf dist/<tool>_<version>_linux_amd64.tar.gz
```

Expected archive contents: one executable named exactly `<tool>`.

5. Create and push the per-binary tag when the user has asked to release or deploy:

```bash
make tag TOOL=<tool> VERSION=<version>
make push-tag TOOL=<tool> VERSION=<version>
```

The pushed tag should trigger `.github/workflows/release.yml`, which builds that one binary for Linux and macOS on `amd64` and `arm64`.

6. Verify the GitHub release:

```bash
gh release view "<tool>-<version>" --json tagName,name,assets,url
gh run list --workflow release.yml --limit 5
```

Confirm the release contains these assets:

```text
<tool>_<version>_linux_amd64.tar.gz
<tool>_<version>_linux_arm64.tar.gz
<tool>_<version>_darwin_amd64.tar.gz
<tool>_<version>_darwin_arm64.tar.gz
```

## Makefile Commands

Use these targets instead of hand-written build/tag commands:

```bash
make help
make list
make test
make build
make build-tool TOOL=slack-post VERSION=v0.1.0
make package TOOL=slack-post VERSION=v0.1.0 GOOS=linux GOARCH=amd64
make release-archives TOOL=slack-post VERSION=v0.1.0
make print-tag TOOL=slack-post VERSION=v0.1.0
make tag TOOL=slack-post VERSION=v0.1.0
make push-tag TOOL=slack-post VERSION=v0.1.0
```

`make package` and `make release-archives` require valid SemVer with a leading `v`. Bad examples such as `0.1.0` should fail before any archive or tag is created.

## Docker Install Updates

When the user asks to update a Docker install command, use this private-release URL shape:

```dockerfile
ARG AGENT_CLI_TOOLS_VERSION=v0.1.0
ARG TARGETOS=linux
ARG TARGETARCH=amd64

RUN --mount=type=secret,id=github_token \
GITHUB_TOKEN="$(cat /run/secrets/github_token)" \
&& curl -fsSL -H "Authorization: Bearer ${GITHUB_TOKEN}" -L \
"https://github.com/berrydev-ai/agent-cli-tools/releases/download/slack-post-${AGENT_CLI_TOOLS_VERSION}/slack-post_${AGENT_CLI_TOOLS_VERSION}_${TARGETOS}_${TARGETARCH}.tar.gz" \
| tar -xz -C /usr/local/bin slack-post \
&& chmod +x /usr/local/bin/slack-post
```

Replace `slack-post` with the requested tool name.

## Response Checklist

When finished, report:

- Tool and version released.
- Tag name.
- Verification commands that passed.
- Release URL or the exact blocker if GitHub release publication failed.
4 changes: 4 additions & 0 deletions .agents/skills/release-agent-binaries/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
interface:
display_name: "Release Agent Binaries"
short_description: "Version and release repo CLI binaries"
default_prompt: "Use $release-agent-binaries to version and release the slack-post binary."
22 changes: 22 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: CI

on:
pull_request:
push:
branches:
- main

permissions:
contents: read

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
- run: go test ./...
- run: make build
112 changes: 112 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
name: Release

on:
push:
tags:
- "*-v*"
workflow_dispatch:
inputs:
tool:
description: "Binary to build from cmd/<tool>"
required: true
default: "slack-post"
version:
description: "SemVer to stamp into the binary, e.g. v0.1.0"
required: true
default: "v0.1.0"

permissions:
contents: read

jobs:
test:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
- run: go test ./...

release:
needs: test
runs-on: ubuntu-latest
permissions:
contents: read
strategy:
fail-fast: false
matrix:
goos:
- linux
- darwin
goarch:
- amd64
- arm64
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
- name: Resolve release target
shell: bash
env:
INPUT_TOOL: ${{ github.event.inputs.tool }}
INPUT_VERSION: ${{ github.event.inputs.version }}
run: |
set -euo pipefail
if [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then
tag="${GITHUB_REF_NAME}"
tool=""
version=""
for dir in cmd/*/; do
candidate="$(basename "${dir}")"
if [[ "${tag}" == "${candidate}-v"* ]]; then
tool="${candidate}"
version="${tag#${candidate}-}"
break
fi
done
if [[ -z "${tool}" || -z "${version}" ]]; then
echo "release tag must match a known cmd/<tool> binary, e.g. slack-post-v0.1.0" >&2
exit 1
fi
else
tool="${INPUT_TOOL}"
version="${INPUT_VERSION}"
fi
expected="$(make print-tag TOOL="${tool}" VERSION="${version}")"
if [[ "${GITHUB_REF_TYPE}" == "tag" && "${tag}" != "${expected}" ]]; then
echo "release tag ${tag} does not match normalized tag ${expected}" >&2
exit 1
fi
echo "TOOL=${tool}" >> "${GITHUB_ENV}"
echo "VERSION=${version}" >> "${GITHUB_ENV}"
- name: Build binaries
run: make package TOOL="${TOOL}" VERSION="${VERSION}" GOOS="${GOOS}" GOARCH="${GOARCH}"
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
- uses: actions/upload-artifact@v4
with:
name: binaries-${{ matrix.goos }}-${{ matrix.goarch }}
path: dist/*.tar.gz
Comment thread
coderberry marked this conversation as resolved.
if-no-files-found: error

publish:
needs: release
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/')
permissions:
contents: write
steps:
- uses: actions/download-artifact@v4
with:
path: dist
merge-multiple: true
- uses: softprops/action-gh-release@v2
with:
files: dist/*.tar.gz
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dist/
92 changes: 92 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
SHELL := /bin/bash

BINS := $(notdir $(patsubst %/,%,$(wildcard cmd/*/)))
DIST_DIR ?= dist
TOOL ?=
VERSION ?=
BUILD_VERSION := $(if $(VERSION),$(VERSION),dev)
GOOS ?= $(shell go env GOOS)
GOARCH ?= $(shell go env GOARCH)
RELEASE_OSES ?= linux darwin
RELEASE_ARCHES ?= amd64 arm64
TAG_NAME = $(TOOL)-$(VERSION)
ARCHIVE = $(DIST_DIR)/$(TOOL)_$(VERSION)_$(GOOS)_$(GOARCH).tar.gz
export TOOL
export VERSION

.PHONY: help list test build build-tool package release-archives print-tag tag push-tag clean check-tool check-version check-build-version

help:
@printf 'Targets:\n'
@printf ' make list\n'
@printf ' make test\n'
@printf ' make build\n'
@printf ' make build-tool TOOL=slack-post VERSION=v0.1.0\n'
@printf ' make package TOOL=slack-post VERSION=v0.1.0 GOOS=linux GOARCH=amd64\n'
@printf ' make release-archives TOOL=slack-post VERSION=v0.1.0\n'
@printf ' make print-tag TOOL=slack-post VERSION=v0.1.0\n'
@printf ' make tag TOOL=slack-post VERSION=v0.1.0\n'
@printf ' make push-tag TOOL=slack-post VERSION=v0.1.0\n'

list:
@printf '%s\n' $(BINS)

test:
go test ./...

build: check-build-version
@mkdir -p "$(DIST_DIR)"
@set -euo pipefail; \
for bin in $(BINS); do \
echo "building $$bin ($(BUILD_VERSION))"; \
go build -trimpath -ldflags "-s -w -X main.version=$(BUILD_VERSION)" -o "$(DIST_DIR)/$$bin" "./cmd/$$bin"; \
done

build-tool: check-tool check-build-version
@mkdir -p "$(DIST_DIR)"
go build -trimpath -ldflags "-s -w -X main.version=$(BUILD_VERSION)" -o "$(DIST_DIR)/$(TOOL)" "./cmd/$(TOOL)"

package: check-tool check-version
@mkdir -p "$(DIST_DIR)"
@set -euo pipefail; \
tmp_dir="$$(mktemp -d)"; \
trap 'rm -rf "$$tmp_dir"' EXIT; \
echo "packaging $(TOOL) $(VERSION) for $(GOOS)/$(GOARCH)"; \
GOOS="$(GOOS)" GOARCH="$(GOARCH)" CGO_ENABLED=0 \
go build -trimpath -ldflags "-s -w -X main.version=$(VERSION)" -o "$$tmp_dir/$(TOOL)" "./cmd/$(TOOL)"; \
tar -C "$$tmp_dir" -czf "$(ARCHIVE)" "$(TOOL)"; \
echo "$(ARCHIVE)"

release-archives: check-tool check-version
@set -euo pipefail; \
for os in $(RELEASE_OSES); do \
for arch in $(RELEASE_ARCHES); do \
$(MAKE) --no-print-directory package TOOL="$(TOOL)" VERSION="$(VERSION)" GOOS="$$os" GOARCH="$$arch"; \
done; \
done

print-tag: check-tool check-version
@echo "$(TAG_NAME)"

tag: check-tool check-version
@if git rev-parse -q --verify "refs/tags/$(TAG_NAME)" >/dev/null; then \
echo "tag $(TAG_NAME) already exists" >&2; \
exit 1; \
fi
git tag -a "$(TAG_NAME)" -m "Release $(TOOL) $(VERSION)"
@echo "created tag $(TAG_NAME)"

push-tag: check-tool check-version
git push origin "$(TAG_NAME)"

clean:
rm -rf "$(DIST_DIR)"

check-tool:
@scripts/validate-release-inputs.sh tool

check-version:
@scripts/validate-release-inputs.sh version

check-build-version:
@if [ -n "$${VERSION:-}" ]; then scripts/validate-release-inputs.sh version; fi
Loading
Loading