Use native-sounding English in all communication with no excessive capitalization (e.g HOW IS THIS GOING), multiple question marks (how's this going???), grammatical errors (how's dis going), or typos (thnx fr update).
- ❌ Before: "is this still open ?? I am happy to work on it ??"
- ✅ After: "Is this actively being worked on? I've started work on it here…"
Explain the reasoning behind your changes, not just the change itself. Describe the architectural decision or the specific problem being solved. For bug fixes, identify the root cause. Don't apply a fix without explaining how the invalid state occurred.
git clone https://github.com/antiwork/gumroad-cli
cd gumroad-cli
make build # Compile to ./gumroad
make test # Run all tests- Go 1.25+
- golangci-lint for linting
- Include an AI disclosure
- Self-review (comment) on your code
- Break up big 1k+ line PRs into smaller PRs (100 loc)
- Must: Include a video for every PR. For user-facing changes (new commands, changed output), show before/after in a terminal recording. For non-user-facing changes, record a short walkthrough of the relevant existing functionality to demonstrate understanding and confirm nothing broke.
- Include updates to any tests!
Non-trivial PRs should follow this structure:
- What — What this PR does. Concrete changes, not a list of files.
- Why — Why this change exists and why this approach was chosen over alternatives.
- Before/After — Video is required for all PRs. For user-facing changes, show before/after terminal output. For non-user-facing changes, include a short walkthrough video.
- Test Results — Screenshot of tests passing locally.
End with an AI disclosure after a --- separator. Name the specific model (e.g., "Claude Opus 4.6") and list the prompts given to the agent.
Claude Code Review is set to manual mode. After opening a PR, request a review by posting a @claude review once comment on the PR.
Use the latest and greatest state-of-the-art models from American AI companies like Anthropic and OpenAI. As of this writing, that means Claude Opus 4.6 and GPT-5.4, but always check for the newest releases. Don't settle for last-gen models when better ones are available.
Rebase your branch onto main when starting work and before every commit:
git fetch origin
git rebase origin/mainResolve conflicts locally before pushing. PRs with stale branches will not be merged.
Always run the full check suite before pushing:
make test-cover # Tests with coverage gates (85% cmd, 90% infra)
make lint # golangci-lintDo not push code with failing tests. CI is not a substitute for local verification.
- Run
gofmtbefore committing (the linter enforces this) - Follow Effective Go for Go style and the Command Line Interface Guidelines (clig.dev) for CLI UX — flags, output, errors, and help text. clig.dev is the design baseline this CLI is built on.
- No explanatory comments — code should explain itself through clear naming and structure. The rare exception is a comment the code alone can't carry (a surprising invariant, a workaround for an external bug, a link to a non-obvious decision); write those in plain language per the style rule in AGENTS.md
- Don't apologize for errors, fix them
- Assign raw numbers to named constants to clarify their purpose
- Use
productinstead oflinkin new code - Use
buyerandsellerwhen naming variables instead ofcustomerandcreator
- Don't use "should" in test descriptions
- Write descriptive test names that explain the behavior being tested
- Group related tests together
- Keep tests independent and isolated
- Tests must fail when the fix is reverted. If the test passes without the application code change, it is invalid.
- Use
testutil.Setupfor mock HTTP servers andtestutil.Commandfor wrapping cobra commands - Use
@example.comfor emails andexample.comfor domains in tests
- Create a package under
internal/cmd/<noun>/ - Add
New<Noun>Cmd()returning*cobra.Command, register ininternal/cmd/root.go - Each subcommand: parse flags → call
api.Client→ format viaoutputpackage - Always use
RunE(notRun) to propagate errors - Add tests — coverage must meet gates
- Document the command in its
--helptext andskills/gumroad/SKILL.md— not the README (see Documentation)
Commands use cmdutil.RunRequestDecoded[T]() (or RunRequest, RunRequestWithSuccess for mutations). These runners handle auth, spinners, dry-run, and JSON/JQ output automatically — the render callback only needs to handle the plain and table cases:
return cmdutil.RunRequestDecoded[productsListResponse](opts, "Fetching products...", "GET", "/products", url.Values{}, func(resp productsListResponse) error {
if opts.PlainOutput {
return output.PrintPlain(opts.Out(), rows)
}
return output.WithPager(opts.Out(), func(w io.Writer) error {
return tbl.Render(w)
})
})Delete/refund require prompt.Confirm(). --yes skips it, --no-input fails if confirmation is needed.
Tests use testutil.Setup to create a mock HTTP server and temp config. testutil.Command wraps a cobra command with test options:
testutil.Setup(t, func(w http.ResponseWriter, r *http.Request) {
testutil.JSON(t, w, map[string]any{
"products": []map[string]any{
{"id": "p1", "name": "Art Pack", "published": true, "formatted_price": "$10"},
},
})
})
cmd := testutil.Command(newListCmd(), testutil.JSONOutput())
out := testutil.CaptureStdout(func() { _ = cmd.RunE(cmd, []string{}) })The CLI is used mostly by AI agents, so the canonical command reference lives where they read it: each command's --help text and the agent skill at skills/gumroad/SKILL.md. Keep the README lean and stable.
- The README covers install, quick start, authentication, output modes, the AI-agent entry point, and development — not per-command reference.
- When you add or change a command, update its
--helpandskills/gumroad/SKILL.md. Do not add a per-command section to the README. - If you're about to explain a command's flags, behavior, or examples in the README, that content belongs in
--helpor the skill instead.
Releases are tag-driven and use Go-compatible date versioning: v0.YYYYMMDD.N.
YYYYMMDDis the UTC release date.Nstarts at0and increments only when multiple releases ship on the same UTC date.make release-tagprints today's default tag, for examplev0.20260609.0; usemake release-tag RELEASE_SEQ=1for a second release on the same day.- The binary and Homebrew formula display date versions as
YYYY.MM.DDorYYYY.MM.DD.N.
Non-obvious behaviors that directly affect how you write code:
- 200 OK with
success: false— the API returns HTTP 200 for many errors. Always check thesuccessfield, not just the status code.internal/api/errors.gohandles this. - Inconsistent numeric types — some fields like
sales_usd_centsarrive as0(int) or0.0(float) depending on state. Usejson.Numberor handle both when parsing. - Null vs missing fields — optional fields may be
null, empty string, or omitted entirely. - Deprecated
pageparam on sales — use cursor-basedpage_keyinstead.
Issues for enhancements, features, or refactors use this structure:
What needs to change. Be concrete:
- Describe the current behavior and the desired behavior
- Who is affected (CLI users, internal team)
- Quantify impact with data when possible
- Use a checkbox task list for multiple deliverables
Why this change matters:
- What user or business problem does this solve?
- Link to related issues, support tickets, or prior discussions for context
Keep it short. The title should carry most of the weight, the body adds context the title can't.
A great bug report includes:
- A quick summary and/or background
- Steps to reproduce
- Be specific!
- Give sample code if you can
- What you expected would happen
- What actually happens
- Notes (possibly including why you think this might be happening, or stuff you tried that didn't work)
- Any issue with label
help wantedis open for contributions - view open issues
By contributing, you agree that your contributions will be licensed under the MIT License.
If a maintainer corrects your approach in review — a convention, a workflow, a gotcha that isn't written down — don't just fix the code. Propose an edit to this guide in the same PR (or a fast follow-up) so the correction is captured once and never has to be repeated. The contributing guide should get a little smarter every time someone gets corrected.