Files
agent-tools/README.md
T
unkin-agent 6a82b88947
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/test Pipeline failed
ci/woodpecker/pr/pre-commit Pipeline was successful
Add agentws worktree-management binary
agentws manages per-branch git worktrees for the unkin-agent user: it clones
repos into the source root (~/src/prodenv/<repo>) so branches are visible in
Ben's main checkout, and creates isolated worktrees under the worktree root
(~/.cache/agentws/<repo>__<branch>).

- New internal/agent/git.go: small, testable git helpers shelling out to the
  git binary (clone/fetch/worktree add/remove/list/prune, branch + config ops,
  porcelain parsing, path sanitizing). No go-git dependency.
- New cmd/agentws: new / list / rm / clean / token / credential subcommands.
  Auth uses an ephemeral git credential helper (agentws credential get) so the
  ~1h Gitea token is never persisted in a remote URL or config; per-worktree
  config keeps the shared checkout's identity untouched.
- Wire agentws into Makefile, scripts/build-rpm.sh, packaging/nfpm.yaml (binary
  + bash/zsh/fish completions), .woodpecker/release.yaml (cross-compile + assets)
  and .gitignore.
- Tests: table tests for parsing/sanitizing/dir-naming, a real temp-git repo for
  the worktree lifecycle, and hermetic cmd tests (bad input + credential-helper
  host guard) that never touch the network.
- Document agentws in README.md and AGENTS.md.
2026-08-15 12:21:08 +10:00

5.2 KiB

agent-tools

Small Gitea-automation CLIs, shipped together in one RPM (agent-tools). They act as the unkin-agent user by minting a scoped Gitea token from Vault, so automated PRs, comments and pushes are attributed to the agent — not to whoever happens to run the command.

  • agentpr — create pull requests and post PR comments as unkin-agent.
  • watchpr — poll one or more PRs and exit when one changes in a way worth acting on.
  • agentws — manage per-branch git worktrees for unkin-agent, cloning into Ben's source checkout and isolating agent work under the XDG cache.

How it gets a token

On first use each tool performs a Vault AppRole login (role_id only, no secret_id), then reads gitea/creds/unkin-agent to obtain a short-lived Gitea token, cached in-process for the run.

Everything is configured by environment variables, all with defaults:

Variable Default Purpose
VAULT_ADDR https://vault.service.consul:8200 Vault/OpenBao address
AGENT_APPROLE_ROLE_ID built-in default AppRole role_id (overridable)
GITEA_URL https://git.unkin.net Gitea base URL
AGENT_LOGIN unkin-agent login whose comments watchpr ignores
AGENTWS_SRC_ROOT ~/src/prodenv source-of-truth checkout root (agentws)
AGENTWS_ROOT ~/.cache/agentws worktree root (agentws)
AGENTWS_OWNER unkin Gitea org that owns the repos (agentws)

agentpr

# Verify identity (should print: unkin-agent)
agentpr whoami

# Open a PR
agentpr pr create --repo unkin/argocd-apps \
  --base main --head benvin/my-change \
  --title "Add woodpecker SA" --body "Adds the ServiceAccount ..."
# prints: #<number> <html_url>

# Comment on a PR
agentpr pr comment --repo unkin/argocd-apps --pr 42 --body "Rebased, CI green."

agentpr --version
agentpr --help

Non-zero exit on any API error.

watchpr

Poll PRs and exit (reporting what changed) when a tracked PR merges/closes, gets a new comment from someone other than the agent, its CI fails (failure/error), or it loses mergeability (a conflict appears). Benign transitions — CI pendingsuccess, the agent's own comments — are ignored.

# Watch until something meaningful happens (default interval 60s)
watchpr unkin/argocd-apps#42

# Multiple PRs, custom interval; refs accept #N or :N
watchpr --interval 30s unkin/argocd-apps#42 unkin/terraform-vault:98

# One-shot: print current state and exit 0 (great for scripts)
watchpr --once unkin/argocd-apps#42
watchpr --once --json unkin/argocd-apps#42

On a meaningful change watchpr prints the reason and the PR's current state, then exits 0. Use --json for machine-readable output.

agentws

agentws gives an agent an isolated git worktree per branch without disturbing Ben's shared checkouts. Repos are cloned into the source root (~/src/prodenv/<repo>) so branches created here are visible in the main checkout too; the worktrees themselves live under the worktree root (~/.cache/agentws/<repo>__<branch>).

# Clone unkin/argocd-apps into ~/src/prodenv if missing, then add a worktree for
# a new branch off the remote default branch. Prints the worktree path.
agentws new argocd-apps --branch benvin/my-change

# Branch off a specific base instead of the remote default
agentws new argocd-apps --branch benvin/hotfix --from release-1.2

# List managed worktrees (repo, branch, path)
agentws list

# Remove a worktree (by path or branch); refreshes the source repo afterwards
agentws rm benvin/my-change
agentws rm ~/.cache/agentws/argocd-apps__benvin-my-change --delete-branch

# Remove every managed worktree and prune each source repo
agentws clean

# Print a fresh unkin-agent Gitea token
agentws token

Auth / credential-helper design

Gitea tokens minted from Vault are short-lived (~1h), so agentws never persists one in a remote URL or in git config. Instead it wires itself as an ephemeral git credential helper:

  • agentws token prints a fresh token to stdout (handy for scripts).
  • agentws credential get speaks the git credential protocol on stdin and, for the configured Gitea host only, emits username=unkin-agent + password=<fresh token>.

agentws new sets this up per worktree without touching the shared checkout: it enables extensions.worktreeConfig on the repo once, then writes user.name / user.email and credential.helper = !<agentws> credential to the per-worktree config. Clone/fetch use the same helper via a transient -c credential.helper=...; the shared origin URL is left clean. On worktree removal agentws fetches in ~/src/prodenv/<repo> so its default branch stays current.

Build & package

make build                 # -> dist/agentpr, dist/watchpr, dist/agentws
make test                  # go test -race ./...
make rpm                   # build + package dist/agent-tools-<version>-1.x86_64.rpm

Release is tag-driven (v*) via Woodpecker: builds the RPM, PUTs it to the artifactapi rpm-internal yum repo, and cuts a Gitea release with cross-compiled binaries attached.

Version bump

make patch   # or: make minor / make major  — tags vX.Y.Z and pushes the tag