Make changes, run make check, fix what it catches, repeat until green, then
push. make check runs fmt-check + vet + lint + tidy-check + check-surface +
check-skill-drift + test. Treat it as your inner-loop companion, not a final
hurdle.
bunny-cli/
├── cmd/ # Command implementations (one file per resource group)
├── internal/
│ ├── client/ # bunny.net API client wrapper, retry logic, error handling
│ ├── config/ # TOML configuration (api_key)
│ ├── output/ # Output formatting (table, JSON, jq filtering, fields)
│ ├── pagination/ # Generic paginator (Collect[T] with limit/all modes)
│ └── surface/ # CLI surface snapshot generator
│ └── cmd/gensurface/ # Executable that produces .surface
├── e2e/ # BATS end-to-end tests (Prism mock server)
├── openapi/ # OpenAPI specs — source for Prism mock
├── skills/ # Embedded SKILL.md for AI agent usage
├── man/ # Man page sources
├── scripts/ # CI helper scripts
├── .github/workflows/ # CI pipeline
├── main.go # Entrypoint — delegates to cmd.Execute()
├── Makefile # Build, lint, test, surface targets
├── .surface # CLI surface snapshot (committed)
└── .golangci.yml # Linter config (gocritic, paralleltest, cyclop)
Base URLs:
- Platform:
https://api.bunny.net - Stream:
https://video.bunnycdn.com - Storage:
https://storage.bunnycdn.com
Authentication: AccessKey header with API key.
Key resource paths (see openapi/ for full specs):
/dnszone— DNS zone CRUD, DNSSEC, import/export/dnszone/{id}/records— DNS record management/pullzone— Pull zone CRUD, edge rules, hostnames, purge/storagezone— Storage zone CRUD, password reset/compute/script— Edge scripting CRUD, publish, secrets, variables/shield/zone— Shield zone management/shield/waf— WAF rules, custom rules, triggered rules/shield/rate-limit— Rate limit rules/videolibrary— Stream library CRUD/library/{id}/videos— Video management, upload, fetch, captions/library/{id}/collections— Collection management/statistics— CDN statistics/region— Available regions/country— Country list
make check is the local CI gate. Run it before pushing.
make check # All checks (local CI gate)
make test # Go unit tests only
make test-race # Unit tests with race detector
make test-coverage # Generate coverage report
make lint # golangci-lint
make vet # go vet
make fmt # Format code (gofmt)
make fmt-check # Check formatting (fails if not formatted)
make tidy # Run go mod tidy
make tidy-check # Verify go.mod/go.sum are tidy (non-mutating)
make test-e2e # BATS e2e tests (requires Prism)
make build # Build binary to ./bin/bunny
make surface # Regenerate .surface snapshot
make check-surface # Verify .surface is up to date
make check-skill-drift # Verify SKILL.md matches CLI surface
make help # Show all available targetsWhen iterating on a specific area, use targeted targets for faster feedback,
then finish with make check before pushing.
Unit tests live alongside source files as *_test.go. Each resource has a
corresponding *_api.go interface file to enable mocking. Tests use t.Parallel()
throughout (enforced by the paralleltest linter).
E2E tests use BATS (Bash Automated
Testing System) with a Prism mock server
that validates requests against the OpenAPI specs in openapi/.
The test runner (e2e/run.sh) handles the full lifecycle: build binary, start
Prism on a random port, run BATS tests, clean up. Test helpers live in
e2e/test_helper.bash.
Environment variables used by e2e tests:
PRISM_URL— mock server base URL (set by run.sh)BUNNY_BINARY— path to compiled CLI binary (set by run.sh)
The .surface file at the repo root is a deterministic, sorted snapshot of
every command, argument, and flag in the CLI. It is generated by
internal/surface/surface.go and committed to version control.
Purpose: catch accidental command/flag changes in CI and provide a stable reference for skill drift detection.
Format — one line per entry, sorted lexicographically:
CMD bunny dns list
ARG bunny dns get <id>
FLAG bunny dns list --limit int
FLAG bunny --output string # persistent flag on root
Regenerate after adding/removing/renaming commands, flags, or arguments:
make surface # regenerate .surfaceCI enforcement: make check includes check-surface, which fails with a
diff if .surface is stale. Always run make surface after command/flag changes
and commit the updated file.
skills/bunny/SKILL.md is an agent-facing reference document embedded into the
binary via skills/embed.go. It teaches AI agents how to use bunny-cli:
authentication, commands, flags, workflows, error codes, and decision trees.
bunny skill— prints SKILL.md to stdout (no auth required)bunny skill install— installs SKILL.md to the Claude Code skills directory
Every command and flag mentioned in SKILL.md must match an entry in .surface.
After command/flag changes:
- Run
make surfaceto regenerate.surface - Update
skills/bunny/SKILL.mdif commands, flags, or workflows changed - Run
make check— thecheck-skill-drifttarget will catch any mismatches
scripts/check-skill-drift.sh scans SKILL.md for bunny <cmd> and --<flag>
references and verifies each one exists in .surface. Known/accepted mismatches
can be baselined in .surface-skill-drift.
make check-skill-drift # run drift check standaloneWhen you add, remove, or rename a command or flag:
- Make the code change in
cmd/ make surface— regenerate.surface- Update
skills/bunny/SKILL.mdif the change affects agent-visible behavior make check— validates fmt, vet, lint, tests, e2e, surface, and tidy
- Go 1.26+ (see
go.modfor exact version) - golangci-lint — multi-linter runner
- bats-core — Bash testing framework (for e2e tests)
- Node.js/npx — required for Prism mock server
- jq — used by e2e test assertions
Install all dependencies via Homebrew:
brew bundle