# agent-tools Small Gitea-automation CLIs, shipped together in one RPM (`agent-tools`). They act as an agent user (**`unkin-agent`** by default) 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. Set `AGENT_LOGIN` to act as a different agent identity. - **`agentpr`** — create pull requests and post PR comments as the agent user. - **`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. - **`agentvault`** — run deterministic Vault flows in one invocation, so agents never plumb secret material through a shell. ## 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/$AGENT_LOGIN` — or `GITEA_CREDS_PATH` when set — to obtain a short-lived Gitea token, cached in-process for the run. With neither variable set that is `gitea/creds/unkin-agent`, as before. 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` | agent identity: selects `gitea/creds/`, and the login whose comments `watchpr` ignores | | `GITEA_CREDS_PATH` | `gitea/creds/$AGENT_LOGIN` | Vault path minting the Gitea token (wins over `AGENT_LOGIN`) | | `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`) | | `AUTHENTIK_URL` | `https://identity.k8s.syd1.au.unkin.net` | Authentik base URL (`agentvault`) | ## agentpr ```bash # Verify identity (prints the agent login, unkin-agent by default) 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: # # 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 `pending`→`success`, the agent's own comments — are ignored. ```bash # 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/`) so branches created here are visible in the main checkout too; the worktrees themselves live under the **worktree root** (`~/.cache/agentws/__`). ```bash # 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 Gitea token for the agent login 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=$AGENT_LOGIN` + `password=`. `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 = ! 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/` so its default branch stays current. ## agentvault Deterministic Vault flows, each a single self-contained invocation: the tool reads and writes the secrets itself, and prints only identifiers. ### seed-outpost Copy an Authentik outpost's token into Vault KV-v2. `agentvault` reads the Authentik API token from `kv/service/authentik/agent-api-token`, resolves the named outpost's `token_identifier`, fetches its key via `/api/v3/core/tokens//view_key/` and writes it to the destination KV path. The token value is never printed or logged. ```bash agentvault seed-outpost \ --outpost k8s-outpost \ --dest-path kubernetes/namespace/authentik/default/outpost-token ``` ``` outpost: k8s-outpost token_identifier: ak-outpost-k8s-outpost dest: kv/kubernetes/namespace/authentik/default/outpost-token version: 3 ``` Re-running is safe: it writes a new KV version. Flags: `--outpost` and `--dest-path` are required; `--dest-key` (default `token`), `--kv-mount` (default `kv`), `--token-path` (default `service/authentik/agent-api-token`) and `--authentik-url` override the rest. Errors name the failing stage: AppRole login, KV read denied (policy not applied), outpost not found (terraform not applied), `view_key` failure, or KV write denied. ### seed-oauth Make a Vault KV-v2 path hold a complete oauth2-proxy credential set. It is a read-modify-write: `client_id` is set from the flag, `client_secret` and `cookie_secret` are generated (32 bytes from `crypto/rand`) only when missing, every other key on the path is written back untouched, and nothing is written at all when the secret is already correct. `cookie_secret` is base64url so it decodes to exactly the 32 bytes oauth2-proxy requires. ```bash agentvault seed-oauth \ --path kubernetes/namespace/repospawner/default/oauth-credentials \ --client-id 4f1c… ``` ``` path: kv/kubernetes/namespace/repospawner/default/oauth-credentials keys: client_id, client_secret, cookie_secret client_id: created client_secret: kept cookie_secret: created version: 4 ``` That is the common case: the provider's `client_secret` already lives on the path, so only the missing keys are added. A run with nothing to do prints `version: unchanged` and issues no write. Flags: `--path` and `--client-id` are required; `--kv-mount` (default `kv`) and `--rotate` override the rest. `--rotate` regenerates both secrets — only use it when the IdP provider's secret is being rotated alongside, since a rotated `client_secret` no longer matches the provider. Errors name the failing stage: AppRole login, KV read denied, or KV write denied. Only key names, actions and the KV version are printed. ## Build & package ```bash make build # -> dist/agentpr, dist/watchpr, dist/agentws, dist/agentvault make test # go test -race ./... make rpm # build + package dist/agent-tools--1.x86_64.rpm ``` Release is tag-driven (`v*`) via Woodpecker: builds the RPM, `PUT`s it to the artifactapi `rpm-internal` yum repo, and cuts a Gitea release with cross-compiled binaries attached. ### Version bump ```bash make patch # or: make minor / make major — tags vX.Y.Z and pushes the tag ```