* billing: $10 signup credit + lazy claim modal; disable welcome credit promo
Adds a per-Account $10 Router signup credit granted on first Router-tab
mount via a new admin-gated POST /api/account/[owner]/signup-credit/claim.
The endpoint is idempotent — the inserted CreditGrant row IS the dedup
state, so subsequent calls return granted:false. Client SignupCreditModal
fires the POST on mount (only when modelAccessMode === "router") and
opens a celebratory dialog when granted:true.
Disables the legacy welcome credit ($10 on first card add) via a new
WELCOME_CREDIT_PROMO_ACTIVE = false flag in utils/stripe.ts. Code path
stays intact — flip the flag to revive. Strips the now-untruthful
"$10 on enabling billing" copy from BillingCard, EnableRouterPrompt,
triggerWorkflow paywall comment, action router_requires_card summary,
email snippet, billing/pricing docs and wiki.
Cuts WELCOME_CREDIT_CENTS from 2000 to 1000 to reflect the lower amount
that would land if the flag is ever re-enabled. Adds "signup" reason
mapping to BillingCard wallet history.
Verified end-to-end against dev: admin+Router fires modal, admin+BYOK
gate-blocks mount, BYOK→Router transition fires modal on click, member
and collaborator paths skip the mount entirely, reload after grant is
idempotent. Wallet history shows "Router signup credit +$10.00".
* billing: address PR review (race fix, copy sweep, modal retry)
Correctness:
- Add @@unique([accountId, reason]) on CreditGrant + migration. The prior
check-then-insert pattern in /signup-credit/claim and finalizeCheckoutSession
raced at READ COMMITTED — two concurrent admin tabs could land two grants of
the same reason on a fresh account ($10 each). Both write sites now rely on
the unique index for dedup (P2002 = "already granted") and route updated to
catch P2002 cleanly. Verified zero existing duplicates in prod before
migration.
- Add log.info on signup grant insert so a successful grant has any chance of
being caught by ops monitoring.
- Add retry: 2 with backoff to the claim mutation. Endpoint is idempotent so
a server-side success that lost its response cleanly returns granted:false
on retry.
Public copy that still advertised the (now-deleted) $20 welcome credit:
- app/page.tsx landing pricing card
- emails/announceBilling.ts broadcast template
- docs/keys.mdx BYOK note
- components/AgentSettings.tsx Router-without-billing warning
- utils/stripe.ts finalizeCheckoutSession JSDoc
- utils/email/snippets.ts ROUTER_CREDIT_PS_HTML JSDoc
Wiki staleness sweep:
- wiki/billing.md TOC, mermaid diagram (signup edge added; welcome marked
dormant), test coverage list, key modules section, no-card wallet narrative
- wiki/pricing.md welcome-credit drawdown reference
- Rewrote my own internally-inconsistent dormancy paragraph to be honest
about the $20-historical / $10-on-revival framing.
Trivia:
- ModelAccessCard JSX comment had a literal \\u2192 instead of →.
* billing: address PR review round 2
- Replace try/catch P2002 inside finalizeCheckoutSession's prisma.$transaction
with createMany skipDuplicates. The previous form is broken on Postgres: a
unique-violation poisons the surrounding TX, so the catch block returns
cleanly but the outer commit fails and the account.update (stripeCustomerId)
silently rolls back too. Currently armed only behind the dormant welcome-
credit flag, but would have broken billing enablement the moment the flag
flipped. createMany skipDuplicates yields a single ON CONFLICT DO NOTHING
statement that returns count: 0 cleanly without aborting the TX.
- Apply the same createMany skipDuplicates pattern to the signup-credit route
too — drops the exception-as-control-flow Prisma namespace import and is
more uniform with the welcome path.
- Drop the now-orphaned credit_grants_accountId_idx in the same migration.
The schema removed @@index([accountId]) when @@unique([accountId, reason])
was added (covered by the leftmost prefix), but the migration only added
the unique index, leaving prod drifted.
* billing: fix stale finalizeCheckoutSession JSDoc
The function-level JSDoc still described the abandoned try/catch P2002
mechanism after switching to createMany skipDuplicates. The inline
comment + code now agree on the new ON CONFLICT DO NOTHING shape.
* billing: decouple first-card alert, drop vestigial billing field, fix modal cents; sync copy
* docs+homepage: align Router credit copy with signup claim (no card-on-add carrot)
* homepage: add pricing screenshot and pay-as-you-go promo line
* billing: fix once-per-lifetime misframe on first-card alert
* billing: suppress signup credit for prior welcome-credit recipients
* billing: drop bogus '1000 users' cap; invalidate billing on signup-credit settle
Pullfrog is a GitHub bot that brings the full power of your favorite coding agents into GitHub. It's open source and powered by GitHub Actions.
Tag @pullfrog — Tag @pullfrog in a comment anywhere in your repo. It will pull in any relevant context using the action's internal MCP server and perform the appropriate task.
Prompt from the web — Trigger arbitrary tasks from the Pullfrog dashboard
Automated triggers — Configure Pullfrog to trigger agent runs in response to specific events. Each of these triggers can be associated with custom prompt instructions.
issue created
issue labeled
PR created
PR review created
PR review requested
and more...
Pullfrog is the bridge between your preferred coding agents and GitHub. Use it for:
🤖 Coding tasks — Tell @pullfrog to implement something and it'll spin up a PR. If CI fails, it'll read the logs and attempt a fix automatically. It'll automatically address any PR reviews too.
🔍 PR review — Coding agents are great at reviewing PRs. Using the "PR created" trigger, you can configure Pullfrog to auto-review new PRs.
🤙 Issue management — Via the "issue created" trigger, Pullfrog can automatically respond to common questions, create implementation plans, and link to related issues/PRs. Or (if you're feeling lucky) you can prompt it to immediately attempt a PR addressing new issues.
Literally whatever — Want to have the agent automatically add docs to all new PRs? Cut a new release with agent-written notes on every commit to main? Pullfrog lets you do it.
Standalone Usage
You can also use pullfrog/pullfrog as a step in your own workflows. The action exposes a result output that can be consumed by subsequent steps.
Example: Auto-generate release notes on new tags
name:Releaseon:push:tags:['v*']permissions:contents:writejobs:release:runs-on:ubuntu-lateststeps:- name:Checkoutuses:actions/checkout@v4with:fetch-depth:0- name:Generate release notesid:notesuses:pullfrog/pullfrog@v0with:prompt:| Generate release notes for ${{ github.ref_name }}.
Compare commits between this tag and the previous tag.
Format as markdown: summary paragraph, then ### Features, ### Fixes, ### Breaking Changes sections.
Omit empty sections. Be concise.env:ANTHROPIC_API_KEY:${{ secrets.ANTHROPIC_API_KEY }}# write to file to avoid shell escaping issues with special characters- name:Create GitHub releaserun:| notesfile="$RUNNER_TEMP/release-notes-$GITHUB_RUN_ID.md"
printf '%s' "$NOTES" > "$notesfile"
gh release create ${{ github.ref_name }} --title "${{ github.ref_name }}" --notes-file "$notesfile"env:GH_TOKEN:${{ github.token }}NOTES:${{ steps.notes.outputs.result }}
Example: Structured Output with Zod Schema
You can force the agent to return structured JSON output by providing a JSON schema. This allows you to reliably parse and use the agent's response in subsequent workflow steps.
You can define your JSON schema directly or uou can use any validation library that converts to JSON Schema. Here's an example using Zod:
name:Release Checkon:pull_request:types:[closed]jobs:check-release:if:github.event.pull_request.merged == trueruns-on:ubuntu-lateststeps:- uses:actions/checkout@v4- name:Install dependenciesrun:npm install --no-save --no-package-lock zod @actions/core- name:Generate Schemaid:schemarun:| node -e '
import { z } from "zod";
import { setOutput } from "@actions/core";
const schema = z.object({
version: z.string().describe("Semantic version number (e.g. 1.0.0)"),
isBreaking: z.boolean().describe("Whether this release contains breaking changes"),
changelog: z.array(z.string()).describe("List of changes in this release"),
});
setOutput("schema", JSON.stringify(z.toJSONSchema(schema)));
'- name:Analyze PRid:analysisuses:pullfrog/pullfrog@v0with:prompt:| Analyze this PR and determine semantic versioning impact.
Return a JSON object matching the provided schema.output_schema:${{ steps.schema.outputs.schema }}env:ANTHROPIC_API_KEY:${{ secrets.ANTHROPIC_API_KEY }}- name:Process Resultrun:| # Parse the JSON result using fromJSON()
echo "Version: ${{ fromJSON(steps.analysis.outputs.result).version }}"
echo "Breaking: ${{ fromJSON(steps.analysis.outputs.result).isBreaking }}"