Add a tool page
Ask the agent for a new paid AI feature - what to tell it, which files it creates, and how to test the sign-in wall, the credit wall, a run and a refund.
Outcome: a public page at /<tool> in every language. A signed-in user presses generate, the route charges a fixed number of credits, calls the AI provider, and returns the result. A failed call gives the credits back. The agent's checks print:
✓ app/api/<tool>/route.ts charges <N> credits per call
and pnpm lint and pnpm test pass.
The agent follows skills/add-tool-page/SKILL.md. It copies the admin-only demo under /ai-demo and turns the copy into a product page.
Before you start
- The AI provider answers: the AI integration passed.
- Users can sign in, and the database works.
- Your test user has credits. Every new account gets 30 (
signupBonusCreditsinconfig/credits.ts); more come from a plan inconfig/pricing.ts.
What to tell the agent
Give it five things. The agent asks for any you leave out, one at a time.
Add a tool page.
- Name and URL: Background Remover, /bg-remover
- Input: one uploaded photo (PNG, JPG or WebP)
- Output: the same photo with a transparent background, as a PNG to download
- Provider and model: fal, <model id from config/ai-models.ts or the provider's docs>
- Price: 5 credits per run
- Name and URL. The slug is lowercase with dashes. It becomes the page path and the name of the files.
- Input. A prompt, an image, both, or something else. Say what a user must provide.
- Output. An image, a video, a text. Say whether the user downloads it.
- Provider and model. A model listed in
config/ai-models.ts, or one the provider documents. If users may choose between models, say so and give a price per model. - Price in credits. One number per run, or one per model. It lives in one constant (
PRICE) at the top of the route file.
A provider that answers within one request (chat, most image models) gives a sync tool. A provider that works for minutes and calls back (video) gives an async tool with a status route; it also needs the webhook address and Redis from the AI setup.
What you get
| File | What it is |
|---|---|
app/[locale]/(basic-layout)/<tool>/page.tsx |
the page, with the site header and footer |
app/api/<tool>/route.ts |
the API route: sign-in check, credit charge, provider call, refund on failure |
app/api/<tool>/status/route.ts |
async tools only: lets the page poll for the result, only for the user who started it |
components/<tool>/<Tool>Page.tsx |
the form and the result, with the sign-in dialog and the "not enough credits" message |
i18n/messages/<locale>/<Tool>.json |
the page text, one file per language, each written in that language |
It also edits: i18n/request.ts (registers the text file), i18n/messages/<locale>/common.json (header and footer links), app/sitemap.ts (the new path in every language), and for async tools lib/ai/video.ts and lib/ai/task-store.ts (the refund when a job fails later).
The /ai-demo pages stay admin-only. Your tool does not reuse them.
How to test it
Run the site locally (pnpm dev, see Run locally) and go through this list. The agent runs the same list; it is short enough to do yourself once.
| Do | Expect |
|---|---|
Open /<tool>, then /de/<tool>, /ja/<tool> and the other languages |
the page loads, the title is translated, the link is in the header and footer |
| Login wall: signed out, press generate | the sign-in dialog opens (or you go to /login?next=/<tool>, depending on NEXT_PUBLIC_LOGIN_MODE); no credits move |
| A run: sign in, generate once | the result appears; the balance in the sidebar drops by the price, and Dashboard → Credit History shows the spend with the note <tool> |
| Credit wall: use an account with fewer credits than the price (or ask the agent to set the price to 999999 for one try) | a "not enough credits" message with a button to the pricing page; the balance does not change |
| A failure and its refund: ask the agent to point the route at a model id that does not exist, then generate | an error message with the provider's own words; Credit History shows the charge and a refund, and the balance is back where it was |
| Async tools: one real job | the page polls until the result appears; a failed job is refunded when the provider reports the failure |
Open /sitemap.xml |
it lists /<tool> for every language |
Ask the agent to undo the test changes (the price, the model id) when you are done.
No limits of our own
The site does not refuse users with checks of its own. The provider decides what it generates.
- No prompt length cap, image size cap, file format allow-list or content filter added by the site. The demo carries a few (a 5000-character prompt limit, a 5 MB image limit); the agent drops them when it copies the demo, or uses the limits the provider documents.
- When the provider refuses a request, the user sees the provider's reason and gets the credits back.
- The provider's own safety system stays on. The one block the site may add itself is sexual content involving minors.
- Limits that are not about content stay: credits, rate limits, and checks that an uploaded file really is the type it claims.
If you want a different rule for your product, say so explicitly when you ask for the tool.
When it fails
| What you see | Cause | Fix |
|---|---|---|
✗ … never calls refundSpentCredits from scripts/check-ai.ts |
the route charges but does not refund on failure | the agent adds the refund to every failure path |
A ✗ line naming a route that generates without charging |
the route calls a provider but never calls consumeCredits |
the agent adds the charge, or makes the route admin-only if that was the intent |
| Generate does nothing for signed-out users | the client does not handle the 401 answer |
the agent adds the sign-in dialog handling |
| Every run fails with an authentication error from the provider | the provider key is missing or wrong in this environment | rerun the AI integration check; in production, the key must be in the deployed env |
| A page in one language shows English text | that language's <Tool>.json is missing a key; missing keys fall back to English |
ask the agent to translate the missing keys |
| Async job never finishes locally | the provider cannot reach your machine | the AI setup's tunnel and WEBHOOK_BASE_URL must be running |