diff --git a/CHANGELOG.md b/CHANGELOG.md index 771dcf4..02a8127 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,23 @@ Notable changes per release. Dates are UTC. +## [v0.6.1](https://github.com/akshitkrnagpal/revcat/releases/tag/v0.6.1) - 2026-05-17 + +Release hardening for the public launch. + +### Fixed + +- `revcat doctor` now exits non-zero when a health check fails, so CI and agents can trust it as a gate. +- `revcat subscriptions search` and `revcat purchases search` now follow `next_page` cursors instead of silently returning only the first page. +- `revcat init` now describes its app-selection default accurately: all apps are selected by default. +- Root help and docs now describe the current mode-0600 file-backed auth model instead of the removed keychain backend. + +### Added + +- GitHub Actions CI for `make verify` and the docs build. +- pnpm 11 build-script approvals so docs install and build non-interactively. +- Hermetic marketing/demo GIFs for paywall publishing, project bootstrap, customer debugging, catalog inspection, and operations checks. + ## [v0.6.0](https://github.com/akshitkrnagpal/revcat/releases/tag/v0.6.0) - 2026-05-02 Drops the keyring backend. `~/.revcat/config.json` (mode 0600) is now the only global credential store. diff --git a/README.md b/README.md index f16f5d9..47ecb10 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Docs](https://img.shields.io/badge/docs-revcat.vercel.app-7c8aff)](https://revcat.vercel.app) [![Go](https://img.shields.io/github/go-mod/go-version/akshitkrnagpal/revcat)](./go.mod) -The RevenueCat CLI. Run your RevenueCat project from the terminal instead of clicking through the dashboard. +The RevenueCat CLI for terminal-first product teams. Debug customers, inspect catalog wiring, ship paywall updates, manage webhooks, and pull metrics without clicking through the dashboard. ![revcat demo](./demo/demo.gif) @@ -19,10 +19,51 @@ revcat publish offering pro --paywall ./paywalls/pro.json ## Why -RevenueCat ships a dashboard, a REST API, and an MCP server, but no first-party CLI. revcat covers the v2 REST API surface accessible via OAuth: full CRUD on entitlements, offerings, packages, products, paywalls, webhooks, virtual currencies; per-customer grants/refunds/transfers; metrics + charts; audit-logs. +RevenueCat ships a dashboard, a REST API, and an MCP server, but no first-party CLI. revcat gives you a fast, scriptable surface for the jobs that are slow or repetitive in the dashboard: + +- debug "paid but no premium" support tickets from a store transaction id +- inspect offerings, packages, products, entitlements, and paywalls +- ship paywall config changes and set the current offering in one command +- grant, revoke, refund, transfer, and inspect customer state +- check webhooks, audit logs, charts, metrics, and project configuration +- give CI, scripts, and agents JSON output without another flag Output is a colored table when you're at a terminal and JSON when you're piping into a script - no `--json` ceremony. +## Demos + +All demo GIFs are hermetic recordings backed by local fixtures in [`demo/mock-bin`](./demo/mock-bin/). They show real command syntax without touching a live RevenueCat project. + +### Ship a paywall update + +Push a paywall config and keep the offering current from the terminal. + +![revcat paywall publish demo](./demo/demo.gif) + +### Bootstrap project context + +Bind a repo once so future commands inherit the right project and credentials. + +![revcat init demo](./demo/init.gif) + +### Debug and fix customer access + +Resolve a store transaction id, inspect the customer, and grant goodwill access. + +![revcat customer debug demo](./demo/customer-debug.gif) + +### Inspect catalog wiring + +Check offerings, products, and package membership without opening the dashboard. + +![revcat catalog demo](./demo/catalog.gif) + +### Monitor operations + +Review webhooks, audit logs, and headline metrics while launch traffic is coming in. + +![revcat ops demo](./demo/ops.gif) + ## Install ```sh @@ -183,10 +224,10 @@ Full docs at - install, quickstart, every command, c revcat ships four [Agent Skills](./skills/) (open standard) so Claude Code, Cursor, and Codex can compose revcat commands accurately: -- `revcat-getting-started` — install, auth, top-level command map -- `revcat-commands` — real syntax + examples for every subcommand -- `revcat-troubleshooting` — common errors and fixes -- `revcat-storefront-debug` — 7-step diagnostic for "the SDK sees 0 packages from my offering" +- `revcat-getting-started` - install, auth, top-level command map +- `revcat-commands` - real syntax + examples for every subcommand +- `revcat-troubleshooting` - common errors and fixes +- `revcat-storefront-debug` - 7-step diagnostic for "the SDK sees 0 packages from my offering" Install via [skills.sh](https://skills.sh) (auto-detects your agent): @@ -194,7 +235,7 @@ Install via [skills.sh](https://skills.sh) (auto-detects your agent): npx skills add akshitkrnagpal/revcat ``` -Or manually — see [`skills/README.md`](./skills/README.md) for the Claude Code / Cursor / Codex paths. +Or manually - see [`skills/README.md`](./skills/README.md) for the Claude Code / Cursor / Codex paths. ## License diff --git a/demo/README.md b/demo/README.md index 5680c40..0334899 100644 --- a/demo/README.md +++ b/demo/README.md @@ -1,30 +1,30 @@ # revcat demos -Three [vhs](https://github.com/charmbracelet/vhs) tapes, each rendering one user-facing flow. `demo.gif` is the README hero; the other two anchor specific guide pages. +Five [vhs](https://github.com/charmbracelet/vhs) tapes, each rendering one user-facing flow. `demo.gif` is the README hero; the other GIFs are marketing/support cuts for specific use cases. + +All tapes are hermetic by default: they prepend `./mock-bin` to `PATH`, so they show real revcat command syntax without calling RevenueCat or requiring credentials. | Tape | Output | What it shows | | --- | --- | --- | | `demo.tape` | `demo.gif` | Hero: ship a paywall update + set offering current with one orchestrator command. | | `init.tape` | `init.gif` | Bootstrap: `revcat init` materializes `revcat.toml` + `.revcat/config.json` so subsequent commands inherit project context. | -| `customer-debug.tape` | `customer-debug.gif` | Support flow: resolve a store transaction id back to a customer, pull their state, surface the entitlements catalog. Read-only; the grant command is shown as a comment. | +| `customer-debug.tape` | `customer-debug.gif` | Support flow: resolve a store transaction id back to a customer, inspect access, then grant goodwill access. | +| `catalog.tape` | `catalog.gif` | Catalog inspection: offerings, products, and package membership. | +| `ops.tape` | `ops.gif` | Ops checks: webhooks, audit logs, and headline metrics. | ## Regenerate ```sh brew install vhs -# revcat must be on $PATH. Either bind a project (`revcat init` in this -# directory) so revcat picks up project context from ./.revcat/config.json, -# OR set the env hatch for a one-off: -export REVCAT_REFRESH_TOKEN=rtk_... -export REVCAT_PROJECT_ID=proj_... - cd demo vhs demo.tape # produces demo.gif vhs init.tape # produces init.gif vhs customer-debug.tape # produces customer-debug.gif +vhs catalog.tape # produces catalog.gif +vhs ops.tape # produces ops.gif ``` -The `customer-debug.tape` ids are placeholders (`1000000123456789`, `app_user_demo`, `premium`). Edit them to match real ids in your test project before recording. The other two tapes work against any project that has an offering called `default` (for the hero) or just any authed credential (for init). +For social video uploads, duplicate any tape and change `Output something.gif` to `Output something.mp4`; VHS will render an MP4 with the same scene. -`paywall.json` is a sample paywall body kept in this folder so the hero tape is self-contained. +`paywall.json` is a sample paywall body kept in this folder so the hero tape is self-contained. `mock-bin/revcat` owns the deterministic fixture output; update it when a tape adds a new command. diff --git a/demo/catalog.gif b/demo/catalog.gif new file mode 100644 index 0000000..ab1abf2 Binary files /dev/null and b/demo/catalog.gif differ diff --git a/demo/catalog.tape b/demo/catalog.tape new file mode 100644 index 0000000..a074439 --- /dev/null +++ b/demo/catalog.tape @@ -0,0 +1,47 @@ +##################################################################### +# revcat catalog demo - inspect RevenueCat catalog relationships +# +# Shows offerings, products, and package membership without opening the +# RevenueCat dashboard. +# +# Regenerate with: +# brew install vhs +# cd demo +# vhs catalog.tape # produces catalog.gif +# +# Hermetic by default: uses ./mock-bin/revcat and never calls RevenueCat. +##################################################################### + +Output catalog.gif + +Set Shell "zsh" +Set FontSize 17 +Set Width 1280 +Set Height 600 +Set Theme "Catppuccin Mocha" +Set Padding 24 +Set TypingSpeed 28ms +Set PlaybackSpeed 1.0 + +Hide +Type 'export PS1="%F{12}$ %f"' Enter +Type 'export PATH="$PWD/mock-bin:$PATH"' Enter +Ctrl+L +Show + +Type "revcat offerings list" +Sleep 200ms +Enter +Sleep 1600ms + +Type "revcat products list" +Sleep 200ms +Enter +Sleep 1600ms + +Type "revcat packages list --offering default" +Sleep 200ms +Enter +Sleep 1600ms + +Sleep 600ms diff --git a/demo/customer-debug.gif b/demo/customer-debug.gif new file mode 100644 index 0000000..849cc76 Binary files /dev/null and b/demo/customer-debug.gif differ diff --git a/demo/customer-debug.tape b/demo/customer-debug.tape index a445c77..daf084b 100644 --- a/demo/customer-debug.tape +++ b/demo/customer-debug.tape @@ -9,12 +9,10 @@ # # Regenerate with: # brew install vhs -# export REVCAT_REFRESH_TOKEN=rtk_... -# export REVCAT_PROJECT_ID=proj_... -# # Edit the placeholders below to match real ids in your TEST project: -# # STORE_TX_ID, APP_USER_ID, ENTITLEMENT_ID # cd demo # vhs customer-debug.tape # produces customer-debug.gif +# +# Hermetic by default: uses ./mock-bin/revcat and never calls RevenueCat. ##################################################################### Output customer-debug.gif @@ -25,11 +23,12 @@ Set Width 1280 Set Height 600 Set Theme "Catppuccin Mocha" Set Padding 24 -Set TypingSpeed 35ms +Set TypingSpeed 28ms Set PlaybackSpeed 1.0 Hide Type 'export PS1="%F{12}$ %f"' Enter +Type 'export PATH="$PWD/mock-bin:$PATH"' Enter Ctrl+L Show @@ -39,26 +38,24 @@ Show Type "revcat subscriptions search 1000000123456789" Sleep 200ms Enter -Sleep 2200ms +Sleep 1600ms # 2. Now you have the customer. Pull their state. Type "revcat subscribers info app_user_demo" Sleep 200ms Enter -Sleep 2500ms +Sleep 1800ms # 3. Read-only entitlement list - confirms what you'd attach to. -# The demo stops here so it stays mutation-free. Type "revcat entitlements list" Sleep 200ms Enter -Sleep 2200ms +Sleep 1600ms -# When you're ready to actually grant goodwill access, the next step -# is one line - shown as a comment so this gif doesn't mutate state: -Type "# revcat subscribers grant app_user_demo premium -d 7d --confirm" +# 4. Fix the user in one command. This is fixture output, not live state. +Type "revcat subscribers grant app_user_demo premium -d 7d --confirm" Sleep 200ms Enter -Sleep 1800ms +Sleep 1600ms Sleep 600ms diff --git a/demo/demo.gif b/demo/demo.gif index 6580a15..e880c01 100644 Binary files a/demo/demo.gif and b/demo/demo.gif differ diff --git a/demo/demo.tape b/demo/demo.tape index 740f6c3..37a7cee 100644 --- a/demo/demo.tape +++ b/demo/demo.tape @@ -8,36 +8,34 @@ # cd demo # vhs demo.tape # produces demo.gif # -# Assumes: -# - revcat is on $PATH (brew install akshitkrnagpal/tap/revcat) -# - this directory has been bound to a project (cd demo && revcat init) -# so revcat picks up project context from ./.revcat/config.json, -# OR REVCAT_REFRESH_TOKEN + REVCAT_PROJECT_ID are set in the env -# - the bound project has an offering called "default" (or edit below) +# Hermetic by default: +# - prepends ./mock-bin to PATH so this recording never calls RevenueCat +# - shows real revcat command syntax against deterministic fixture output ##################################################################### Output demo.gif Set Shell "zsh" -Set FontSize 18 +Set FontSize 17 Set Width 1280 -Set Height 480 +Set Height 560 Set Theme "Catppuccin Mocha" Set Padding 24 -Set TypingSpeed 35ms +Set TypingSpeed 28ms Set PlaybackSpeed 1.0 # Custom prompt: simple $, no zsh continuation `>`. Hide Type 'export PS1="%F{12}$ %f"' Enter +Type 'export PATH="$PWD/mock-bin:$PATH"' Enter Ctrl+L Show -# 1. Frame the input. Show the paywall body that will ship. -Type "cat paywall.json | jq" +# 1. Show a compact summary of the paywall body that will ship. +Type "jq '{title: .config.title, cta: .config.cta, packages: .config.package_ids}' paywall.json" Sleep 200ms Enter -Sleep 1500ms +Sleep 1400ms # 2. Ship it. The orchestrator does both steps - PUT paywall + set # offering as current - in one shot. --dry-run prints the plan so @@ -45,13 +43,13 @@ Sleep 1500ms Type "revcat publish offering default --paywall ./paywall.json --dry-run --confirm" Sleep 200ms Enter -Sleep 2500ms +Sleep 2200ms # 3. Confirm. Read-only, just shows that the offering is what the SDK # will see. Type "revcat offerings view default" Sleep 200ms Enter -Sleep 1500ms +Sleep 1600ms Sleep 600ms diff --git a/demo/init.gif b/demo/init.gif index 9e3c11b..615100d 100644 Binary files a/demo/init.gif and b/demo/init.gif differ diff --git a/demo/init.tape b/demo/init.tape index 4d18284..c4f0516 100644 --- a/demo/init.tape +++ b/demo/init.tape @@ -9,33 +9,33 @@ # # Regenerate with: # brew install vhs -# export REVCAT_REFRESH_TOKEN=rtk_... -# export REVCAT_PROJECT_ID=proj_... # cd demo # vhs init.tape # produces init.gif +# +# Hermetic by default: uses ./mock-bin/revcat and never calls RevenueCat. ##################################################################### Output init.gif Set Shell "zsh" -Set FontSize 18 +Set FontSize 17 Set Width 1280 -Set Height 560 +Set Height 600 Set Theme "Catppuccin Mocha" Set Padding 24 -Set TypingSpeed 35ms +Set TypingSpeed 28ms Set PlaybackSpeed 1.0 -# Setup: throwaway sandbox dir, simple prompt, project_id pre-resolved -# from the env so the survey picker doesn't gate the recording. +# Setup: throwaway sandbox dir, simple prompt, mock revcat on PATH. Hide Type 'export PS1="%F{12}$ %f"' Enter +Type 'export DEMO_DIR="$PWD"; export PATH="$DEMO_DIR/mock-bin:$PATH"' Enter Type 'cd $(mktemp -d)' Enter Ctrl+L Show # 1. Empty directory. Same shape every reader's repo starts as. -Type "ls -la" +Type "find . -maxdepth 2 -type f | sort" Sleep 200ms Enter Sleep 800ms @@ -45,24 +45,24 @@ Sleep 800ms Type "revcat init --no-apps" Sleep 200ms Enter -Sleep 2500ms +Sleep 2200ms # 3. Two files now exist. revcat.toml is the committed half. -Type "ls -la" +Type "find . -maxdepth 2 -type f | sort" Sleep 200ms Enter -Sleep 1200ms +Sleep 1000ms Type "cat revcat.toml" Sleep 200ms Enter -Sleep 1500ms +Sleep 1200ms # 4. Project context is now automatic. No --project-id, no env, just # works from cwd. Type "revcat auth status" Sleep 200ms Enter -Sleep 2000ms +Sleep 1800ms Sleep 600ms diff --git a/demo/mock-bin/revcat b/demo/mock-bin/revcat new file mode 100755 index 0000000..58b9ba5 --- /dev/null +++ b/demo/mock-bin/revcat @@ -0,0 +1,143 @@ +#!/usr/bin/env bash +set -euo pipefail + +case "$*" in + "publish offering default --paywall ./paywall.json --dry-run --confirm") + cat <<'OUT' +Plan: publish offering "default" + + ok read ./paywall.json + ok validate packages: $rc_monthly, $rc_annual + dry update paywall config + dry keep "default" as current offering + +No changes sent because --dry-run is set. +OUT + ;; + + "offerings view default") + cat <<'OUT' +Offering default +Current yes +Paywall Unlock Pro +Packages $rc_monthly, $rc_annual +OUT + ;; + + "init --no-apps") + mkdir -p .revcat + cat > revcat.toml <<'OUT' +project_id = "proj_demo" +OUT + cat > .revcat/config.json <<'OUT' +{"project_id":"proj_demo","profile":{"name":"demo","access_token":"redacted","refresh_token":"redacted"}} +OUT + cat <<'OUT' +wrote revcat.toml +wrote .revcat/config.json (mode 0600) +project_id: proj_demo +commit revcat.toml; do NOT commit .revcat/ +OUT + ;; + + "auth status") + cat <<'OUT' +name demo +source local +source_path .revcat/config.json +project_id proj_demo +validation ok +OUT + ;; + + "subscriptions search 1000000123456789") + cat <<'OUT' +id customer store status +sub_demo_123 app_user_demo app_store active +OUT + ;; + + "subscribers info app_user_demo") + cat <<'OUT' +customer app_user_demo +active premium +subscription sub_demo_123 +store app_store +renews 2026-06-17 +OUT + ;; + + "entitlements list") + cat <<'OUT' +id display_name products +premium Premium pro_monthly, pro_annual +team Team team_monthly +OUT + ;; + + "subscribers grant app_user_demo premium -d 7d --confirm") + cat <<'OUT' +granted premium +customer app_user_demo +expires 2026-05-24 +OUT + ;; + + "offerings list") + cat <<'OUT' +id current packages +default yes $rc_monthly, $rc_annual +trial no $rc_weekly +winback no $rc_monthly_discount +OUT + ;; + + "products list") + cat <<'OUT' +id store type status +pro_monthly app_store subscription active +pro_annual app_store subscription active +coins_100 app_store consumable active +OUT + ;; + + "packages list --offering default") + cat <<'OUT' +id offering product +$rc_monthly default pro_monthly +$rc_annual default pro_annual +OUT + ;; + + "webhooks list") + cat <<'OUT' +id url events +wh_prod https://api.acme.com/rc initial_purchase, renewal, cancellation +wh_ops https://ops.acme.com/revcat refund, billing_issue +OUT + ;; + + "audit-logs list") + cat <<'OUT' +time actor action +10:42 UTC akshit offering.default.updated +10:39 UTC support-bot customer.app_user_demo.granted +10:31 UTC revenuecat webhook.wh_prod.delivered +OUT + ;; + + "metrics overview") + cat <<'OUT' +metric value +MRR $24,820 +ARR $297,840 +active subs 1,842 +refund rate 1.7% +OUT + ;; + + *) + echo "mock revcat: no fixture for: revcat $*" >&2 + exit 64 + ;; +esac diff --git a/demo/ops.gif b/demo/ops.gif new file mode 100644 index 0000000..db8d7d6 Binary files /dev/null and b/demo/ops.gif differ diff --git a/demo/ops.tape b/demo/ops.tape new file mode 100644 index 0000000..ef85b07 --- /dev/null +++ b/demo/ops.tape @@ -0,0 +1,47 @@ +##################################################################### +# revcat ops demo - audit logs, webhooks, and headline metrics +# +# Shows operational checks that are awkward to hop through the dashboard +# for during support or launch monitoring. +# +# Regenerate with: +# brew install vhs +# cd demo +# vhs ops.tape # produces ops.gif +# +# Hermetic by default: uses ./mock-bin/revcat and never calls RevenueCat. +##################################################################### + +Output ops.gif + +Set Shell "zsh" +Set FontSize 17 +Set Width 1280 +Set Height 600 +Set Theme "Catppuccin Mocha" +Set Padding 24 +Set TypingSpeed 28ms +Set PlaybackSpeed 1.0 + +Hide +Type 'export PS1="%F{12}$ %f"' Enter +Type 'export PATH="$PWD/mock-bin:$PATH"' Enter +Ctrl+L +Show + +Type "revcat webhooks list" +Sleep 200ms +Enter +Sleep 1700ms + +Type "revcat audit-logs list" +Sleep 200ms +Enter +Sleep 1700ms + +Type "revcat metrics overview" +Sleep 200ms +Enter +Sleep 1700ms + +Sleep 600ms diff --git a/docs/src/content/docs/guides/debug-paid-no-premium.md b/docs/src/content/docs/guides/debug-paid-no-premium.md index 289f8c0..621676e 100644 --- a/docs/src/content/docs/guides/debug-paid-no-premium.md +++ b/docs/src/content/docs/guides/debug-paid-no-premium.md @@ -5,6 +5,8 @@ description: Walk through a real support ticket end-to-end with revcat. The single most common RC support flow: a customer contacts you saying they paid but the app doesn't unlock the paid features. Here's how to debug it without leaving the terminal. +![revcat customer debug demo](https://raw.githubusercontent.com/akshitkrnagpal/revcat/main/demo/customer-debug.gif) + ## 1. Find the customer If you have the **store transaction id** from their receipt or App Store / Play / Stripe email: diff --git a/docs/src/content/docs/index.mdx b/docs/src/content/docs/index.mdx index 983ed43..6435962 100644 --- a/docs/src/content/docs/index.mdx +++ b/docs/src/content/docs/index.mdx @@ -22,7 +22,7 @@ import { Card, CardGrid, Code } from "@astrojs/starlight/components"; Sign in once, then everything is one command away. ```sh -# install (Go 1.23+) +# install (Go 1.26+) go install github.com/akshitkrnagpal/revcat/cmd/revcat@latest # auth (browser OAuth) @@ -62,6 +62,20 @@ revcat publish offering pro --paywall ./paywalls/pro.json +## Demo workflows + +### Ship a paywall update + +![revcat paywall publish demo](https://raw.githubusercontent.com/akshitkrnagpal/revcat/main/demo/demo.gif) + +### Debug customer access + +![revcat customer debug demo](https://raw.githubusercontent.com/akshitkrnagpal/revcat/main/demo/customer-debug.gif) + +### Inspect catalog wiring + +![revcat catalog demo](https://raw.githubusercontent.com/akshitkrnagpal/revcat/main/demo/catalog.gif) + ## What's next - [Installation](/getting-started/installation/) - Homebrew, `go install`, and pre-built binaries diff --git a/skills/revcat-commands/SKILL.md b/skills/revcat-commands/SKILL.md index 87f1de5..b9cd9fe 100644 --- a/skills/revcat-commands/SKILL.md +++ b/skills/revcat-commands/SKILL.md @@ -81,6 +81,7 @@ revcat entitlements detach [ ...] revcat offerings list # current marked with * revcat offerings view +revcat offerings preview [] [--platform ios|android|web] [--as ] revcat offerings create --id pro --display-name "Pro" | --file ./offering.json revcat offerings update --display-name "..." | --file ./patch.json revcat offerings delete [-y] diff --git a/skills/revcat-troubleshooting/SKILL.md b/skills/revcat-troubleshooting/SKILL.md index fa1a931..cf965b2 100644 --- a/skills/revcat-troubleshooting/SKILL.md +++ b/skills/revcat-troubleshooting/SKILL.md @@ -67,7 +67,7 @@ The credential resolved fine but no project id is set. ## "compile error" / build fails on `go install` -revcat targets Go 1.23+. Check with `go version`. Older Go won't build the cobra dependency tree. +revcat targets Go 1.26+. Check with `go version`. Older Go won't build the cobra dependency tree. ## `revcat publish offering --paywall` keeps a no-op for me