Skip to content

Latest commit

 

History

History
213 lines (143 loc) · 9.33 KB

File metadata and controls

213 lines (143 loc) · 9.33 KB

Contributing to Gumroad CLI

Overall

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.

Development setup

git clone https://github.com/antiwork/gumroad-cli
cd gumroad-cli
make build    # Compile to ./gumroad
make test     # Run all tests

Requirements

Pull requests

  • 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!

PR description structure

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

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.

AI models

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.

Branch hygiene

Rebase your branch onto main when starting work and before every commit:

git fetch origin
git rebase origin/main

Resolve conflicts locally before pushing. PRs with stale branches will not be merged.

Before pushing

Always run the full check suite before pushing:

make test-cover   # Tests with coverage gates (85% cmd, 90% infra)
make lint         # golangci-lint

Do not push code with failing tests. CI is not a substitute for local verification.

Code standards

  • Run gofmt before 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

Code patterns

  • Use product instead of link in new code
  • Use buyer and seller when naming variables instead of customer and creator

Testing guidelines

  • 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.Setup for mock HTTP servers and testutil.Command for wrapping cobra commands
  • Use @example.com for emails and example.com for domains in tests

Adding a new command

  1. Create a package under internal/cmd/<noun>/
  2. Add New<Noun>Cmd() returning *cobra.Command, register in internal/cmd/root.go
  3. Each subcommand: parse flags → call api.Client → format via output package
  4. Always use RunE (not Run) to propagate errors
  5. Add tests — coverage must meet gates
  6. Document the command in its --help text and skills/gumroad/SKILL.md — not the README (see Documentation)

Output pipeline

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)
    })
})

Destructive operations

Delete/refund require prompt.Confirm(). --yes skips it, --no-input fails if confirmation is needed.

Test pattern

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{}) })

Documentation

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 --help and skills/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 --help or the skill instead.

Releases

Releases are tag-driven and use Go-compatible date versioning: v0.YYYYMMDD.N.

  • YYYYMMDD is the UTC release date.
  • N starts at 0 and increments only when multiple releases ship on the same UTC date.
  • make release-tag prints today's default tag, for example v0.20260609.0; use make release-tag RELEASE_SEQ=1 for a second release on the same day.
  • The binary and Homebrew formula display date versions as YYYY.MM.DD or YYYY.MM.DD.N.

Gumroad API quirks

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 the success field, not just the status code. internal/api/errors.go handles this.
  • Inconsistent numeric types — some fields like sales_usd_cents arrive as 0 (int) or 0.0 (float) depending on state. Use json.Number or handle both when parsing.
  • Null vs missing fields — optional fields may be null, empty string, or omitted entirely.
  • Deprecated page param on sales — use cursor-based page_key instead.

Writing issues

Issues for enhancements, features, or refactors use this structure:

What

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

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.

Writing bug reports

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)

Help

License

By contributing, you agree that your contributions will be licensed under the MIT License.

When you're corrected, fix the docs

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.