Add marketing recommendations and action commands - #220
Merged
Merged
Conversation
8 tasks
gumclaw
added a commit
to antiwork/gumroad
that referenced
this pull request
Sep 17, 2026
## Stages - [x] Scope - [x] Design - [x] Build - [x] Local regression tests - [ ] QA - [ ] Shipped - [ ] Market - [ ] Sell ## What Expose recommendations plus create/show/approve/execute/cancel through API v2, behind the same `auto_marketing` eligibility the product page uses (holdout sellers excluded), reusing the existing `Marketing::Recommendations`, action state machine, and X executor. Reuse `/v2/emails` authorization (`edit_emails`, including its legacy `account` fallback); scope every resource to the token's seller. Stable idempotency keys preserve the existing row across retries. A separate confirmation token prevents posting text or an account changed after preview. The web controller shares the locked approval method; the Share card is unchanged. Ref antiwork/gumroad-private#2721. Companion: antiwork/gumroad-cli#220. This Rails PR must merge and deploy before the CLI PR merges. Both remain draft for Gianfranco. ## Why Give CLI, MCP, and Gumhead thin access to the web engine, without another scheduler or client-side marketing state. `schedule` in the companion CLI executes an approved action immediately; future-date scheduling is not added. ## Specs - `bundle exec rspec spec/models/marketing spec/services/marketing spec/controllers/products/marketing_actions_controller_spec.rb spec/controllers/api/v2/marketing_actions_controller_spec.rb`: `140 examples, 0 failures`. - `npx vitest run app/javascript/components/ProductEdit/ShareTab/ShareYourLaunchCard.test.tsx`: `Test Files 1 passed (1)`; `Tests 6 passed (6)`. - RuboCop: `8 files inspected, no offenses detected`. No JavaScript files changed, so ESLint/Prettier/TypeScript touched-file checks and UI screenshots are not applicable. - Mutation proofs (guard removed, focused specs run, then restored): idempotency-key reuse/double approve `1 example, 1 failure`; flag `6 examples, 6 failures`; OAuth scope `6 examples, 6 failures`; key validation `3 examples, 3 failures`; seller ownership `4 examples, 4 failures`; published-product gate `2 examples, 2 failures`; claimed-copy freeze `1 example, 1 failure`; posted-action reuse `1 example, 1 failure`; approval preview `2 examples, 2 failures`; execution preview `2 examples, 2 failures`; holdout eligibility (the `auto_marketing` delegation removed from the `Feature` wrapper) `63 examples, 1 failure`. Restored combined suite: `140 examples, 0 failures`. - `bin/test-confidence` hit the isolated web-controller Devise route-initialization error (`Could not find a valid mapping`); it did not reach its confidence gate. The combined suite above loads the real routes and passes unchanged assertions. - CI requires the full suite for `config/routes.rb`; the `run-all-specs` label is applied. ## QA 1. Enable `auto_marketing` for a test seller, connect X, publish a product, and open the Share tab. The existing card still works; a seller outside the flag (or in the holdout) gets 404 from all new endpoints. 2. Fetch `/v2/products/:id/marketing/recommendations`; show the live entry's exact `post_text`, `handle`, and `link_url` before approval. POST `/v2/marketing_actions/:id/approve`, then `/execute`, with the returned `idempotency_key` and reviewed `confirmation_token`. GET the same action for status; POST `/cancel` before execution to dismiss it. Full contract: `docs/marketing-api.md`. 3. Real built CLI → local Rails → database verification passed: repeated approve/execute returned one action and made exactly one outbound X request. Only X was stubbed; no real social post was sent. Demo fixtures were removed afterward. ## Before/after Filtered replay of the captured real CLI/Rails transcript; synthetic action IDs and preview tokens are replaced with placeholders, with replay pacing for readability.  ## Review rounds - Round 1 @ `9b780ba3932049584da92eb5225f37c439c92654`: Astra + Fable panel clean on the initial build. - Round 2 @ `c2cee05f73d90e3c8f2199c35cb0f6646353541e`: maintainer commit — the API approve requires a reviewed `confirmation_token`, the web controller keeps its own approval method, and the handle and token read from one reloaded account snapshot. Panel re-run clean at that head; Greptile also reviewed it with no findings. - Round 3 @ `3afb45f5586be5a1ffe8599b828b864bd7dc1264`: merge main (#7730 credentials copy fix and #7727 holdout gate) plus a spec that pins the treatment side, since the eligibility gate now freezes an assignment on first use. Panel clean. - Round 4 @ `05d714de14c08a5a909f26978d28614c07dc870c`: dropped a redundant `Marketing::Eligibility` indirection in the API gate (`Feature.active?` already delegates there after #7727). Panel clean. - Round 5 @ `05d714de14c08a5a909f26978d28614c07dc870c` (independent re-run of the same diff): one codex P1 asked for a `Marketing::Eligibility` call at the API gate, on the grounds that `Feature.active?` ignores the holdout. Refuted: `Feature.active?(:auto_marketing, actor)` returns `Marketing::Eligibility.enabled_for?(actor)` (`lib/utilities/feature.rb:39`), which is false for a holdout seller, and `spec/controllers/api/v2/marketing_actions_controller_spec.rb` "refuses a holdout seller on every endpoint" pins the 404 on all three actions — green in CI at this head. No code change; the fable engine reported 0 findings. --- AI disclosure: implemented with **gpt-6-astra** (GPT-6 Astra) via Hermes Agent, from antiwork/gumroad-private#2721. Prompt: "Build antiwork/gumroad-private#2721 end to end to a panel-clean DRAFT PR: expose `GET /v2/products/:id/marketing/recommendations` plus `marketing_actions` show/approve/execute/cancel behind `auto_marketing` with the `edit_emails` scope, reusing the existing `Marketing::Recommendations`, action state machine and X executor — no second engine, idempotent approve/execute, 404 when the flag is off; add a paired draft CLI PR in antiwork/gumroad-cli." Review: read the existing engine on `origin/main` before editing, run the focused RSpec + vitest suites with one mutation proof per guard, and run the Astra + Fable premerge panel before calling it ready. Premerge review: clean @ 05d714d --------- Co-authored-by: Gianfranco Piana <52470719+gianfrancopiana@users.noreply.github.com>
gumclaw
marked this pull request as ready for review
September 17, 2026 01:25
TastelintNeither screenshot shows a user interface — one is a mostly blank terminal frame and the other is captured test output — so there is nothing to review here. |
TastelintNeither screenshot shows a user interface — one is an empty terminal frame and the other is captured test output, so there is nothing to review here. |
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Add
gumroad marketing recommend <product>andapprove|schedule|cancel|status <action>, with the same commands exposed by the existing MCP registry. Every mutation previews the exact text, account and link; scripts use--yes, and approve/schedule additionally require--confirmation-tokenfrom the seller-reviewed preview. MCP does not silently inject confirmation for marketing.Ref antiwork/gumroad-private#2721. Depends on antiwork/gumroad#7724, merged 2026-09-17: the API is on
mainbut not yet deployed, so this CLI must not be released (tag) until that deploy lands. Merging the CLI does not release it.Why
Keep the CLI a thin client of the existing seller marketing engine—no local scheduler or marketing state.
scheduleinvokes the server's immediate execute operation, not a future-dated send; help and the agent skill say so explicitly.Specs
go test ./internal/cmd/marketing ./internal/cmd/mcp ./internal/cmd -count=1 -cover:ok github.com/antiwork/gumroad-cli/internal/cmd/marketing 0.908s coverage: 100.0% of statementsok github.com/antiwork/gumroad-cli/internal/cmd/mcp 1.813s coverage: 100.0% of statementsok github.com/antiwork/gumroad-cli/internal/cmd 0.921s coverage: 93.9% of statements--yesfailsTestMarketingToolsRequireExplicitConfirmation; removing the reviewed-token comparison fails approve/schedule'sold-previewsubtests. Restored focused suite is green.make test-coverwas attempted twice, including serial package execution: two media upload timing tests fail. Both also fail on unchangedorigin/mainwithS3 calls = 2, want 3:TestPutDirectUpload_TimesOutEachAttemptandTestMediaUpload_DirectStateUnknownCarriesRecovery. Not changed here; the full-suite coverage gate remains unpassed locally.--timeout=5m, gofmt, andgit diff --checkpass.95 examples, 0 failures; existing Share card:Test Files 1 passed (1)/Tests 6 passed (6). Idempotency-key reuse mutation:1 example, 1 failure, then restored Rails suite green.--yescannot approve/execute a freshly substituted token. Regression and mutation tests pin the reviewed-token check.QA
auto_marketingfor a test seller, publish a product, and open the Share tab; use the same seller'sedit_emailsOAuth token.gumroad marketing recommend <product> --json; show the exact action text, account and link to the seller. After confirmation rungumroad marketing approve <action> --yes --confirmation-token <reviewed-token>, thengumroad marketing schedule <action> --yes --confirmation-token <reviewed-token>.gumroad marketing status <action>: same row, no second post. Missing confirmation or a changed token refuses the write. Cancel only before the executor claims it.Before/after
Filtered replay of the captured real CLI/Rails transcript; synthetic IDs/tokens are replaced with placeholders and timing is replayed for readability.
Review rounds
b11b4f2a(gpt-6-astra + claude-fable-5-1): 0 findings, patch correct (0.86).antiwork/gumroad#7724head (05d714de, merged 2026-09-17):GET products/:id/marketing/recommendations,GET/POST marketing_actions/:idand/:id/approve|/execute|/cancel,idempotency_key+confirmation_tokensent as form params,{success, marketing_action, handle}responses withid, idempotency_key, confirmation_token, post_text, link_url, external_url, error_code, status— all match this CLI.Premerge review: clean @ b11b4f2
Built with GPT-6 Astra; reviewed with the Astra + Fable panel. Prompt: implement paired marketing API/CLI drafts for Gianfranco, reuse the existing engine, confirm exact content, prove guards with mutations, and do not merge or modify Gumhead.