Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Manage the credit system (allocation, purchasing, usage). Use when adding credit types, configuring pricing, or building credit UI.
.claude/skills/aiskillstore-credits-handler/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-06 | ✗→✓ | ▲ Improved | 163% | 0% |
| case-01 | ✗→✓ | ▲ Improved | -3% | 0% |
| case-02 | ✗→✓ | ▲ Improved | -5% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 33% | 0% |
| case-04 | ✗→✓ | ▲ Improved | -8% | 0% |
This skill guides you through the entire credit system, from backend configuration to frontend UI implementation.
All credit configuration lives in src/lib/credits/config.ts.
creditTypeSchema enum.typescript export const creditTypeSchema = z.enum([ "image_generation", "video_generation", "your_new_credit_type" // Add this ]);
creditsConfig.typescript your_new_credit_type: { name: "New Credit Name", currency: "USD", minimumAmount: 10, // Option A: Fixed Slabs slabs: [ { from: 1, to: 100, pricePerUnit: 0.10 }, { from: 101, to: 1000, pricePerUnit: 0.08 }, ], // Option B: Dynamic Calculator (e.g., based on user plan) priceCalculator: (amount, userPlan) => { // Logic here return amount * 0.1; } }
onPlanChangeCredits.To let users purchase credits, use the useBuyCredits hook. This hook handles price calculation (factoring in plan discounts) and generating checkout URLs.
useBuyCreditsLocation: src/lib/credits/useBuyCredits.ts
typescriptimport useBuyCredits from "@/lib/credits/useBuyCredits"; import { PlanProvider } from "@/lib/plans/getSubscribeUrl"; const { price, // Calculated total price (number | undefined) isLoading, // Price calculation in progress error, // Error state getBuyCreditsUrl // Function to generate payment URL } = useBuyCredits(creditType, amount);
Here is a pattern for creating a credit purchase UI, similar to src/components/website/website-credits-section.tsx.
tsx"use client"; import { useState } from "react"; import useBuyCredits from "@/lib/credits/useBuyCredits"; import { PlanProvider } from "@/lib/plans/getSubscribeUrl"; import { Button } from "@/components/ui/button"; import { Loader2 } from "lucide-react"; // 1. Define your packages const PACKAGE = { credits: 100, name: "Starter Pack", }; export function BuyCreditsCard({ creditType }: { creditType: "image_generation" }) { const [provider] = useState(PlanProvider.STRIPE); // or LEMONSQUEEZY // 2. Call the hook const { price, isLoading, error, getBuyCreditsUrl } = useBuyCredits( creditType, PACKAGE.credits ); // 3. Handle Purchase const handleBuy = () => { const url = getBuyCreditsUrl(provider); window.location.href = url; }; // 4. Render UI return ( <div className="border p-4 rounded-lg"> <h3>{PACKAGE.name}</h3> <div className="text-2xl font-bold"> {isLoading ? ( <Loader2 className="animate-spin" /> ) : ( `$${price?.toFixed(2) || "0.00"}` )} </div> <Button onClick={handleBuy} disabled={isLoading || !price} className="w-full mt-4" > Buy {PACKAGE.credits} Credits </Button> {error && <p className="text-red-500 text-sm">{error.message}</p>} </div> ); }
To show the user's current balance, use the useCredits hook.
useCreditsLocation: src/lib/users/useCredits.ts
typescriptconst { credits, // Record<string, number> | undefined // e.g. { "image_generation": 100, "video_generation": 50 } isLoading, // boolean error, // any mutate // SWR mutate function to refresh data } = useCredits();
tsximport useCredits from "@/lib/users/useCredits"; export function CreditBalance() { const { credits, isLoading } = useCredits(); if (isLoading) return <div>Loading...</div>; return ( <div> Image Credits: {credits?.image_generation || 0} </div> ); }
These functions are used in API routes and webhooks.
Use allocatePlanCredits to give users credits when they subscribe/upgrade.
src/lib/credits/allocatePlanCredits.tsuserId, planId, paymentId (for idempotency).onPlanChangeCredits config and adds credits if applicable.To manually manipulate balances, use helpers from src/lib/credits/recalculate.ts (e.g., addCredits, deductCredits). Note: Always ensure you have a unique `paymentId` or transaction reference when adding credits to prevent duplicates.
Use canDeductCredits before performing an action.
typescriptimport { canDeductCredits } from "@/lib/credits/credits"; // Check if user has enough credits const hasBalance = canDeductCredits( "image_generation", 1, user // Must contain { credits: { ... } } ); if (!hasBalance) { throw new Error("Insufficient credits"); }
For deep dives into database schema and architecture, see reference.md.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-06 | fail→pass | 4,666 | 1,521 | -67% | 1 | 1 | 0% | 642 | 1,691 | +163% | 0 | 0 | — |
case-01 | fail→pass | 12,421 | 4,435 | -64% | 1 | 1 | 0% | 2,498 | 2,427 | -3% | 0 | 0 | — |
case-02 | fail→pass | 17,451 | 9,020 | -48% | 1 | 1 | 0% | 3,515 | 3,355 | -5% | 0 | 0 | — |
case-03 | fail→pass | 13,042 | 6,757 | -48% | 1 | 1 | 0% | 2,112 | 2,816 | +33% | 0 | 0 | — |
case-04 | fail→pass | 10,836 | 1,469 | -86% | 1 | 1 | 0% | 1,874 | 1,720 | -8% | 0 | 0 | — |
case-05 | fail→pass | 12,925 | 6,105 | -53% | 1 | 1 | 0% | 2,211 | 2,633 | +19% | 0 | 0 | — |
case-07 | fail→pass | 10,806 | 3,204 | -70% | 1 | 1 | 0% | 1,818 | 2,060 | +13% | 0 | 0 | — |
case-08 | fail→pass | 6,955 | 1,537 | -78% | 1 | 1 | 0% | 1,036 | 1,717 | +66% | 0 | 0 | — |
case-09 | pass→pass | 10,574 | 2,608 | -75% | 1 | 1 | 0% | 1,719 | 1,911 | +11% | 0 | 0 | — |
case-10 | fail→pass | 12,071 | 5,112 | -58% | 1 | 1 | 0% | 2,163 | 2,339 | +8% | 0 | 0 | — |
case-11 | fail→pass | 12,883 | 1,625 | -87% | 1 | 1 | 0% | 1,946 | 1,729 | -11% | 0 | 0 | — |
case-12 | fail→pass | 6,587 | 1,392 | -79% | 1 | 1 | 0% | 1,016 | 1,740 | +71% | 0 | 0 | — |
case-13 | fail→pass | 12,022 | 1,424 | -88% | 1 | 1 | 0% | 1,966 | 1,681 | -14% | 0 | 0 | — |
case-14 | pass→pass | 7,222 | 1,862 | -74% | 1 | 1 | 0% | 1,083 | 1,758 | +62% | 0 | 0 | — |
case-15 | fail→pass | 3,294 | 2,691 | -18% | 1 | 1 | 0% | 531 | 1,893 | +256% | 0 | 0 | — |
case-16 | fail→pass | 11,792 | 1,730 | -85% | 1 | 1 | 0% | 1,851 | 1,764 | -5% | 0 | 0 | — |
case-17 | fail→pass | 13,106 | 6,628 | -49% | 1 | 1 | 0% | 2,155 | 2,691 | +25% | 0 | 0 | — |
case-18 | fail→pass | 14,213 | 4,720 | -67% | 1 | 1 | 0% | 2,212 | 2,361 | +7% | 0 | 0 | — |
case-19 | fail→pass | 10,608 | 2,051 | -81% | 1 | 1 | 0% | 1,600 | 1,828 | +14% | 0 | 0 | — |
case-20 | pass→pass | 13,425 | 10,774 | -20% | 1 | 1 | 0% | 2,683 | 3,758 | +40% | 0 | 0 | — |
case-21 | pass→pass | 5,879 | 3,969 | -32% | 1 | 1 | 0% | 1,020 | 2,215 | +117% | 0 | 0 | — |
case-22 | pass→pass | 10,331 | 7,025 | -32% | 1 | 1 | 0% | 1,938 | 2,723 | +41% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +77 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.